Blauer Grad

Prinzipien

Design and Implementation Don’t Overlapp

Warum?
Planungsunterlagen, die mit der Umsetzung nichts mehr gemein haben, schaden mehr, als dass sie nützen. Deshalb nicht die Planung aufgeben, sondern die Chance auf Inkonsistenz minimieren.
Wandelbarkeit   
Korrektheit   
Produktionseffizienz   
Kontinuierliche Verbesserung   
Team

Eines der grundlegenden Probleme der Softwareentwicklung sind Implementationen, denen eine vorausgegangene Planung nicht mehr anzusehen ist. Da hängen dann Entwurfsdiagramme an der Wand, die kaum noch etwas mit der Coderealität zu tun haben. Die Ursache dafür ist eine Verletzung des fundamentalen DRY-Prinzips: Entwurf und Implementation sind Wiederholungen desselben, der Struktur einer Software. Da Implementation auf Entwurf folgt und den Löwenanteil der Arbeit ausmacht, geraten beide schnell aus dem Tritt, wenn Strukturänderungen während der Implementation nicht immer wieder in den Entwurf eingearbeitet werden. Entwurfsdiagramme sind nach Beginn der Implementation sonst bald nichts mehr wert.

Wie kann die Situation verbessert werden? Sollte vielleicht auf Entwurf verzichtet werden, wenn letztlich in der Implementation die „Strukturwahrheit“ liegt? Nein, sicher nicht. Entwurf muss sein. Ohne Planung gibt es keine Zielvorstellung. Aber Entwurf und Implementation müssen dem DRY-Prinzip gerecht werden. Deshalb sollten Entwurf und Implementation sich so wenig überlappen wie möglich. Ihre Schnittstelle sollte dünn sein. Wenn das der Fall ist, stellen sie keine Wiederholungen mehr dar, sondern beschreiben unterschiedliches. Das bedeutet: Entwurf/Architektur kümmert sich nicht um die Implementation und Implementation kümmert sich nicht um Architektur.

Und wo verläuft diese Trennlinie? Bei den so genannten Komponenten (s.u. Praktiken). Architekten kümmern sich nicht um den internen Aufbau von Komponenten. Für sie sind es Black Boxes, deren Klassenstruktur nicht architekturrelevant ist. Umgekehrt ist für einen Komponentenimplementierer die Architektur irrelevant. Was er zu implementieren hat, ergibt sich aus den Komponentenkontrakten, die seine Komponente importiert und exportiert. Einen größeren Zusammenhang muss er nicht kennen.

Die Aufgabe der Architektur ist es mithin, Software in Komponenten zu zerlegen, deren Abhängigkeiten zu definieren und Leistungen in Kontrakten zu beschreiben. Diese Strukturen werden dann auch einzig durch Architekten gepflegt. Und die Aufgabe der Implementation ist es, die von der Architektur definierten Komponenten zu realisieren. Wie sie das tun, ist nicht architekturrelevant. Ihre innere Struktur ist für die Architektur unsichtbar.

Implementation Reflects Design

Warum?
Umsetzung, die von der Planung beliebig abweichen kann, führt direkt in die Unwartbarkeit. Umsetzung braucht daher einen durch die Planung vorgegebenen physischen Rahmen.
Wandelbarkeit   
Korrektheit   
Produktionseffizienz   
Kontinuierliche Verbesserung   
Team

Architektur und Implementation sollen nicht überlappen, damit sie das DRY-Prinzip nicht verletzten. So werden Inkonsistenzen vermieden, die dadurch entstehen können, dass auf der einen Seite etwas geändert wird, ohne diese Änderung auf der anderen Seite nachzuführen.

Nichtsdestotrotz macht die Architektur aber ja Aussagen über die Implementation. Nicht ihre Details, aber ihre grundsätzliche Form. Architektur definiert die Strukturelemente und deren Beziehungen innerhalb eines Codesystems. Implementation existiert also auch bei Abwesenheit von Überlappungen nicht unabhängig von Architektur, sondern sozusagen in ihr.

Genau das sollte sich dann aber auch in der Implementation ausdrücken. So wird die leichter verständlich, so kann besser sichergestellt werden, dass die Implementation tatsächlich der Architektur folgt. Die von der Architektur auf verschiedenen Abstraktionsebenen definierten Strukturelemente sollten deshalb nicht in einem großen „Codetopf“ (z.b. eine große Visual Studio Solution) „zusammengerührt werden“. Viel besser auch im Sinne hoher Produktivität und einfacher Testbarkeit ist es, die logischen Strukturen der Architektur so physisch wie möglich zu manifestieren.

  1. Die von der Architektur geplanten Strukturen auf verschiedenen Abstraktionsebenen sollten sich so weitgehend wie möglich in der Codeorganisation widerspiegeln. Das bedeutet zum einen, dass die Architektur als Strukturelemente vor allem physische Codeeinheiten benutzt. Und zum anderen sollen diese Strukturelemente dann aber auch im Quellcode bzw. in der Codeorganisation im Repository klar sichtbar sein.
  2. Bei der Arbeit an der Implementation der Strukturelemente und insbesondere innerhalb von Komponenten sollen Architekturänderungen „im Vorbeigehen“ unmöglich sein. Wer in bzw. an einem Strukturelement arbeitet, also an einem Teil, darf nicht ad hoc die umliegende Struktur, d.h. das Ganze, ändern können. Nur wenn das gewährleistet ist, wächst die Entropie einer Software nicht unkontrolliert. Das ist wichtig, da das Hauptziel von Architektur ist, die Entropie und damit die Komplexität von Software zu minimieren.

Planung muss sein. Implementation darf Planung nicht torpedieren. (Wenn auch Erkenntnisse während der Implementation natürlich auf die Planung zurückwirken dürfen.) Deshalb sind Planung und Implementation zu entkoppeln. Und wo das nicht möglich ist, da sollte die Planung mit Mitteln der Implementation arbeiten und die Implementation physisch die Planung widerspiegeln.

You Ain´t Gonna Need It (YAGNI)

Warum?
Dinge die niemand braucht, haben keinen Wert. Verschwende an sie also keine Zeit.
Wandelbarkeit   
Korrektheit   
Produktionseffizienz   
Kontinuierliche Verbesserung   
Single Developer

Das YAGNI-Prinzip (You Ain´t Gonna Need It) ist eines der einfachsten in der Softwareentwicklung – und doch wohl das nach dem DRY-Prinzip am häufigsten verletzte Prinzip. Deshalb steht YAGNI nicht nur am Anfang des roten Grads, sondern auch hier gegen Ende des Weges durch das Wertesystem.

Geschuldet ist das YAGNI-Prinzip dem in der Softwareentwicklung besonderen Verhältnis von Anforderungsgenauigkeit und Produktmaterialität. Anforderungen sind notorisch ungenau oder wechselnd und das Produkt, in dem sie umgesetzt werden sollen, immateriell. Im Vergleich zum Maschinen- oder Gebäudebau ist das Material also unendlich flexibel und kann sich prinzipiell mit vergleichsweise wenig Aufwand an quasi jede Anforderung anpassen lassen. Hohe Volatiliät bzw. Ungenauigkeit trifft also auf hohe Flexibilität. Das scheint zunächst einmal ideal.

Die Praxis zeigt jedoch, dass gerade in diesem Verhältnis der Keim des Misserfolges vieler Projekte liegt. Kurzfristig betrachtet, versuchen die Projekte mit dem Naheliegenden auch das Richtige zu tun:

  • Ungenaue Anforderungen werden oft kompensiert durch Produkte, die versuchen, die Ungenauigkeit zu kompensieren. Die Immaterialität von Software wird dazu genutzt, so breit und flexibel zu implementieren, dass auch noch unbekannte oder schwammige Anforderungen quasi schon im vorauseilenden Gehorsam erfüllt werden.
  • Ständig wechselnde Anforderungen werden im Produkt möglichst schnell nachgeführt, weil das dank seiner Immaterialität möglich ist.

Langfristig ist solches Verhalten allerdings kontraproduktiv:

  • Der vorauseilende Gehorsam führt zu Breite und Flexibilität, die nicht wirklich gebraucht werden. Er realisiert Features, die keine Anwendung finden.
  • Schnelle Umbauten an Software aufgrund wechselnder Anforderungen führen zu Qualitätserosionen im Code. Software ist zwar immateriell und flexibel – aber nicht jede Softwarestruktur ist evolvierbar oder auch nur verständlich.

Unklare und wechselnde Anforderungssituationen vor dem Hintergrund der hohen grundsätzlichen Flexibilität von Software führen schnell zu unnötigen Aufwänden und sprödem Code. Eine große Anzahl von Projekten, die ihre Budgetgrenzen gesprengt haben, und eine noch größere Zahl von Projekten, die schon nach wenigen Jahren unwartbar geworden sind, sind dafür beredtes Zeugnis.

CCD als professionelle Softwareentwickler sehen es als ihre Pflicht, sich solcher Entwicklung jeden Tag entgegen zu stemmen. Angesichts der nicht zu leugnenden Natur von Software – sie ist und bleibt immateriell -, liegt der Ansatz dafür beim Umgang mit den Anforderungen. Das ist der Ursprung des YAGNI-Prinzips.

Das YAGNI-Prinzip ist wie ein scharfes Messer: Wer sie anwendet, schneidet ein Problem in kleine Würfel des unmittelbar Nötigen. Nach dem YAGNI-Prinzip wird nur das unzweifelhaft und unmittelbar Nutzbringende implementiert. Alles andere… nun, das kommt später. Insofern geht YAGNI Hand in Hand mit der Regel „Entscheide so spät wie möglich“ des Lean Software Development.

Das YAGNI-Prinzip ist relevant auf allen Ebenen der Softwareentwicklung und in allen Phasen. Wann immer Sie sich Fragen „Sollte ich diesen Aufwand wirklich treiben?“ oder „Brauchen wir das wirklich?“ – und sei es auch nur ganz verschämt und leise im Hinterkopf -, dann ist das ein Anwendungsfall für das YAGNI-Prinzip. Es besagt: Wenn im Zweifel, entscheide dich gegen den Aufwand.

Das klingt leicht, ist aber schwer. Daher auch die häufigen Zuwiderhandlungen. Es gibt viele Kräfte, die der Entscheidung gegen einen Aufwand widersprechen. „Ach, das ist doch gar nicht soviel Aufwand“ oder „Wenn wir jetzt nicht vorausschauen, dann können wir in Zukunft nicht mehr anders“ sind nur zwei naheliegende Begründungen für Aufwand, auch wenn Zweifel an seinem Nutzen bestehen. Das betrifft architektonische Entscheidungen (z.B. Soll schon mit einer verteilten Architektur begonnen werden, auch wenn die heutige Last sie noch nicht bräuchte?) wie lokale Entscheidungen (z.B. Soll der Algorithmus schon jetzt optimiert werden, auch wenn er im Augenblick noch keine Performanceprobleme macht?).

Der Kunde bezahlt nur für unmittelbaren Nutzen. Was er heute nicht klar spezifizieren kann, nutzt ihm nicht. Es in der Implementation voraussehen zu wollen, investiert also Aufwand ohne Nutzen zu generieren. Wenn der Kunde später einmal genauer weiß, was er will, dann – und nicht früher! – ist es Zeit, seinem Willen nachzukommen. Wo immer aber ein Projekt versucht, diesen Willen vorwegzunehmen riskiert es, von der morgigen Willensrealität des Kunden widerlegt zu werden. Ein Feature – funktional oder nicht-funktional -, das heute ohne klare Anforderung implementiert wird, interessiert den Kunden morgen vielleicht schon nicht mehr. Oder es ist ihm nicht mehr so wichtig wie ein anderes Feature.

Das bedeutet für die Softwareentwicklung:

  • Ausschließlich klare Anforderungen implementieren.
  • Der Kunde priorisiert seine klaren Anforderungen.
  • Die klaren Anforderungen in der Reihenfolge ihrer Priorisierung umsetzen.
  • Entwicklungsprozess und Codestruktur im Großen und Kleinen so aufsetzen, dass keine Angst aufkommt, sich ändernde und neue Anforderungen zu realisieren.

CCD als professionelle Entwickler kommunizieren diese Vorgehensweise unmissverständlich dem Kunden gegenüber. Dadurch werden sie:

  • servicewillig, denn sie müssen dem Kunden keine klare Anforderung abschlagen
  • verantwortungsbewusst, weil sie das Budget nur für klar formulierten Nutzen einsetzen
  • beschützend dem Code gegenüber, weil sie ihn gegen Überladung mit letztlich Unnötigem bewahren

YAGNI ist deshalb nicht nur ein Prinzip, das jeder Entwickler befolgen soll, sondern auch ein Prinzip für Projekte und Teams, also auf Organisationsebene. YAGNI ist immer in Anschlag zu bringen, genauso wie DRY. Wenn im Zweifel, dann verschiebe die Entscheidung falls möglich. Ansonsten entscheide dich gegen den Aufwand. Das entspannt und entschlackt und führt schneller zum Erfolg.

Praktiken

Design before Implementation

Warum?
Vor der Umsetzung muss eine Lösung entworfen werden. Andernfalls findet kein konsequentes Nachdenken über die Lösung statt.
Wandelbarkeit   
Korrektheit   
Produktionseffizienz   
Kontinuierliche Verbesserung   
Team

Die Aufgabe eines Entwicklers besteht darin, Anforderungen in Code zu übersetzen. Dazu ist es erforderlich, eine Lösung für die Anforderungen zu entwickeln. Es muss nachgedacht werden. Wie kann das aber auf gute Weise geschehen, wenn Entwickler direkt ins Codieren springen?

In trivialen Fällen mag es möglich sein, direkt Code zu schreiben. Dennoch wird auch beim unmittelbaren Sprung ins Codieren über die Lösung nachgedacht. Allerdings geschieht dies eher unbewusst, vor allem aber während der Umsetzung. Der Entwickler denkt ein wenig nach, codiert, denk nach, codiert, usw. Es fehlt hier ein konsequentes Durchdenken der Lösung, getrennt von der Umsetzung.

Spätestens, wenn eine Gruppe von Entwicklern gemeinsam als Team arbeiten möchte, muss der Entwurf zeitlich getrennt von der Umsetzung stattfinden. Andernfalls ist keine flüssige arbeitsteilige Vorgehensweise möglich.

Der Entwurf ermöglicht es dem Team bzw. einem einzelnen Entwickler, bereits vor der Codierung über wichtige Prinzipien nachzudenken. Es entstehen bspw. erst gar keine Methoden oder Klassen mit mehreren Verantwortlichkeiten, da schon auf der Ebene des Entwurfs über das Single Responsibility Principle (SRP) nachgedacht werden kann. Damit erspart sich das Team den Refaktorisierungsaufwand der entsteht, wenn „drauf los“ codiert wird.

Siehe auch https://flow-design.info.

Continuous Delivery (CD)

Warum?
Als Clean Code Developer möchte ich sicher sein, dass ein Setup das Produkt korrekt installiert. Wenn ich das erst beim Kunden herausfinde, ist es zu spät.
Wandelbarkeit   
Korrektheit   
Produktionseffizienz   
Kontinuierliche Verbesserung   
Team

Im grünen Grad haben wir den Continuous Integration Prozess für Build und Test aufgesetzt. Damit sorgt der Continuous Integration Prozess dafür, dass Fehler während der Build- und Testphase schnell entdeckt werden. Wenn z.B. eine Änderung am Code dazu führt, dass eine andere Komponente nicht mehr übersetzt werden kann, weist der Continuous Integration Prozess kurze Zeit nach dem Commit der Änderung auf den Fehler hin. Wenn am Ende jedoch ein Setup Programm produziert wird, welches sich aufgrund von Fehlern nicht installieren lässt, haben wir unser Ziel trotzdem nicht erreicht: funktionierende Software die bei unseren Kunden installiert werden kann.

Folglich müssen wir auch die Phasen Setup und Deployment automatisieren, um sie per Knopfdruck ausführen zu können. Nur so können wir sicher sein, dass wir installierbare Software produzieren. Und durch die Automatisierung ist sichergestellt, dass niemand einen wichtigen Schritt, der „zu Fuß“ ausgeführt werden muss, vergisst. So kann jeder im Team zu jedem Zeitpunkt den aktuellen Stand des Produktes installationsfertig produzieren und installieren.

Siehe auch unter Tools.

Iterative Development

Warum?
Frei nach von Clausewitz: Kein Entwurf, keine Implementation überlebt den Kontakt mit dem Kunden. Softwareentwicklung tut daher gut daran, ihren Kurs korrigieren zu können.
Wandelbarkeit   
Korrektheit   
Produktionseffizienz   
Kontinuierliche Verbesserung   
Team

Natürlich schreitet Softwareentwicklung immer von einer Planung über die Implementation zu einem Test durch den Kunden voran. Irrig ist allerdings die Annahme, ein Projekt käme mit einer Planungsphase und einer Implementationsphase und einer Kundentestphase aus. Das funktioniert – wenn überhaupt – nur in trivialen Szenarien, wo in der Planungsphase alle Anforderungen bekannt sind. In realen Projekten jedoch liefert jede Phase Erkenntnisse für vorhergehende Phasen. Allemal durch den Kundentest ergeben sich Konsequenzen für die Planung und Implementation.

Solche Erkenntnisse können allerdings nur Einfluss auf ein Projekt nehmen, wenn das Vorgehen nicht linear ist. Wenn es von einer späteren Phase keinen Weg zurück zu einer früheren Phase gibt, ist Feedback nutzlos.

Um Feedback in ein Softwareprodukt einfließen lassen zu können, muss der Entwicklungsprozess Schleifen enthalten. Allemal die Schleife von der Kundentestphase zurück zur Planung ist nötig. Das heißt, Softwareentwicklung kann nur iterativ, also in mehreren Durchläufen, über den Anforderungskatalog des Kunden stattfinden. Wer versucht, „mit einem Mal“ (big bang) auszuliefern, handelt dieser Erkenntnis zuwider. Der Softwareentwicklungsprozes ist vielmehr so zu planen, dass er sich durch die Anforderungen „in kleinen Happen durchbeißt“. Jeder dieser Happen sollte nicht größer sein, als dass der Durchlauf von Planung bis Kundentest mehr als 2-4 Wochen dauert. Nur dann kommt das Feedback vom Kunden häufig genug, um nicht allzu lange in der Umsetzung in die Irre zu laufen.

Softwareentwicklung ist damit ein Lernprozess. In seinem Verlauf lernt das Projektteam etwas über die Anforderungen des Kunden. Es hört ihm zu, plant, implementiert, und händigt eine Softwareversion aus, die das Verständnis des Gehörten widerspiegelt. Dann hört das Team wieder zu, plant weiter/erneut nach den aktuellen Erkenntnissen usw. usf. immer im Kreis. Iteration für Iteration. Manchmal wird etwas aus einer früheren Iteration verfeinert, manchmal Neues hinzugefügt.

Doch nicht nur die Entwicklung einer Software ist ein Lernprozess. Lernen sollte auch auf organisatorischer Ebene stattfinden. Das Team sollte nicht nur über den Kunden etwas lernen, sondern auch über sich selbst. Deshalb sollte es auch immer wieder „Haltepunkte“ geben, an denen das Team über sein Vorgehen reflektiert. Die Erkenntnisse aus solcher Retrospektive fließen dann ein in die nächste Iteration der organisatorischen Entwicklung. Hier schließt der blaue Grad an den roten Grad an, zu dem die tägliche persönliche Reflexion gehört.

Natürlich muss jede Iteration auch ein Ende haben. Und damit man weiß ob man fertig ist, muss vorher klar definiert sein, was in der Iteration erreicht werden soll. Die Erreichbarkeit von Zielen kann immer nur geschätzt werden, auch dabei hilft die Reflexion, um die Schätzungen schrittweise soweit zu verbessern, dass sie für die Planung ausreichend genau sind. Doch wann ist das vorher definierte Ziel erreicht? ‚What is done?‘ Oberstes Ziel ist die Lieferung funktionsfähiger Software an unsere Kunden. Folglich kann das Ziel nur erreicht sein wenn wir auslieferungsfertige Software produziert haben. Das bedeutet insbesondere, dass die Software getestet ist und dass sie per Setup installiert werden kann. Durch Continuous Integration stellen wir dies kontinuierlich sicher. Keinesfalls dürfen wir kurz vor Ende einer Iteration entscheiden, dass ein Ziel erreicht ist, obwohl noch nicht alle Tests abgeschlossen sind.

Siehe auch unter Tools.

Incremental Development

Warum?
Nur die Arbeit in Inkrementen ermöglicht es dem Product Owner, Feedback zu geben.
Wandelbarkeit   
Korrektheit   
Produktionseffizienz   
Kontinuierliche Verbesserung   
Team

Ein Inkrement stellt einen vertikalen Schnitt durch die verschiedenen Aspekte eines Softwaresystems dar. Somit ist ein Inkrement ein Stück ausführbare Software. Das Inkrement kann einem Product Owner auf einer Testmaschine zur Verfügung gestellt werden, um Feedback einzuholen.

Regelmäßiges Feedback in kurzen Abständen, jeweils am Ende einer Iteration, ist die Definition von Agilität.

Wird dagegen horizontal statt vertikal vorgegangen, entstehen Module, die nicht eigenständig ausführbar sind. Zu solchen Modulen kann ein Product Owner kein Feedback geben. Somit ist kein echtes agiles Vorgehen möglich.

Component Orientation

Warum?
Software braucht Black-Box-Bausteine, die sich parallel entwickeln und testen lassen. Das fördert Wandelbarkeit, Produktivität und Korrektheit.
Wandelbarkeit   
Korrektheit   
Produktionseffizienz   
Kontinuierliche Verbesserung   
Team

Die Prinzipien des CCD-Wertesystems haben sich bisher vor allem auf kleinere Codeausschnitte bezogen. Was sollte in einer Methode stehen, was sollte über mehrere verteilt werden? Welche Methoden sollte eine Klasse veröffentlichen? Woher sollte ein Client-Objekt zu einem Service-Objekt kommen? Bisher ging es um Prinzipien für die Softwareentwicklung im Kleinen.

Hat das CCD-Wertesystem denn aber nichts zu größeren Strukturen, zur Softwareentwicklung im Großen zu sagen? Wie steht es mit der Softwarearchitektur? Genau hier setzt das Prinzip der Komponentenorientierung an. Bisher haben wir zwar auch schon das Wort „Komponente“ gebraucht, doch eher lax und in einem umgangssprachlichen Sinn. Von nun an jedoch soll Komponente etwas sehr spezifisches beschreiben, das wir für grundlegend für evolvierbare Software halten.

Solange wir Software letztlich nur aus Klassen mit Methoden aufgebaut denken, versuchen wir sozusagen Computer auf Transistorebene zu beschreiben. Das funktioniert letztlich aber nicht, weil wir im Detailreichtum ersticken. Selbst die Klassen in Schichten zusammenzufassen hilft da nicht viel. Wir brauchen vielmehr sowohl ein Beschreibungsmittel für größere Softwarestrukturen. Aber nicht nur das: das Beschreibungsmittel sollte auch ein Implementationsmittel sein – so wie Klassen -, damit das Modell, der Plan, die Beschreibung sich im Code widerspiegelt.

Betriebssystemprozesse sind zwar solche architektonischen Mittel, letztlich sind auch sie jedoch zu groß. Solange die EXE eines Prozesses einer Applikation aus mehreren Hundert oder Tausend Klassen besteht, gewinnen wir nichts.

Hilfe bringt allerdings das Prinzip der Komponentenorientierung. Es besagt, dass ein Anwendungsprozess zunächst einmal aus Komponenten besteht und nicht aus Klassen. Erst die Bausteine der Komponenten sind dann Klassen. Und was ist eine Komponente? Es gibt einige Definitionen für Komponenten, von denen im Kern zwei Kriterien unverbrüchlich erscheinen:

  • Komponenten sind binäre Funktionseinheiten. (Eine Klasse hingegen ist eine Funktionseinheit auf Quellcodeebene.)
  • Die Leistung von Komponenten wird durch einen separaten (!) Kontrakt beschrieben. (Die Leistungsbeschreibung einer Klasse liegt hingegen in ihr. Es ist die Summe ihrer Methodensignaturen.)

Ein CCD sucht beim Entwurf einer Software nach der Definition der Prozesse also zunächst nach den Komponenten, aus denen die Prozesse bestehen sollten. Er fragt sich, welche „Dienstleistungsblöcke“ machen die Anwendung aus? Und diese Blöcke sieht der CCD als Black Boxes in Bezug auf ihren Aufbau aus Klassen an. Diese Blöcke sind Assemblies mit wohldefinierter Dienstleistung, aber unbekannter Struktur.

Eine Client-Komponente C weiß daher nichts über die Klassenstruktur ihrer Service-Komponente S. C kennt nur den Kontrakt von S, der unabhängig von der Implementation von S ist. Kontrakte sind insofern für Komponenten das, was Interfaces für Klassen sind. Nicht zufällig bestehen Kontrakte zu einem guten Teil oder gar vollständig aus Interfaces.

Komponenten sind also Elemente der Planung wie auch der Implementation. Um das zu unterstreichen, werden Komponenten physisch unabhängig voneinander implementiert; ein probates Mittel dafür sind Komponentenwerkbänke, d.h. separate Visual Studio Solutions je Komponentenimplementation. Das fördert nicht nur die Konzentration auf eine Aufgabe, weil man während der Arbeit an einer Komponente in der IDE nur deren Code sieht. Darüber hinaus fördert es auch konsequente Unit Tests unter Einsatz von Attrappen, da Quellcode anderer Komponenten nicht sichtbar ist. Außerdem steigert solche Codeorganisation die Produktivität, weil Komponenten dank ihrer separaten Kontrakte parallel implementiert werden können. Und schließlich stellt sich eine physische Isolation gegen den schleichenden Zuwachs an Entropie im Code. Denn wo Bindungen zwischen Komponenten nur via Kontrakt aufgebaut werden können, ist die Kopplung lose und kontrolliert.

Zur Komponentenorientierung gehören deshalb nicht nur binäre, größere Codeeinheiten mit separaten Kontrakten, sondern auch die Entwicklung der Kontrakte vor der Implementation (Contract-first Design). Denn sobald die Kontrakte definiert sind, die eine Komponente importiert und exportiert, kann die Arbeit an der Komponente unabhängig von allen anderen beginnen.

Siehe auch unter Tools.

Test First

Warum?
Der Kunde ist König und bestimmt die Form einer Dienstleistung. Service-Implementationen sind also nur passgenau, wenn sie durch einen Client getrieben werden.
Wandelbarkeit   
Korrektheit   
Produktionseffizienz   
Kontinuierliche Verbesserung   
Single Developer

Wenn Komponentenorientierung fordert, die Kontrakte für Komponenten unabhängig von ihrer Implementation zu definieren, stellt sich die Frage, wie das denn geschehen soll. Durch Diskussion am runden Tisch? Das ist sicherlich ein Weg. Ein besserer ist jedoch, Kontrakte nicht erst lange an einer Tafel zu entwerfen, sondern sie sofort in Code zu gießen. Komponentenkontrakte – oder allgemeiner: jede Codeschnittstelle – dient letztlich anderem Code als API. Es ist daher konsequent und effektiv, von diesem Code ausgehend Schnittstellen zu spezifizieren.

Das ist das Anliegen von Test first. Test first basiert auf dem Gedanken, dass Funktionseinheiten (Methoden, Klassen, usw.) durch Client-Service-Verhältnisse charakterisiert sind. Diese Verhältnisse drehen sich um die Schnittstelle zwischen Client und Service. Und diese Schnittstelle sollte durch den Client bestimmt werden. Der Client ist als Kunde des Service König. Ihm soll der Service dienen, nach ihm soll sich deshalb die Schnittstelle des Service richten.

Die Definition der Schnittstellen der Codeeinheiten einer Software erfolgt aus diesem Grund von außen nach innen. Außen, an der Benutzeroberfläche, sitzt der ultimative Client, der Anwender. Er definiert die visuelle/haptische Schnittstelle der UI-Codeeinheiten. Die wiederum sind die Clients von darunterliegenden Codeschichten. Die sind dann Clients von tieferliegenden Schichten usw. Die Leistungen und Schnittstellen der tiefsten Codeschichten kann somit nur bestimmt werden, wenn die der darüberliegenden schon bestimmt sind usw.

Das widerspricht dem häufigen Ansatz der bottom-up Definition von Codeeinheiten. Gern fangen Projekte an, eine Datenzugriffsschicht zu definieren und zu implementieren. Das ist verständlich, weil solch fundamentale Funktionalität doch scheinbar die Voraussetzung für alles weitere ist. Aber dieses Vorgehen ist problematisch, wie viele gescheiterte Projekte zeigen:

  • Wer von unten nach oben, von innen nach außen spezifiziert und implementiert, bietet dem Kunden erst sehr spät einen Wert an. Das ist zumindest frustrierend, wenn nicht gar kontraproduktiv.
  • Wer bottom-up in der Spezifikation vorgeht, der spezifiziert ohne genaue Anforderungen des ultimativen Clients, des Benutzers. Was er also spezifiziert läuft Gefahr, am Ende zu allgemein und damit unhandlich zu sein – oder schlicht nicht gebraucht zu werden (eine Verletzung des YAGNI-Prinzips, s.o. und im roten Grad).
  • Wer von unten nach oben implementiert, läuft Gefahr, nicht wirklich zu entkoppeln. Denn wenn tiefere Schichten nötig sind, um darüberliegende zu implementieren, dann werden wahrscheinlich keine wirklich isolierten Unit Tests mit Attrappen eingesetzt und auch keine Inversion of Control.

Clean Code Developer vermeiden diese Probleme jedoch. Sie spezifizieren Schnittstelle nicht nur vor den Implementationen (Contract-first, s.o. Komponentenorientierung), sondern auch von außen nach innen und ganz praktisch durch Codierung. Mit den Mitteln des automatisierten Testens ist es nämlich sehr einfach, Schnittstellen in kleinen Schritten in Form von Tests zu definieren.

Test first fügt dadurch syntaktischen Kontrakten (z.B. Interfaces) eine semantische Seite hinzu. In Ermangelung anderer, formaler Methoden, um Semantik zu spezifizieren, sind Tests der einzige Weg, um Anforderungen zu formalisieren. Wer einem Entwickler eine Komponente zur Implementierung zuweisen will, der tut daher gut daran, nicht nur ihre „Oberfläche“ (API) syntaktisch vorzugeben, sondern auch das gewünschte Verhalten in Form von Tests.

Das hat viele Vorteile:

  • Die Form einer Schnittstelle ist unmittelbar Client-getrieben und damit maximal relevant. YAGNI hat keine Chance.
  • Die Tests sind nicht nur Tests, sondern auch Spezifikationsdokumentation. Nutzer einer Schnittstelle und Implementierer können sie gleichermaßen studieren. Eine separate Dokumentation erübrigt sich weitgehend. Das tut dem DRY-Prinzip genüge.
  • Die Spezifikationen sind nicht nur passive Texte, sondern ausführbarer Code. Wenn dann eine Implementation vorliegt, kann sie gegen diese Tests geprüft werden. Spezifikation und Test sind damit nicht zeitraubend aufeinanderfolgende Phasen. Das erhöht die Produktivität. Qualitätssicherung ist so der Implementation schon vorgeschaltet.

Siehe auch unter Tools.

Weiter geht’s mit dem weißen Grad