Neulich habe ich es getan. Wieder, muss ich sogar zugeben. Ich habe codiert, ohne Tests zu schreiben. Schon gar nicht habe ich test-first codiert. Wie kann das nur sein? Immerhin habe ich ein 500-Seiten Buch darüber geschrieben, wie wichtig es ist, test-first vorzugehen bei der Softwareentwicklung.
Meine Erklärung für mein unprofessionelles (?) Vorgehen ist…
Test-first Vorgehen ist ein Werkzeug, das nur zu einem bestimmten Problem passt. Darauf sollte es angewandt werden, ansonsten aber nicht.
Und was ist das Problem, für das test-first Codierung das passende Vorgehen ist?
Test-first Codierung dient der Herstellung von Produktionscode.
Produktionscode ist der Code, der zum Kunden ausgeliefert wird. Produktionscode ist der Code, der verlässlich für Anwender laufen muss. Anwender sind gewöhnlich keine Softwareentwickler oder zumindest nicht Entwickler, die den Produktionscode geschrieben haben. Anwender haben insofern keine Toleranz gegenüber Inkorrektheiten oder Crashes von Produktionscode.
Deshalb muss Produktionscode hohe Qualität haben. Bugs sind hier ein no-go.
Wie kann Bug-armer Produktionscode erreicht werden? Durch Tests, automatisierte Tests. Manuelle skalieren einfach nicht und sind nicht nachvollziehbar.
Die wichtigsten Tests sind aus meiner Sicht deshalb auch Regressionstests, d.h. Tests, die feststellen, ob Produktionscode noch korrekt ist, nach Veränderungen.
Das oberste Gebot bei der Anpassung von Software ist einfach: Mach nichts kaputt!
Softwareentwickler sind insofern wie Ärzte, für die gilt: primum non nocere (“erstens nicht schaden”). Was einmal läuft und ausgeliefert wurde, darf nicht hinter diesen Stand zurückfallen (Regression). Das beste Bemühen um eine Verbesserung von Software hier, darf sie nicht dort beschädigen.
Erst die zweitwichtigsten Tests sind dann die Reifetests, d.h. Tests, die feststellen, ob Produktionscode schon korrekt ist. Sie definieren ein Soll für eine Veränderung im Produktionscode. Bei TDD geht es um Reifetests.
Wo mit Produktionscode begonnen wird, stehen selbstverständlich Reifetests am Anfang. Es gibt noch keinen Code, der stabil gehalten werden muss. Doch sobald initiales Verhalten gem. Reifetests korrekt hergestellt wurde und weiteres dazu kommen soll, braucht der Produktionscode schon Stabilität. Aus den ersten Reifetests werden ganz natürlich Regressionstests, zu denen neue Reifetests treten für die nächsten Erweiterungen. Und so weiter und so fort.
So sieht für mich der Umgang mit Produktionscode aus
Doch nicht jeder Code, den ich schreibe, ist Produktionscode. Das kann er nicht sein, weil ihm etwas fehlt. Produktionscode braucht etwas, ohne das auch keine Tests geschrieben werden können. Das ist: Klarheit.
Erst wenn nicht nur Anforderungen klar sind, sondern auch ein Lösungsansatz klar ist, kann Produktionscode in Angriff genommen werden.
Ohne diese Klarheit können erstens keine Tests formuliert werden. Zweitens führt das Suchen nach einem Lösungsansatz und darauf aufbauend einem Modell mittels Produktionscode zu einer Belastung des Produktionscodes, die ihn brüchig macht. Das Vor und Zurück am Code während der Suche nach einer Lösung führt zu Unordnung.
Diese Unordnung ist unvermeidbar, weil funktionierender Code und sauberer Code zwei ganz unterschiedliche Mindsets brauchen. Auf der Suche nach einer Lösung, d.h. wenn ich funktionierenden Code schreibe, bin ich in einem kreativen Modus. Mein Ziel ist, “dass die Sache läuft.” Dass ich zeigen kann, dass ein Problem überhaupt lösbar ist. Dafür muss ich schnell alle möglichen Optionen ausprobieren können. Sauberkeit ist währenddessen eine Belastung. Solange ich mir bei der Lösungsfindung durch Unsauberkeit nicht auf den Füßen stehe, ist Sauberkeit nur hinderlich für meinen Gedankenfluss. In dieser Phase achte ich auf Details.
Ganz anders, wenn ich sauberen Code “für die Nachwelt” (inklusive mir) herstelle. Im Grunde ist das eine reine Refaktorisierungsaufgabe. Die Lösung ist in dem Fall schon da; ich muss sie nur noch in Form bringen und durch Tests abstützen. Die Verhaltensqualitäten stehen in dieser Phase nicht mehr in Frage. Ich weiß, wie ich mit Code, die funktionalen wie nicht-funktionalen Anforderungen erfülle. Die Logik liegt vor mir — nur fehlt ihr eine saubere Struktur. Die zu finden, braucht ein anderes Bewusstsein. Jetzt muss ich aufs Ganze achten: Wie passt der Code in die ganze Codebasis? Wie kann der Code über ganz lange Zeit verständlich sein?
Konformitätscode, also Code, der sich einfach nur anforderungsgemäß verhält, und Produktionscode sind zwei sehr unterschiedliche Produkte der Softwareentwicklung. Konformitätscode ist nicht unbedingt Produktionscode; aber Produktionscode ist immer konformer Code, sogar “bewiesenermaßen” durch die Testabdeckung.
Ich behaupte nun, dass für nicht-triviale Probleme die Codierung durch zwei Phasen läuft, ja, laufen muss:
Konformitätsphase: Es wird eine Lösung gefunden; kein Gedanke an Sauberkeit/Nachhaltigkeit des Codes; der muss einfach nur laufen.
Produktionsphase: Eine vorhandene Lösung wird so “aufpoliert”, dass sie auch nachhaltig ist, d.h. morgige Veränderungen willkommen heißt. Produktionscode ist nachweislich reif und stabil (keine Regressionen) und flexibel und auch noch easy to reason about.
Beide Phasen brauchen ein anderes Mindset. Womöglich sind für beide Phasen sogar unterschiedliche Entwickler geeignet. In der Konformitätsphase gehts um Kreativität, gar um Kühnheit. In der Produktionsphase gehts um Sorgfalt und Kommunikation.
Automatisierte Tests sind — das ist nun leicht zu erkennen — eine Sache der Produktionsphase. Davon bin ich weiterhin überzeugt. Dass ich mein Buch geschrieben habe, macht weiterhin Sinn.
Nur ist die Produktionsphase eben nicht die einzige. Ich glaube sogar, dass sie weithin überbewertet ist. Sie soll alle gebührende Aufmerksamkeit bekommen — doch darüber sollte nicht vernachlässigt werden, dass es eben auch eine Konformitätsphase gibt.
Die Konformitätsphase halte ich umgekehrt für weithin unterbewertet. Sie wird nicht klar von der Produktionsphase abgegrenzt. Es gibt zwar Begriffe wie Prototyp oder Spike, die zur Konformitätsphase gehören, doch sind sie in Ungnade gefallen, scheint mir. Ein Grund: Die überstarke Suggestion von TDD seit 20 Jahren war, dass beide Phasen im red-green-refactor Vorgehen von TDD quasi-gleichzeitig durchlaufen werden können.
Das halte ich für eine Mär. Das berücksichtigt einfach nicht genügend die Verschiedenheit der Bewusstseinszustände, die für Konformitätscode vs Produktionscode nötig sind.
Wer sich also immer wieder dabei ertappt, wie er trotz allem gutem Willen nicht test-first vorgeht bei der Codierung, wer immer wieder gegen TDD verstößt… der muss nicht an sich selbst zweifeln. Das ist keine Sünde und braucht keine Buße. Es fehlt nicht einfach an Einsicht und Disziplin. Vielmehr halte ich es zunächst einmal für einen ganz natürlichen Ausdruck des Mindset, das bei Ansicht eines Problems gefragt ist. In dem Moment darf es keine Einengung geben; die Konformitätsphase braucht maximale Freiheit für die Erkundung des Lösungsraumes. Tests, Modularisierung, Prinzipien… das ist in dem Moment zweitrangig.
In dem Moment, nicht länger. Das finde ich wichtig! Alle Freiheit für die Lösungsfindung! Doch später, wenn die Lösung gefunden ist… dann darf die nächste Phase nicht übersprungen werden. Als Softwareentwickler darf ich mich auf Konformität nicht ausruhen. Ich muss anschließend auch Nachhaltigkeit herstellen. Mein Bewusstsein muss ich umstellen von Kreativität auf Sorgfalt.
Ich verstehe, dass das nicht leicht ist. Das macht es jedoch nicht unnötig. Ohne sauberen Produktionscode kommt die Konformitätsherstellung sonst alsbald ins Stocken.
Grundlegende Schritte der Softwareentwicklung sind also:
Analyse
Entwurf
Codierung
Konformität
Produktion
Deployment
Im Entwurf wird zwar auch schon an der Lösung gearbeitet. Die Konformitätsherstellung kann durch ihn vereinfacht werden. Letztlich kann er jedoch nicht alles vorweg nehmen. Es braucht auch noch codenahe Exploration. Die kann sogar vorher stattfinden:
Analyse
Codierung I: Konformität1
Entwurf
Codierung II: Produktion
Deployment
Bei dieser Unterscheidung gehts mir vor allem darum, den Blick für unterschiedliche Perspektiven zu schärfen. Sie sind alle nützlich. Sie haben alle ihre eigenen Werkzeuge. Wie lange diese Phasen jeweils dauern, ist nicht der Punkt. Selbst wenn hier eine Reihenfolge steht, die nach Wasserfall riecht, bedeutet das nicht, dass die Phasen nur einmal durchlaufen werden. Nein, natürlich kann das itterativ mehrfach geschehen. Verschiedene Phasen stehen Agilität nicht im Wege.
Warum habe ich also keine Tests geschrieben? Weil ich in der Konformitätsphase war. Es ging mir während der Codierung darum, die anvisierte Funktionalität überhaupt erstmal irgendwie herzustellen. Jede Überlegung zu Sauberkeit jenseits dessen, was ich unmittelbar gebraucht habe, um mir nicht in den Fuß zu schießen, war da zu viel. Tests wären Verschwendung gewesen, weil die Anforderungen bzw. die Signaturen von Funktionen unklar waren. Die Codierung in der Phase gehörte insofern im Grunde zur Anforderungsanalyse dazu. Durch sie habe ich besser verstanden, was eigentlich gewollt war.
Ich habe mich also aus gutem Grund nicht an meine eigene Empfehlung der test-first Codierung gehalten. Das will ich bei zukünftigen Clean Code Trainings mehr berücksichtigen. Wenn ich Teilnehmern Aufgaben stelle, muss ich berücksichtigen, dass auch sie intuitiv erstmal nur Konformität herstellen wollen. Sollte ich ihnen dabei mit einem test-first Vorgehen in die Quere kommen, sind Widerstände vorhersehbar.
Also: Entwarnung. Alles ok trotz fehlender Tests. Ich habe schlicht nicht an Produktionscode gearbeitet, sondern “nur” und erstmal an Konformitätscode. Jetzt gilt es allerdings, die nächste Phase nicht zu überspringen. Konformitätscode ist nicht einfach so Produktionscode, selbst wenn er läuft. Zu Produktionscode gehört mehr, z.B. Tests. Meine nächste Aufgabe: den Konformitätscode in Produktionscode überführen. Unprofessionell war ich bisher nicht; ich würde es nur, wenn ich diesen Schritt nicht machen würde.
In beiden Listen folgen Entwurf und Konformität auf einander. Für mich ist das plausibel, weil beide “mit leichtem Gepäck” unterwegs sind. Der Entwurf ist leicht, weil er gar nicht am Code stattfindet. Aber selbst Konformitätscode ist noch leicht, weil er ohne den “Clean-Code-Ballast” auskommt. Beide Phasen zusammengenommen bilden sozusagen die Vorhut vor der Produktion. Beide zusammen drehen sich um zügige Erkundung verschiedener Optionen.