Monday, August 16, 2010

GOOS – Gedanken und Zusammenfassung des zweiten Teils

GOOS – Blogeintrag 1

GOOS – Blogeintrag 2

TDD ist ja absolut keine neue Technik um Software zu entwickeln, meines Erachtens allerdings die einzig richtige Vorgehensweise, um auch über einen längeren Zeitraum ein Projekt wartbar zu halten. Damit meine ich nicht nur Bugs bereinigen, sondern auch neue Features hinzufügen. Wenn man es daher genau nimmt, dann wird ja ein Projekt ab dem Zeitpunkt des ersten Releases gewartet. Daher sollte man der Wartung besondere Aufmerksamkeit widmen.

Im zweiten Teils des Buchs beschreiben die Autoren ihre persönlichen Sichtweises bezüglich TDD und Objektorientierter Programmierung. Einige Dinge werden dabei immer wieder betont und wiederholt. Ich habe schon einige Bücher zur Objektorientierung und TDD gelesen, allerdings haben mich die Sichtweisen dieser beiden Autoren am meisten begeistern und überzeugen können. Das liegt zuletzt mit Sicherheit an der langjährigen Projekterfahrung der Autoren mit diesen Techniken. Wenn man selbst schon einige Erfahrung mit TDD machen konnte, hat man sich mit Sicherheit auch schon gefragt, wie man TDD bei größeren (Enterprise)Anwendungen einsetzen würde.

TDD für ein neues Projekt

image

Eigentlich freut man sich ja als Entwickler ein “Greenfield”-Projekt starten zu können. Aber wie setzt man TDD ein wenn noch absolut keine Infrastruktur vorhanden ist? Wenn man ein neues Feature beginnen möchte, dann fängt man ja mit einem Akzeptanztest (End-To-End) an. Ein solcher sollte so “End-To-End” wie möglich sein, d.h. Build, Deploy und Test sollte ebenfalls im Test beinhaltet sein. Doch beim absolut ersten Feature ist man sich der benötigten Infrastruktur noch nicht 100% sicher. Also wie kann ich dann mit einem Akzeptanztest beginnen, der so “End-To-End” wie möglich, allerdings noch keine Infrastruktur vorhanden ist?

Zu diesem Zeitpunkt führen die Autoren den Begriff des Walking Skeletons” ein. Dabei handelt es sich um die dünnste Schicht echter Funktionalität, die man Bauen, Deployn und Testen kann. Wichtig ist dabei, dass man dafür nicht zu viel Zeit verbraucht. Es sollte sich dabei mehr oder weniger um eine “Whiteboard”-Architektur der Anwendung handeln, die mit der Zeit natürlich wachsen und verbessert werden kann. Wichtig ist nur, dass alle Entwickler eine gemeinsames Verständnis der Architektur und der damit verbunden Infrastruktur entwickelt haben. Sauberkeit und Ausdrucksstärke des ersten Akzeptanztests, der diese Infrastruktur beinhaltet, ist dabei noch nicht so wichtig.

Dieses so genannte “Walking Skeleton” soll also gar nicht perfekt sein. Jedoch ist der dabei entstehende Lernprozess und das Feedback extrem wichtig für den Projektfortschritt. Okay, jetzt wissen wir also wie wir bei einem neuen Projekt mit Hilfe von TDD und mittels eines “Walking Skeleton” den ersten Entwurf unserer Architektur entwickeln. Wie geht es nun weiter? Wie halten wir diesen Prozess am Laufen?

TDD Prozess am Laufen halten

image

Grundsätzlich fängt man von Außen nach innen an. Für jedes neue Feature entwickelt man zuerst einen neuen  fehlschlagenden Akzeptanztest. Wichtig dabei ist, diesen Test möglichst technologieneutral zu implementieren, d.h. nur Ausdrucksweisen der Domäne zu verwenden. Der Vorteil von Akzeptanztest sollte bereits klar sein: Die Präzision Anforderungen in automatisierten Tests zu spezifizieren hilft implizite Annahmen zu vermeiden.

Ganz wichtig ist dabei auch die Trennung zwischen Unit Tests und Akzeptanztests, die ja den Projektfortschritt anzeigen und daher solange fehlschlagen sollten, bis das neue Feature fertig implementiert ist. Im Gegensatz zu Unit- und Integrations-Tests, die schnell und immer sauber sein sollten und dem Entwickler zur Unterstützung dienen.

Weitere wichtige Empfehlung der Autoren sind: Man sollte immer mit dem dem einfachsten erfolgreichen Fall anfangen (nicht mit Fehlerfällen) zu testen. Tests sollten so geschrieben werden, wie man sie selbst gerne lesen würde und Fehlermeldungen sollten solange verbessert werden, bis sie klar ausdrücken was schief gegangen ist. Außerdem sollten Testnamen das Verhalten des zu testenden Objekts im Kontext des Szenarios beschreiben indem es getestet wird.

 OO Style

image

TDD ist einer der beiden Techniken um Software möglichst wartbar zu gestalten, OOP ist die Andere. Allerdings weiß eder OOP-Entwickler, dass man bei dieser Art der Programmierung auch einiges falsch machen. Wie erreicht man also, das Code so einfach wie möglich gleichzeitig aber auch so wartbar wir möglich geschrieben wird?   Die Autoren erwähnen zwei Heuristiken um solchen Code zu schreiben und strukturieren: “Separation of Concerns” und “Higher Levels of Abstractions”. Kommen diese beiden Prinzipien zur Anwendung entstehen so genannte “Ports and Adapters”-Architekturen, wobei der Domänencode von technologieabhängigen Code getrennt entwickelt wird.

Ports sind Interfaces die die Beziehungen mit der äußeren Welt beschreiben. Adapters sind Brücken zwischen der Applikationsdomäne und der technischen Domäne. Diese Brücken implementieren die Interfaces die in der Applikationsdomäne definiert werden. Sie sind daher ein Mapping zwischen Domänen- und technischen Objekten. Zwei weitere wichtige Richtlinien sind die Kapselung und Information Hiding. Diese beiden gilt es nicht zu verwechseln. Kapselung stellt sicher dass das Verhalten eines Objekts nur über dessen öffentliche Schnittstelle beeinträchtigt wird und Information Hiding verbirgt wie ein Objekt sein Funktionalität hinter dessen Abstraktion der öffentlichen Schnittstelle implementiert.

Was ich im Buch auch immer wieder lese, ist die Wichtigkeit der dynamischen Struktur des Objektgeflechts. Die Kommunikation von Objekten und die dabei entstehenden Kommunikationsmuster sind das “Um und Auf”. Peers nennt man dabei die Nachbarn eines Objekts mit denen es kommuniziert, also Nachrichten sendet und empfängt. Dabei ist wichtig, dass ein Objekt nicht zu viel Interna preisgibt, da sonst der Client eventuell zu viel Arbeit übernimmt und dabei verteiltes Verhalten entstehen würde. Dabei handelt es sich natürlich um einen absoluten Wartungsalbtraum. Folgende Arten von Peers eines Objekts werden im Buch beschrieben: Dependencies, Notifications und Adjustments.

Dependecies eines Objekts bieten Services ohne denen das Objekt nicht seinen Verantwortlichkeiten nachgehen kann. Notifications sind Peers die über den Status des Objekts bescheid wissen müssen (“fire and forget”). Bei Adjustments handelt es sich um Peers die das Verhalten eines Objekts verändern, um den breiteren Anforderungen des Systems zu genügen.

Auch Composites und das gegenteilige Konzept dazu “Context Independence” wird im Buch näher beschrieben. Dabei betonen die Autoren, dass das API eines zusammengesetzten Objekts nie komplizierter sein sollte, als die der einzelnen Komponenten. Context Independence stellt fest ob ein Objekt zu viel oder die falschen Dinge verbirgt. Diese Technik angewendet, ergibt explizite Abhängigkeiten zwischen Objekten die jedoch nicht in den Objekten definiert werden (Factory Objects).

OO Design erreichen

image

Wie setzt man nun all diese Techniken und Prinzipien im Code um? Wie kann ich dies mittels TDD erreichen? Prinzipiell kann man sagen, dass ein Client immer wissen möchte wovon ein Objekt abhängt und was es tut aber nicht wie es etwas tut. Bei TDD schreibt man zuerst den Test, d.h. wir müssen uns darüber Gedanken machen, was wir erreichen wollen bevor wie wir wissen wie wir es erreichen.

Wichtig dabei ist, dass man den richtigen Level of Abstraction findet. Wenn die Absicht des Unit-Tests unklar oder nicht eindeutig ist, dann kann es sein dass wir eventuelle Konzepte vermischen. Dazu dient das Konzept des Information Hiding, also was kann die äußere Welt sehen. Durch einschränken des Scopes eines Unit-Tests kann auch verhindert werden, dass der Test zu lange und daher unsauber wird.

Wie bereits erwähnt, liegt der Fokus des Designs darauf wie Objekte miteinander kollaborieren um die gewünschte Funktionalität zu erreichen. Klassenstruktur ist auch wichtig, jedoch sollte die Hauptaufmerksamkeit den Kommunikationsmustern (=Kommunikationsprotokoll) gewidmet werden.

“Ein Interface beschreibt ob zwei Objekte zusammenpassen während ein Protokoll beschreibt ob zwei Objekte miteinander arbeiten können”

Eine weiterer Grund für unklare Tests kann eventuell das Preisgeben von zu viel Information der Implementierung eines Objekts sein. Das bedeutet, dass vielleicht die Verantwortlichkeiten zwischen dem Objekt und dessen Peers neu überdacht bzw. re-balanciert  werden müssen.

Weitere Abschnitte des Buchs behandeln das Finden von Value Types und Objekten. Dabei kommen folgende Techniken für  Value Types zum Einsatz: Breaking out, Budding off und Bundling up. DieseTechnik kann auch auf echte Objekte angewendet werden. Dabei handelt es sich beim Budding Off um ein so genanntes “On-Demand” Design. D.h., Interfaces und deren Implementierung werden aus der Sicht und Bedarfs des Client entwickelt anstatt auf Verdacht Features einer Klasse zu erstellen. Das vermeidet das Preisgeben von zu viel Information über die eigentliche Implementierung und somit wird die implizierte Kopplung minimiert und der Code bleibt formbar. Interfaces bezeichnen dabei Rollen die ein Objekt einnehmen kann und die Nachrichten die sie empfangen bzw. senden können.

Im dritten Teil des Buchs werden kommen nun alle Techniken anhand eines Beispiel-Projekts zum Einsatz. Bei einigen Dingen bin ich mir noch nicht ganz sicher, jedoch hoffe ich, dass mir der Code dabei helfen wird, die beschriebenen Prinzipien und Techniken noch besser zu verstehen.

Sunday, August 08, 2010

GOOS – Gedanken zum ersten Teil

Wow! Obwohl es sich beim ersten Teil doch eher um eine Einführung und Wiederholung der Themen TDD, OOD und Mocks handelt, habe ich eine Menge neuer Dinge gelernt. Bei einigen bin ich mir allerdings noch nicht 100% sicher, ob ich die Konzepte wirklich verstanden habe. Der erste Teil des Buchs besteht aus einer Einleitung, einem Kapitel über “What is the Point of TDD”, einem über “TDD with Objects” und ein kurzer Überblick der Tools die im Buch zum Einsatz kommen.

Objektgeflecht

In meinem letzten Blog-Post habe ich bereits über das Warum? bezüglich TDD geschrieben. Das darauffolgende Kapitel über “TDD with Objects” war äußerst interessant. Es betont die Wichtigkeit zu verstehen, dass es bei OOP nicht um die Objekte selbst sondern vielmehr um die Kommunikation zwischen diesen Objekte geht. Es entsteht ein Objektgeflecht, dass für das Verhalten des Systems maßgeblich verantwortlich ist. Dieses Verhalten kann man natürlich beeinflussen indem man dieses Netz aus kollaborierenden Objekten konfiguriert: Mit welchen Objekten ist ein Objekt verbunden; Objekte können ausgetauscht werden; Komplette Objektkompositionen können ersetzt und neu konfiguriert werden. Es handelt sich dabei um eine deklarative Vorgehensweise wie wir das Verhalten unseres Systems beeinflussen.

Objektgeflecht

Values und Objekte

Ich persönlich tue mir hin und wieder noch etwas schwer den Unterschied zwischen Objekten und reinen Werten zu unterscheiden. Auch hierzu bietet das Buch einen gesonderten Abschnitt zu dieser Thematik. Der Punkt, dass beide Varianten in vielen OOP Sprachen vom selben Konstrukt, nämlich einer Klasse, gebildet werden, trägt zum Verständnis nicht unbedingt bei. Eines ist jedoch ganz klar, wir teilen unser System in zwei Teile. Der eine Teil besteht aus unveränderlichen Werten und der andere aus Objekten die eine eigene Identität besitzen und somit den Zustand des Systems repräsentieren.

image

Kommunikationsmuster

Wie bereits erwähnt, ist die Kommunikation zwischen Objekten das wichtigste Konstrukt in einem OOP System. Die entstehenden Kommunikationspfade können mit Hilfe von so genannten Communication Patterns organisiert werden. Genau diese Muster bilden die eigentliche Domäne des Systems ab (im Gegensatz zum statischen Konstrukt der Klasse). Diese Denkweise ist auch für mich relativ neu und wird noch einiges an Übung erfordern um komplett von mir verstanden zu werden. Noch dazu wird das Domänenmodell verschleiert, da diese Kommunikationsmuster kein eigenes Konstrukt in einer derzeitigen OOP Sprache darstellen. Um diese Thematik besser verstehen zu lernen, wird auf das Buch von Rebecca Wirfs-Brock “Object Design: Roles, Responsibilities and Collaborations” verwiesen.

Tell, don’t ask!

Ein weiterer wichtiger Punkt ist das Law of Demeter bzw. der “Tell, don’t ask”-Style. Dabei geht es darum, dass Objekte aufgrund ihres eigenen Zustands Entscheidungen treffen sollen. Objekte sollten nicht aufgrund des Zustands anderer Objekte Entscheidungen treffen. Dadurch können “Train wreck” Konstrukte vermieden werden, was dazu führt, dass wir nicht nur Information Hiding unterstützen, sondern auch die Kommunikation zwischen Objekten explizit machen. Anders ausgedrückt, ein Objekt muss ganz genau sagen was es erreichen möchte und nicht wie.

Natürlich gibt es wie bei fast allen Richtlinien auch hier Ausnahmen. Das Abfragen von Werten oder Collections ist natürlich erlaubt. Hin und wieder müssen sogar richtige Objekte nach ihrem Zustand gefragt werden. Dabei sollte dem passendsten Objekt die Abfragemethode hinzugefügt und der Name der Methode so ausdrucksstark wie möglich formuliert werden. Solche Methoden sollten aber nur sparsam eingesetzt werden, da sonst das System unflexibel werden kann.

Mock-Objekte

Daraus folgt natürlich die Frage: Ok, ich soll also meinen Objekten sagen was sie tun sollen und nicht irgendwelche Eigenschaften abfragen. Aber wie teste ich dann ein Objekt das nur Kommandos besitzt und keine Rückgabewerte? Das ist der Zeitpunkt indem die Rolle der Mock-Objekte eingeführt wird. Im Buch wird das von den Autoren entwickelte Mocking-Framework JMock propagiert. Jedoch egal welches Framework verwendet wird, der wichtigste Punkt welcher beachtet werden sollte ist folgender:

Die Intention eines Tests sollte immer klar zum Ausdruck gebracht werden. Dabei muss eindeutig zwischen der zu testenden Funktionalität, der notwendigen Infrastruktur und der Objektstruktur unterschieden werden.

Saturday, August 07, 2010

Growing Object-Oriented Software (GOOS)

Nachdem ich mein letztes Buch (Softwaretests mit JUnit von Johannes Link) fertiggelesen habe und von der Materie einfach nur begeistert und überzeugt bin, habe ich beschlossen ein weiteres Buch, dass sich mit dem Thema TDD beschäftigt, zu lesen. Nur welches? Es gibt so extrem viele, die ich alle noch unbedingt lesen möchte, allerdings hat sich eines davon ganz klar abgesetzt, nämlich: “Growing Object-Oriented Software, Guided by Tests

So ziemlich jeder meiner Entwickler-Vorbilder, haben dieses Buch empfohlen. Man solle es allerdings nicht nur lesen, sondern die Vorgehensweisen der Autoren wirklich nachvollziehen und verstehen. Michael Feathers spricht von “Sate of the Art TDD”, Robert C. Martin sagt “This one’s a keeper” und noch einige mehr sind davon überzeugt, dass das Lesen und Verstehen dieses Buchs die eigenen Entwicklerfähigkeiten auf ein anderes Niveau anheben wird.

Fragen

Dieser Blog-Post soll keine Zusammenfassung des kompletten Buchs werden, sondern vielmehr einer von vielen, die meine Gedanken bezüglich der einzelnen Kapitel wiedergeben. Ich bin ja schon seit einiger Zeit überzeugter “TDDler” und doch stellen sich mir immer wieder Fragen wie z.B.:

  • Wie baut man ein größeres verteiltes System mittels TDD?
  • Wie viel wird vorab Designed?
  • Was bedeutet eigentlich die Phrase: “Die Tests treiben meine Entwicklung”?
  • Wie und wo fängt man eigentlich an?
  • Wie behält man bei einer großen Anzahl von Tests die Übersicht?
  • Wann ist man fertig?

Natürlich werden noch weitere Fragen auftauchen und hoffentlich mit Hilfe dieses Buchs beantwortet werden. Mittlerweile habe ich das erste Kapitel “What is the point of TDD?” fertiggelesen und bin begeistert. Es konnten schon einiger meiner Fragen zum Teil beantwortet werden. Wie in jedem von mir bereits gelesenen Buch zum Thema TDD, Clean Code und Refactoring, werden auch in diesem Buch diese Begrifflichkeiten im ersten Kapitel auf allgemeinster Ebene behandelt.

Testarten

Auch die Fragen wie TDD das Software-Design beeinflusst wird äußerst gut erklärt. Des weiteren findet man Definitionen zu verschiedenen Testarten wie z.B.: Unit-, Integration- und Acceptance-Tests. Allerdings handelt es sich dabei nicht um irgendwelche trockenen, schwer-verstehbaren Beschreibungen, sondern um gut erklärte, leicht nachvollziehbare Sichtweisen auf diese Themen.

Feedback

Die Autoren betonen auch immer wieder wie wichtig Feedback in der Softwareentwicklung heutzutage ist. Man sollte auf jeder Ebene, Mikro- also auch Makroebene, Feedback-Schleifen einbauen, um ständig dazuzulernen und das System wen nötig zu verbessern. Je früher man im Entwicklungsprozess Unklarheiten beseitigt und dadurch Fehler vermeiden kann bzw. aufdeckt desto billiger ist es natürlich auf diese zu reagieren und Verbesserungen vorzunehmen.

Akzeptanztests

Was für mich relativ neu war bzw. was ich nach lesen des ersten Kapitels dazugelernt habe, ist die Wichtigkeit des Akzeptanztests zu verstehen. Bevor man ein neues Feature in einem System hinzufügt, muss der dazugehörige Akzeptanztest geschrieben werden. Inwiefern dieser aussehen muss wird im Buch äußerst gut erklärt. Allerdings bin ich mir noch nicht ganz sicher wie ein solcher tatsächlich im Code festgeschrieben wird. Jedoch beinhaltet das Buch auch einen eigenen Teil wo ein kleines System entwickelt wird. In diesem wird mit Sicherheit auch mindestens ein Akzeptanztest geschrieben.

Qualität in Symbiose mit TDD

Meiner Ansicht nach trägt der Abschnitt über “External and Internal Quality” stark dazu bei, TDD zumindest einmal in einem Projekt ausgetestet zu haben. Es wird ausführlich beschrieben, wie TDD den geschrieben Code qualitätsmäßig in positiver Hinsicht beeinflusst. Wenn man seine Klassen testbar gestaltet, d.h.: man definiert klar die Abhängigkeiten und Verantwortlichkeiten dann erhält man automatisch eine besser Kopplung und Kohäsion seines Designs und ganz wichtig:

“TDD ist eine Technik um mit unvermeidlichen, unvorhersehbaren Veränderungen (in jeglicher Hinsicht) im Softwareentwicklungsprozess umzugehen”.

Also: “Never write new functionality without a failing unit test.”