Was ist TCR?
English Translation: What Is TCR?
TCR wurde von Kent Beck vor Kurzem als neue Programmiertechnik vorgestellt. TCR wird derzeit als Nachfolger von Test Driven Development (TDD) gehandelt. Dieser Artikel beschreibt wie TCR entstanden ist und was der Unterschied zwischen TCR und TDD ist. Es werden die Vorteile und Nachteile der beiden Arbeitstechniken gegenübergestellt und es werden eigene Ideen zur Diskussion gestellt.
Entstehung von Test Driven Development
ist ein legendärer Software-Pionier. Er ist einer der Autoren und Erstunterzeichner des Manifests für agile Software-Entwicklung. Er ist Begründer von Extreme Programming (XP) und er ist Wiederentdecker von Test Driven Development (TDD).
In einem uralten Buch über Programmierung las Beck einst das Folgende:
… you take the input tape, manually type in the output tape you expect, then program until the actual output tape matches the expected output.
Unbekannte Quelle
Mit „tape” sind hier nicht etwa Magnetbänder gemeint. Es sind die Lochstreifenbänder, die dazu dienten die Ausgabe an einen Fernschreiber als Drucker zu leiten.
Als Assert-Methode legte man vermutlich die beiden Lochstreifen übereinander und hielt sie zur Überprüfung gegen das Licht.
Kent Beck schrieb das erste xUnit-Framework in Smalltalk und gilt damit als der Gründer des modernen TDD.
Entstehung von test && commit || revert
Jetzt hat er einen ganz neuen Workflow entdeckt. Er nennt ihn TCR und viele glauben diese Technik wird TDD bald ersetzen. Wieder kam der zündende Funken von außen. Kent Beck hatte gerade die Programmiermethode „test && commit“ eingeführt. Hierbei wird der Code jedes Mal sofort eingecheckt nachdem die Tests erfolgreich ausgeführt worden sind. Der Programmierer „Oddmund Strømmer” hatte den Einwand, dass man aus Gründen der Symmetrie bei Failing-Tests den Code dann wieder entfernen müsse (revert), also -„test && commit || revert“. Beck hasste diese Idee, trotzdem war er versessen darauf es auszuprobieren. So entstand TCR.
Die Idee von TCR ist also, sobald ein neuer Test grün ist, werden die Änderungen committed. Schlägt aber der Test fehl, wird der Code wieder in den Ausgangszustand zurückversetzt. Am 28. September 2018 erschien der Artikel „test && commit || revert”. Darin wurde die neue Programmiermethode erstmals offiziell vorgestellt.
Natürlich ist die Vorstellung, gerade eben geschriebenen Code wieder zu löschen absurd. Für mich ist aber ebenso absurd, bei einem winzigen Stückchen Code jedes Mal einzuchecken. Wie entstehen solche Ideen?
Um diese Frage zu beantworten, muss man etwas weiter ausholen.
Anfänge einer neuen Idee
Während der Zeit bei Facebook, die bis Februar 2018 ging, begann Beck darüber nachzudenken, wie 100.000 Entwickler gleichzeitig am selben System zusammenarbeiten könnten. Die Prozesse bei Facebook waren sehr umständlich und führten zu unvorhersehbaren Verzögerungen. Nach seinem Ausscheiden bei Facebook konzentrierte er sich wieder auf Smalltalk. Bei einem Projekt, bei dem es um den Syntaxbaum für den Bytecode-Compiler ging, erkannte er, dass Baumtransformationen sehr eng mit den Fragen zur Skalierung zusammenhängen.
Conways Game of Life inspirierten ihn zu dem Gedanken, dass sich auf ähnliche Weise Informationen ausbreiten können. Was ist wenn jeder Pixel ein Programmierer wäre und wenn jede Änderung eines Pixels eine Änderung eines Programms wäre? Wie hängen diese Zustandsänderungen mit den Transformationen eines Syntaxbaumes zusammen?
Beck arbeitete vor einigen Jahren mit Thiago Hirai an dem Code Editor Prune. Es zeigte sich überraschenderweise, dass das circa 100 Operationen ausreichen, um alle gängigen Änderungsvarianten, die syntaktisch korrekt sind, abzudecken. Wenige Transformationen sollten also genügen, um ein Programm in einem „korrekten” Zustand zu belassen. Merge-Konflikte wie zum Beispiel das Umbenennen derselben Variablen in verschiedenen Namen sind damit leicht zu erkennen.
Limbo
Es entstand so die Idee von Limbo. Limbo wird in der englischen Sprache auch damit assoziiert, ewas in der Schwebe zu halten. Limbo ist live, shared programming, bei dem der Schwerpunkt der beiden folgenden Prinzipien ständig im Gleichgewicht gehalten wird:
- Jeder arbeitet an dem selben lauffähigen Programm, welches durch einen Syntaxbaum repräsentiert wird.
- Niemand darf anderen (einschließlich der Anwender) Probleme bereiten.
Winzige Änderungen der Codebasis verbreiten sich permanent entlang des Syntaxbaumes, so wie bei Conways Game of Life und wie bei einem lebenden Organismus.
Noch sind viele Fragen offen und noch gibt es kein Tool, welches fix an den Syntaxbaum ankoppelt. Änderungen wären dann nur noch als Baumumwandlungen möglich. Limbo ist derzeit noch ein Gedankenexperiment – eine Lernübung.
Um praktische Erfahrungen zu sammeln, ermutigte Beck seine Fans mit GIT und Quelltexteditor zu experimentieren. Eigentlich wollte er nur sehen, wie Limbo sich anfühlt und welche Anreize es schafft. Er schuf Limbo auf die billige Tour. Die Verwendung von GIT beschreibt er im Artikel „Limbo on the Cheap“ mit dem Wortspiel „(ab)using GIT”
Rather than have tree transformations zipping around modifying the program, we chose to use Git. Both pairs cloned the same remote repository. Then we ran the following shell script:
while(true);
do
git pull –rebase;
git push;
done;
How would we make sure we didn’t break each others’ code? We wouldn’t propagate changes unless all the tests pass. After every change, we ran this script:
test && git commit -am working
The “test” script builds the system, runs the tests, and only returns 0 if all the tests pass. (We actually reverted if the tests failed, but that is a tale for a different article.)
Kent Beck – Limbo on the Cheap
TCR vs. TDD
Damit bin ich wieder am Anfang der Geschichte. Die Idee des Programmierers Oddmund Strømmer führte nämlich dazu, dass das GIT Kommando abgeändert wurde zu „test && commit || revert”. So entstand TCR. Ist ein Test nicht erfolgreich, so führt dies zum Löschen des gerade eben geschriebenen Codes. Somit bleiben alle Tests permanent grün. Das Betätigen der Undo-Funktion hält Beck übrigens für Schummeln.
Bei TDD wird immer zuerst ein Test geschrieben und dann erst produktiver Code. Der erste Test muss immer ein Failing-Test sein. Auch Syntaxfehler zählen dazu. Beispielsweise wird eine noch nicht vorhandene Funktion bereits als Assert-Parameter verwendet. Dieser Zustand heißt „red”. Als nächstes wird genau soviel Produktivcode implementiert, dass der Test erfolgreich durchläuft. Es ist also eine Zustandsänderung auf „green”. Falls nötig folgt nun ein Code „refactor“ für Clean Code. Das Motto lautet: „First make it work then make it right”. Solch ein Zyklus sollte nur wenige Minuten dauern.
Ein typischer Anfängerfehler bei TDD ist, dass man die Tests so formuliert, dass für ihre Erfüllung eine große Menge produktiver Code geschrieben werden muss. Man macht dabei zuviele Schritte auf einmal. Das sprengt den empfohlenen Zeitrahmen für einen Zyklus. Aber noch schlimmer ist dabei, dass die Zwischenschritte ungetestet sind. Falls so entstandener produktiver Code dann unerwartet beim Test nicht grün wird, weiß man nicht, an welchem Zwischenschritt das lag. Man muss wieder mit dem Debugger arbeiten. Das Verwenden eines Debuggers ist immer ein Symptom dafür, dass man die TDD-Technik noch nicht richtig beherrscht.
TCR zwingt im Unterschied zu TDD zu kleinen Schritten. Wer will schon den Verlust eines größeren Happen Codes riskieren. Als erzieherischer Ansatz ist dies sehr begrüßenswert.
TDD macht keine Vorschriften oder Empfehlungen zum Einchecken von Code. Wer als Softwareentwickler Code mit Syntaxfehlern eincheckt, der gilt als Codebreaker. Ein Codebreaker bereitet anderen im Team Probleme. Das gilt erst recht, wenn er kurz vor Feierabend eincheckt und dann nicht mehr erreichbar ist. Um überhaupt weiterarbeiten zu können, muss kurzfristig eine Löung gefunden werden. Das geschieht dann häufig über einen Revert. Bei TDD ist man auch schon ein Codebreaker, wenn irgendein Test rot wird. Daher sollte vor dem Einchecken ein lokaler Build-Lauf ausgeführt werden, der alle Test-Suites erneut compiliert und alle Tests ausführt.
Das dauert in der Regel solange, dass die Zeit dafür reicht, einen Kaffee zu trinken.
Typischerweise kommt es beim Zusammenführen von Code (Merge) zu Konflikten, die zu Fehlern führen oder nicht mehr aufgelöst werden können. Je größer die Menge Code ist und je je seltener in den Haupt-Branch eingecheckt wird, desto größer und problematischer sind diese Konflikte. Durch TCR kann dieses Problem praktisch nicht mehr entstehen. Den Einwand, es dauert zu lange, bis die Tests alle ausgeführt werden, kann ich nicht gelten lassen. Es müssen ja immer nur die Tests ausgeführt werden, die von den Codeänderungen betroffen sind. Ansonsten reicht ein Nightly Build.
„TDD ist tot, lang lebe TCR“
Ist TCR nun das neue TDD? Soll man überhaupt noch TDD lernen, oder soll man gleich auf TCR umsteigen? In einem französischen Blog las ich schon die Schlagzeile „TDD est mort longue vie TCR?“. (Google Übersetzung: „TDD ist tot, lang lebe TCR“).
Nein, TCR wird im Unterschied zu TDD keine praktische Relevanz erhalten. Zumindest nicht in der gegenwärtig praktizierten Form. TCR ist lediglich ein Experiment für das zukünftige Limbo. Die missbräuchliche Verwendung von GIT verursacht viele praktische Probleme. GIT war nie dafür ausgelegt, Code in winzigen Häppchen einzuchecken. Die Commit-Protokolle lassen sich so kaum noch zur Dokumentation einer Code-Historie nutzen.
Auch ohne GIT schneidet der TCR Workflow im Vergleich zu TDD momentan noch schlechter ab. Den beiden o.g. Vorteilen stehen derzeit noch gravierende Nachteile entgegen. Der Hauptnachteil von reinem TCR ist, dass man nicht mehr sieht, welche Fehler man gemacht hat.
Die Test-First-Methode bei TDD, mit einem anfänglichen Failing-Test, führt den Programmierer wie ein Leitstrahl zum Ziel. Natürlich ist TDD kein Automatismus zum Lösen von Problemen, wie manchmal der Eindruck erweckt wird. TDD ist jedoch sehr hilfreich beim Fokussieren auf das Problem. Ständig hat man den nächsten Schritt zur Lösung vor Augen. TDD ist auf die künftige Lösung gerichtet. TCR ist dagegen immer nur auf die Gegenwart bezogen.
Ein weiterer Nachteil von TCR im Unterschied zu TDD ist, dass es einen dazu verführt, die Tests nach der Implementierung des produktiven Codes zu machen. Solange man keine Tests macht und solange man keine Syntaxfehler macht, wird alles eingecheckt. Macht man die Tests vorher, wie es sich gehört, dann sind sie es die den Code zerstören. Macht man dagegen im nachhinein einen richtigen Test für fehlerhaften produktiven Code, dann wird der Test anstatt der fehlerhaften Implementierung gelöscht.
Falsch negativ
Das führt nun direkt zum gravierendsten Nachteil von TCR. Was ist, wenn bei Beginn von einem TDD-Zyklus, statt des erwarteten Failing-Tests, plötzlich grün signalisiert wird?
Dann läuft etwas ganz entsetzlich schief. Alarmstufe rot! Der Test droht zu entgleisen. Der schlimmste Fehler, den man bei TDD machen kann, sind fehlerhafte Tests mit falsch negativen Resultaten.
Um diese Gefahr zu reduzieren, befolgen wir den Ratschlag von Kent Beck: „Never write a single line of code unless you have a failing automated test.“ (Hervorhebung von mir.)
Ich empfehle daher, bei kommerzieller Softwareentwicklung TCR vorerst nicht einzusetzen.
Hybrides TDD/TCR
Ich weiß nicht, wie man es mit GIT schaffen will, die „Spielregeln“ einzuhalten und die Gefahr von falsch negativen Tests zu vermeiden. Meine Idee zur Lösung dieser Probleme ist eine Art hybrides TCR. Eine Kombination aus TDD und TCR.
Die Entwickler arbeiten wie gewohnt mit ihrem lokalen Repository und betreiben TDD. Jedes Mal, wenn ein TDD-Test grün wird, startet automatisch ein Hintergrundprozess, der TCR auf dem shared Code durchführt. Fast immer müssten dann auch diese Tests ebenso erfolgreich grün werden und dadurch zu einem endgültigen Commit bewirken. Gleichzeitig sorgt dieser Hintergrundprozess dafür, dass das Repository auf dem lokalen Arbeitsplatz mit dem aktuellen shared Code synchronisiert wird.
In Ausnahmefällen schlägt ein Test fehl, obwohl er lokal erfolgreich war. Das kann nur bei einem zwischenzeitlich entstandenen Konflikt vorkommen. Der Entwickler bekommt signalisiert: „Es ist eine sofortige Konfliktlösung erforderlich.“
Der vorteilhafte erzieherische Effekt von TCR ist damit ebenfalls vorhanden, weil grundsätzlich eine Konfliktvermeidung angestrebt wird. Die Konflikthäufigkeit ist um so geringer und die Konfliktlösungen sind umso einfacher je kleiner die TDD-Zyklen sind.
Eine derartige hybride Lösung lässt sich nicht einfach über ein GIT-Kommando realisieren. Der Aufwand zur Umsetzung ist aber sehr viel geringer als die Realisierung von Limbo.
Das zukünftige Limbo
Limbo ist eine großartige Idee. Zur Verwirklichung sind aber noch viele Anstrengungen erforderlich. Da jede Programmiersprache ihre eigene Syntax hat, muss auch für jede unterstützte Programmiersprache ein eigenes Limbo geschaffen werden. Eigentlich ist sogar ein speziell zugeschnittenes Entwicklungssystem erforderlich, welches das kollaborative Bearbeiten zum Erstellen von grafischen Benutzeroberflächen unterstützt. Einfacher wäre es, eine neue Programmiersprache samt Entwicklungsumgebung speziell für Limbo zu schaffen. Dann hat man aber ein Akzeptanzproblem.
Ich gehe davon aus, dass Kent Becks Limbo eines Tages realisiert wird. TCR muss darin nicht enthalten sein. Ein optimales Limbo-Entwicklungssystem bietet nur die Baumtransformationen an, die syntaktisch und semantisch korrekt sind. Tests und produktiver Code entstehen dadurch praktisch parallel.
Die Idee von Limbo sollte dann auch noch weitere Anforderungen wie z.B. Code-Reviews berücksichtigen. Angenommen, wir hätten ein Tool ähnlich wie Google Docs und wir würden es dazu verwenden, um kollaborativ eine Geschichte in natürlicher Sprache zu schreiben. Angenommen, es gäbe neben der Gruppe der Texterfasser auch eine Gruppe von Korrektoren. Sie verhindern, dass ein Text der syntaktische oder semantische Fehler enthält und in diesem Tool für alle sichtbar wird. Ist dann der so entstandene Text auch wirklich gut? Eigentlich müsste es dann eine weitere Gruppe geben. Lektoren, die auf einer anderen Ebene nur den zufriedenstellenden Text durchlassen. Das entspricht einem Code-Review. Darüber hinaus könnte es eine weitere Gruppe geben, die auf der nächsten Ebene entscheidet, welche Bestandteile der Story so gut sind, dass man damit auch Geld verdienen kann, wenn man sie veröffentlicht.
Limbo sollte meiner Meinung nach eine solche Pipeline für unterschiedliche Code-Schichten implementieren. Im metaphorischen Sinne wäre das dann eine Art dreidimensionales Game of Life.
Limbo hat Zukunft, aber die beginnt noch nicht morgen.
Update vom 22.02.2019
Es gibt einen sehr interessanten Beitrag von Thomas Deniffel, der am 21.02 auch eine Präsentation beim Münchner Meetup zum Thema TCR zeigte. In dem jetzt noch offenen Medium-Artikel „All Downsides and Open Questions of TCR“ werden Nachteile und offene Fragen aufgelistet, die ihm bei seinen Workshops und Diskussionen begegnet sind. Er macht das nicht, weil er TCR schlechtreden will. Im Gegenteil er ist engagagiert und gegenüber TCR und Limbo sehr aufgeschlossen eingestellt. Ich halte es auch für sehr konstruktiv, Probleme offen anzusprechen. Manche dort genannten Nachteile sind auch bereits in diesem Blog-Post angesprochen worden. Das meiste ist aber neu oder anders dargestellt. IMHO: Es lohnt sich, nach Lösungen zu suchen. Limbo hat das Potential, eine zweite Revolution für nachhaltige Softwareentwicklung auszulösen und TCR ist hierbei die Avantgarde.
Vorheriger Beitrag: Besserer Clean Code mit nachhaltiger Softwareentwicklung
Kommentare
Was ist TCR? — Keine Kommentare