Was ist es im Kern, das TDD ausmacht? Darüber habe ich anlässlich einer längeren Konversation mit Ron Jeffries in der Software Craftsmanship Google Group jetzt noch einmal nachgegrübelt.
TDD hatte bescheiden angefangen als Test-Driven Development. Da ging es darum, Code in einer bestimmten Weise zu schreiben, um ihn von vornherein korrekt zu hinzukriegen.
Doch dann wurde TDD befördert von einer Codiertechnik zu einer Entwurfstechnik für Code: aus dem "D" wie Development wurde eine "D" wie Design: Test-Driven Design [1]. Es ging nicht mehr nur um möglichst korrekten Code, sondern auch um möglichst gute Struktur.
Korrektheit und Strukturqualität sind unabhängig von einander. Sie können korrekten, aber schlecht strukturierten Code schreiben - oder sie können wunderbar strukturierten Code schreiben, der nicht korrekt ist.
Damit ein Werkzeug nützt, bedarf es nun jedoch nicht nur seiner Eignung, sondern auch einer Vorstellung vom Ziel, das Sie mit ihm erreichen wollen, würde ich sagen. Ein Pinsel ist zweifelsohne zweckdienlich, wenn man ein Bild malen will, doch im Pinsel steckt kein ästhetisches Gefühl und keine Intention.
Wie steht es nun in dieser Hinsicht mit TDD?
Die Eignung zur Herstellung von Korrektheit ist zweifelsohne vorhanden. TDD sorgt durch seine Regel red-green auf der Basis von KISS und kleinschrittigem Vorgehen nicht nur für korrekten Code, sondern auch noch für eine hohe Testabdeckung.
Auch die Vorstellung vom Ziel des Einsatzes ist klar: korrekter Code. Sie steckt in den Testfällen, die genau angeben, wann Code korrekt ist, nämlich dann, wenn er für gegebenen Input den erwarteten Output liefert. Die Qualität der Arbeit mit TDD hängt also von der Qualität der Testfälle ab.
Diesen Gedanken lege ich mal auf den mentalen Stack. Push().
Die Eignung von TDD zur Herstellung von gutem Design finde ich hingegen weniger offensichtlich. Sie basiert allein auf dem refactor Schritt am Ende der kleinen Iterationen. Schon das finde ich ein bisschen dünn, weil es dafür keine Kontrolle gibt. Wachsende Korrektheit lässt ist sichtbar machen: die Zahl der grünen Tests steigt und die prozentuale Testabdeckung bleibt auf hohem Niveau. Woran aber ist die (wachsende) Qualität des Designs ablesbar? TDD führt auch zu korrekten Ergebnissen mit hoher Testabdeckung ganz ohne Refactoring für besseres Design.
Es gibt also für den Designaspekt bei TDD kein oder zumindest kein so naheliegendes Messinstrument wie für die Korrektheit.
Und wie steht es mit der Zielvorstellung? Wohin, inwiefern soll denn Code, wenn der Test grün geworden ist, refaktorisiert werden? Woher kommt die Vorstellung davon?
Hier wird für mich das Eis sehr dünn, auf dem TDD sich bewegt. In TDD steckt keine Vorstellung davon, wie gutes Design aussieht und es bietet auch kein Messinstrument für gutes Design. Die Behauptung, TDD führe zu gutem Design liegt allein im schlichten Vorhandensein des Refaktorisierungsschritts. Der ist aber nicht mehr als eine Erinnerung daran, sich bei jeder Miniiteration einmal Gedanken darüber zu machen. Und das wird dann in der Realität dann auch so gehalten: man kann sich darüber Gedanken machen, muss es aber nicht. Oder wenn, dann weiß keiner so genau, wann man sich genug Gedanken gemacht hat. Es fehlt ja jeder Maßstab und die handfesten Tests bleiben eh grün.
Das bedeutet unterm Strich, dass die Qualität des Design nichts so sehr von TDD abhängig ist - Refaktorisieren kann man auch, wenn man nicht nach TDD vorgeht -, sondern von der Designkompetenz des TDD-Betreibers.
Korrektheit ist abhängig von der Codierungskompetenz, Strukturgüte von der Designkompetenz. Ich finde, das hört sich sehr naheliegend an.
Die Leistung von TDD für das Design besteht damit nur noch in der Mahnung, sich im Refaktorisierungsschritt darüber mal Gedanken zu machen. Das war's. Nicht mehr und nicht weniger tut TDD für gutes Design. TDD ist vollständig davon abhängig, dass sein Nutzer sich erstens für die Refaktorisierung Zeit nimmt und zweitens auch noch selbst eine Vorstellung davon hat, wohin er refaktorisieren will.
Nochmal, weil es so wichtg ist: TDD selbst legt überhaupt kein (!) Design nahe. Weder ein gutes, noch ein schlechtes.
Gutes Design entsteht durch eine Vorstellung davon im Spannungsfeld von funktionalen und nicht funktionalen Anforderungen aufgehängt in einem Netz von Prinzipien.
Einzig ist TDD zugute zu halten, dass es eben nahelegt, sich gutem Design schrittweise anzunähern. Eine Zielvorstellung bleibt es jedoch schuldig, ebenso eine Unterstützung bei der Messung, ob wie nahe man ihr gekommen ist.
TDD ersetzt also nicht den Aufbau von Designkompetenz. Ist die nicht vorhanden, hilft TDD nur marginal, wenn überhaupt, beim Design und deckt den Mangel nicht einmal auf.
Und wovon hängt die Qualiät eines Design ab? Klar, von der Designkompetenz. Genauso wichtig ist allerdings auch ein gutes Verständnis der Anforderungen sowie des Problems. Das sollte auf der Hand liegen. Wer nicht versteht, wofür er eine Lösung entwickeln soll, wird seine Lösung kaum angemessen strukturieren.
Pop(). Hier kommt der Gedanke vom Stack ins Spiel. Denn wovon hängt die Qualität des Testfälle ab? Ebenfalls vom Anforderungs- und Problemverständnis. Wer die Domäne hinter den Anforderungen nicht versteht, wer dafür keine Lösungsidee hat, der kann keine angemessenen Testfälle bestimmen.
Wie oft das der Fall ist und TDD es kaschiert, ist immer dann sichtbar, wenn TDD-Sitzungen mit Tests auf "Extremwerte" (z.B. Null oder Leerstring) beginnen. Das sind Verlegenheitstests, um ans Codieren zu kommen. Ihr Kundennutzen ist marginal. Sie verschieben die Notwendigkeit, sich mit dem Problem richtig auseinander zu setzen nur.
Ohne tiefes Domänenverständnis keine guten Testfälle, die die Implementation in kleinen KISS-Schritten vorantreibt.
Ebenso ohne tiefes Domänenverständnis kein gutes Design.
Und ohne Designkompetenz auch kein gutes Design.
Angesichts solcher Voraussetzungshürden frage ich mich, woher die Popularität von TDD rührt. Meine Erklärung: TDD wurde von Leuten erfunden und gepusht, die hohe Designkompetenz haben und einen Weg gesucht haben, die zügig im Code anwenden zu können, statt sich in Designsitzungen zu verlieren. Die Agilitätsgrundsätze lassen grüßen.
Und bei denen funktioniert TDD auch durchaus. Insbesondere bei Code Katas. Denn die werden mit gutem Beispiel von denen vorgeführt, die erstens das Kata-Problem durchdrungen haben und zweitens hohe Designkompetenz besitzen. Guten Sportlern helfen mentales Training und optimierte Sportgeräte. Grobmotoriker hingegen brauchen keine Hightech, sondern müssen ersteinmal Grundfähigkeiten entwickeln. Es gilt die Bedingung für die Möglichkeit hoher Leistung zu schaffen.
Dasselbe gilt bei der Softwareentwicklung. Damit TDD dem Schluss-D gerecht werden kann, müssen einfach zunächst die Bedingungen stimmen. Das, so scheint mir, ist aber seltener der Fall als man gern annimmt. Wo sollen Sie denn auch geschaffen worden sein, die Bedingungen? Wo wird hohe Designkompetenz systematisch vermittelt, die TDD zur Entfaltung bringen kann? Wo werden mentale Modelle gelehrt, die dann mit TDD anstreben kann?
Das Missverständnis besteht also darin, dass TDD diese Kompetenz vermitteln oder ihre Abwesenheit kompensieren würde.
Was bleibt dann noch von TDD-Anspruch?
Test-Driven Development hat unzweifelhaft Wert. Wir brauchen systematisch mehr Korrektheit für unseren Code.
Für ein Test-Driven Design ist aber mehr nötig; allemal da TDD kein Messinstrument für Designqualität bietet.
Zuallererst müssen auch die Testfälle gut gewählt werden. Sonst wird selbst die Herstellung der Korrektheit schwierig.
Und so komme ich zu dem Schluss: TDD kann nur etwas bringen, wenn man wirklich versteht, worum es geht und einen Lösungsansatz hat. Dann und nur dann kann sich Designkompetenz mit TDD noch günstiger als ohne entfalten.
Doch wie zu einem Verständnis von Problem und Lösung kommen?
Ich bin ja der Meinung, dass Nachdenken hilft. Nachdenken und eine Vorstellung vom Design einer Lösung im Kopf bzw. am Whiteboard entwickeln. Keine detaillierte, nicht auf Codeniveau, aber eine solide. Sie sollten das Zutrauen haben, die Lösung ohne große Probleme codieren zu können. (Was nicht bedeutet, dass Sie sich damit nicht verschätzen können. Aber das macht nichts. Das kompensieren die TDD-Minititerationen.)
Ab einem gewissen Puntk jedoch, wird Nachdenken zu theoretisch und man tut gut daran, es mit lauffähigem Code zu untermauern bzw. zu befördern. Das kann ein Spike sein oder eben testgetriebener Code. Wenn der kleinschrittig entsteht, nähert man sich der/einer Lösung in kleinen Schritten.
Hier bringt TDD etwas über die Korrektheit hinaus. Das letzte "D" würde ich dann aber ersetzen durch ein "E" für Exploration (TDE) oder ein "U" für Understanding (TDU). TD hilft der Korrektheit und dem Verständnis. Test-Driven Understanding ist ein einlösbares Versprechen, glaube ich.
Das hat dann allerdings eine Konsequenz für die Praxis der beliebten Code Katas: man sollte sie eher nicht wiederholen. Denn wenn man sie einmal verstanden hat, dann kann man die Entwicklung von Verständnis an ihnen nicht mehr üben. Das jedoch ist das Schwerste in der Softwareentwicklung, scheint mir. Eine Problemstellung wirklich durchdringen, kostet einfach Mühe und Zeit und braucht auch wieder gewisse Kompetenzen.
Coding Dojos scheitern weniger daran, dass die Regeln des TDD nicht beachtet oder Technologien falsch eingesetzt werden. Sie scheitern daran, dass das Problem ungenügend durchdacht wird - und die wie immer geartete Lösungsvorstellung auf wenig Designkompetenz trifft. Da kann dann TDD nichts retten.
Wer TD fürs Development einsetzt, d.h. für mehr Korrektheit, der wird leicht Erfolge erzielen. Wer TD fürs Understanding einsetzt, wird auch voran kommen. Wer jedoch animmt, Designkompetenz durch “Rituale” ersetzen und Verständnis wie Lösungsentwurf überspringen zu können, der wird von TDD frustriert bleiben. TDD ist keine Abkürzung.
Das Problem hinter TDD harrt also immer noch einer Lösung: Wie erhöhen wir in der Branche durchweg die Designkompetenz?
Spendieren Sie mir doch einen Kaffee, wenn Ihnen dieser Artikel gefallen hat…
Fußnoten
[1] Auch wenn ich unterschiedliche Links zu TD-Development und TD-Design angegeben habe, unterscheiden sich die Beschreibungen nicht wesentlich. Im Rückblick lässt sich daher wohl schwer sagen, wann aus welchem Grund aus Development Design geworden ist. Aber vielleicht kennen Sie den Umbruchpunkt und können für ihn einen Literaturbeleg liefern?