Gerade wird wieder eine TDD Demo über Twitter herumgereicht. Corey Haines hat sich an die Kata Roman Numerals gemacht.
Mal abgesehen davon, dass TDD anscheinend ein unerschöpfliches Thema ist und die Katas auf die Dauer langweilig werden… Mir gefällt die Darstellung aus einem anderen Grunde nicht so gut.
Corey gibt sich Mühe. Alles läuft seinen kanonischen TDD Weg. Es könnte ein schönes Video sein. Wäre da nicht die ständige Überraschung.
Ja, es hört sich so an, als würde Corey in die Lösung des Problems “Übersetzung arabischer Zahlen in römische” stolpern. Er zaubert Testfälle aus dem Hut und erstaunt sich dann immer wieder selbst mit der Lösung.
Und ich meine hier wirklich die Lösung und nicht den Code.
Da scheint mir ein Grundproblem im Umgang mit TDD zu liegen. Das habe ich auch neulich auf den XPdays in Coding Dojos gesehen. Das Muster ist so:
- Es wird ein Problem vorgestellt.
- Es wird mit dem Codieren à la TDD begonnen.
Das Ergebnis? Regelmäßig kein Code, der die Aufgabe vollständig erfüllt [1].
Mit dieser Realität sollten wir nicht streiten, denke ich. So ist es einfach. Man bemüht sich redlich um die rechte TDD-Schrittfolge. Das kommt mir vor wie beim Tanzen. Alle starren gebannt auf ihre Füße und hoffen, niemanden anzurempeln. Nur leider geht dabei das große Ganze verloren. Beim Tanzen der Spaß an der Bewegung und am Miteinander. Und bei der Softwareentwicklung lauffähiger Code. Vor lauter TDD-Rhythmus und versuchtem Design funktioniert es nicht mal.
Wie frustrierend, wie tragisch. Kein Wunder, dass auch 2012 immer noch TDD hoch und runter evangelisiert werden muss.
Dabei scheint mir die Rettung der Situation einfach: mehr Systematik.
Ja, tut mir leid, dass ich mit so einem lästigen Wort komme. Das klingt nach Einschränkung, nach viel Aufwand ohne schnellen Nutzen… doch das Gegenteil ist der Fall. Systematik macht frei. Aus Komplexem macht sie Kompliziertes.
Fehlende Systematik überfrachtet TDD. TDD soll plötzlich die ganze Softwareentwicklung retten. Endlich wird das mit der Korrektheit besser. Und dann auch noch bessere Dokumentation durch TDD. Und außerdem höhere Evolvierbarkeit durch besseres Design (lies: bessere Strukturen). Vor allem aber nicht zu vergessen: eine Lösung stellt sich auch wie von selbst ein.
Kommt das niemandem merkwürdig vor? One size fits all?
Ich bin ein großer Freund der Prinzipien Single Responsibility (SRP) und Separation of Concerns (SoC). Danach scheint mir ein bisschen viel Last auf den Schultern von TDD zu liegen.
Das ist es auch, was mich an Coreys Demonstration wieder stört. Er steht dabei nur als einer von vielen, die TDD zeigen. Die Vermischung von Lösung und Design stößt mir auf. Sie ist es nämlich, die zu den erwähnten Misserfolgen in den Dojos führt.
TDD bedeutete zunächst Test-Driven Development. Da ging es also um eine bestimmte Art zu codieren. Red-green-refactor. Das hat vor allem zu hoher Testabdeckung geführt.
Dann wurde aus dem Development das Design: Test-Driven Design. Die Betonung wurde damit auf den Refactoring-Schritt gelegt. Zur hohen Testabdeckung sollte dann auch noch eine “gut” Codestruktur kommen.
Und heute? Mir scheint, dass es gar nicht mehr um TDD geht, sondern um TDPS: Test-Driven Problem Solving. Nicht nur sollen Tests zu einem Design führen – denn über das Design soll man ja vorher nicht nachdenken, es soll entstehen, in minimaler Form. Nein, jetzt sollen am besten die Tests auch noch die Lösung herbeiführen.
Wenn Sie sich nun fragen, was denn da der Unterschied sei, dann rühren Sie genau an dem Problem, das ich meine: Es wird kein Unterschied zwischen Lösung und Code gesehen. Oder vielleicht sollte ich sagen, zwischen Lösungsansatz und Code? Ist es dann deutlicher?
Hier zwei Beispiele für den Unterschied.
Als erstes eine Lösung für das Problem des Sortierens eines Feldes. Text und Bild beschreiben einen Ansatz, sie beschreiben ein Vorgehen, sie sagen, wie man das Ziel ganz grundsätzlich erreichen kann:
Und jetzt ein Design in F# für diesen Lösungsansatz:
Im Design, im Code finden sich natürlich die Aspekte und Schritte des Lösungsansatzes wieder. Aber der Code ist nicht der Lösungsansatz. Er implementiert ihn in einer bestimmten Programmiersprache mit bestimmten Sprach- und Plattformmitteln.
Als zweites ein Lösungsansatz für ein Problem im Compilerbau, die Erkennung von Bezeichnern. Hier ein Syntaxdiagramm dafür:
oder alternativ ein Deterministischer Endlicher Automat:
Dass es überhaupt eine Phase zur Erkennung von Bezeichnern gibt (lexikalische Analyse), ist ebenfalls Teil eines Lösungsansatzes:
Das konkrete Code-Design, die Implementierung des Lösungsansatzes, könnte dann so aussehen:
Lösungsansatz – oder auch Modell – und Code in einer bestimmten Struktur – nach TDD auch Design genannt –, sind einfach verschiedene Aspekte. In den TDD-Vorführung wie bei Corey und den TDD-Selbstversuchen in den Dojos werden die jedoch nicht sauber getrennt. Immer wieder wird gehofft, durch Red-Green-Refactor nicht nur ein evolvierbares Design herzustellen, sondern auch eine Lösung zu bekommen.
Das (!) halte ich für falsch. Erstens ganz praktisch aus der Beobachtung heraus, dass so selten lauffähiger Code entsteht, der die Aufgabe erfüllt. Zweitens eher theoretisch aus dem Gedanken heraus, dass wir Menschen damit schlicht unterfordert sind. Das über Jahrtausende geschliffene Werkzeug “Denken” wird nicht genutzt. Man hofft vielmehr, durch Mustererkennung beim Code, irgendwie zu einer Lösung zu kommen.
Das funktioniert manchmal tatsächlich, wenn man genau hinschaut. Die Kata Roman Numerals könnte dafür ein Fall sein. Nur ist nicht zu erwarten, dass das immer so geht. Auf den Quicksort Lösungsansatz kommt man nicht durch TDD, davon muss man einfach eine Vorstellung entwickeln – eben einen Lösungsansatz. Im Kopf. Durch Nachdenken.
Und wie sollte es dann anders aussehen mit TDD?
Systematischeres Vorgehen
Systematisierung, Entzerrung, Entlastung, Fokus finde ich wichtig. Aus meiner Sicht sollte das Vorgehen diese Schritte beinhalten:
- Problem vorstellen
- Lösungsansatz entwickeln
- Testfälle ermitteln und priorisieren
- Lösungsansatz mit TDD implementieren
Schritte 2. und 3. können dabei mehrfach durchlaufen werden. Und wenn sich bei 4. noch neue Erkenntnisse zum Lösungsansatz ergeben sollten, dann ist das auch ok. Aber 4. ohne explizites 2. und 3. zu beginnen, halte ich für eine Überlastung.
Damit wären Lösungsansatz und Design getrennt. Damit würde – da bin ich ganz sicher – die Erfolgsquote jedes Coding Dojos steigen. Und wenn nicht, dann würde man genau sehen, woran es liegt: Liegt es an mangelnden TDD-Fähigkeiten oder liegt es an mangelndem Problemverständnis und dadurch fehlender Lösungsphantasie?
Die Single Responsibility von TDD liegt für mich bei der Testabdeckung und bei einer hohen Strukturqualität im Kleinen [2].
Was jedoch da überhaupt in Code gegossen und strukturiert werden soll… das ergibt sich nicht durch TDD, sondern durch Nachdenken. Den Lösungsansatz zu finden und die Testfälle zu priorisieren, das ist nicht Teil von TDD – muss aber gezeigt werden. Denn wird es nicht gezeigt bzw. wie bei Corey mit dem TDD-Vorgehen vermischt, entsteht entweder eine Überlastung, die die Aufgabenerfüllung behindert. Oder es entstehen “Wunderlösungen”, über die man nur staunen, sie aber eher nicht nachvollziehen kann. Wer “Wunderlösungen” goutiert, der wird es schwer haben, selbst Lösungen für andere Probleme zu finden.
Fußnoten
[1] Damit will ich nicht sagen, dass es keine Teams gibt, die die Aufgaben schaffen. Aber das passiert eben nicht regelmäßig und systematisch. Ich erinnere mich lebhaft an ein Dojo auf den XPdays und an eines auf der DDC im letzten Jahr. 5-10 Teams gab es jeweils. Und keines hat funktionierenden Code für die Kata Roman Numerals bzw. die Kata Zeckendorf (Übersetzung einer ganzen Zahl in eine Zeckendorf-Sequenz) abgeliefert.
[2] Wobei sogar diese Strukturqualität im Kleinen nicht einfach so entsteht, wie ich an anderer Stelle schon ausgeführt habe. Dafür braucht es Refactoring-Kompetenz und –Willen. Die kommen nicht aus TDD. Dafür braucht es aus meiner Sicht noch Hilfestellung, die Refactorings näher legt. Dadurch kann sich ein TDD 2.0 auszeichnen. Aber dazu ein andermal mehr…