Refaktorisieren im TDD Prozess
Inzwischen ist das TDD Mantra “Red/Green/Refactor” nicht mehr ganz unbekannt. Die Grundidee:
- Schreibe einen Test – Red
- Implementiere gerade soviel, dass der Test erfolgreich durchläuft – Green
- Räume auf – Refactor
Entwickler die beginnen, sich mit Test Driven Development zu befassen, sehen die größte Herausforderung darin, die Reihenfolge von Implementierung und Test umzudrehen. Das ist in der Tat nicht immer einfach und erfordert Disziplin. Man muss sich zunächst mal auf dieses Experiment einlassen, um den tatsächliche Nutzen zu
erkennen. Der liegt nämlich nicht nur in einer verbesserten Korrektheit, sondern vor allem in einer besseren Evolvierbarkeit. Das liegt daran, dass TDD einen starken Einfluss darauf hat, wie man seinen Code strukturiert. Denn es entsteht, oft wird behauptet ganz von alleine, gut testbarer Code. Meiner Erfahrung nach verbessert sich die Qualität des Codes zwar nicht so ganz von alleine, aber ich bestreite nicht, dass schon allein die Reihenfolge, erst Test, dann Implementierung einen positiven Einfluss auf die Codequalität hat. Deutlich besser wird die Qualität durch einen vorgelagerten Planungsschritt. Damit rede ich nicht dem “Big Design Upfront” das Wort, sondern ich meine, als Entwickler müssen wir uns die Zeit nehmen, vor dem Codieren ein paar Skizzen auf Papier zu machen.
Angeregt durch einen Artikel in der aktuellen Ausgabe der Visual Studio One möchte ich aber etwas zum Refaktorisieren los werden. In dem Artikel war eine Abbildung zur Vorgehensweise bei TDD abgedruckt. Nach kurzer Suche stolperte ich bei Wikipedia über eine ähnliche Abbildung. Das Bild beschreibt TDD als Prozess. Was mir sofort auffiel ist die Tatsache, dass es auf dem “Repeat” Pfad vom Refaktorisieren zurück zum nächsten Test keine Entscheidung gibt. Zwischen Test und Implementierung liegt eine Entscheidung: man geht erst zur Implementierung über, wenn man einen Test geschrieben hat, der fehlschlägt. Auch beim Übergang von der Implementierung zum Refaktorisieren kommt es zu einer Entscheidung. Diesmal geht es erst weiter zum Refaktorisieren, wenn alle Tests erfolgreich verlaufen. Doch dann die Überraschung: vom Refaktorisieren geht es einfach so wieder zurück zum nächsten Test. Da stellt sich mir doch die Frage, wann bin ich denn mit dem Refaktorisieren fertig? Und in der Tat taucht diese Frage bei Entwicklern in der Praxis ebenfalls auf, ohne dass sie sich den Prozess anhand eines solchen Bildes bewusst machen. Und auch beim Coding Dojo in München im Rahmen des dotnetpro-powerday kam kurz die Diskussion auf, ob denn nach der Implementierung nicht refaktorisiert werden solle. Die Frage ist also ganz offensichtlich nicht nur, wann bin ich fertig mit dem Refaktorisieren, sondern soll ich überhaupt refaktorisieren?
Meine These dazu: das Mantra Red/Green/Refactor suggeriert etwas Falsches. Da steht Refaktorisieren nämlich jeweils am Ende, nach der Implementierung. Es suggeriert, dass man immer nach der Implementierung refaktorisieren solle. Und das halte ich für falsch. Allemal, wenn beim Refaktorisieren nicht zwischen Mikro und Makro unterschieden wird. Mikrorefaktorisierungen wie etwa das Reformatieren des Quellcodes mit dem Ziel, die Klammern alle an der richtigen Position zu haben oder das Umbenennen eines Bezeichners, etc. stehen möglicherweise desöfteren nach der Implementierung an. Aber die größeren Refaktorisierungen eben nicht. Damit meine ich beispielsweise Änderungen an Zuständigkeiten; so etwas wie das Aufbrechen einer Klasse in zwei, mit dem Ziel, das Single Responsibility Principle (SRP) einzuhalten. Oder auch das Aufteilen einer Methode in mehrere. Das sind alles Refaktorisierungen, für die ich einen triftigen Grund benötige. Dabei bitte ich zu berücksichtigen, dass ich wie oben erwähnt, Verfechter von Planung bin. Wenn ich mich vor dem ersten Test mit einem Stück Papier hinsetze entstehen selten Strukturen, die hinterher einer größeren Refaktorisierung bedürfen.
Mein Vorschlag: das Mantra sollte etwas anders angeordnet werden. Es sollte lauten [Refactor]/Red/Green. Dabei sollen die eckigen Klammern andeuten, dass Refaktorisieren vor dem nächsten Test nicht in allen Fällen, aber oft erforderlich ist. Der Vorteil: nun habe ich konkrete Anhaltspunkte, ob ich Refaktorisieren muss bzw. wann ich damit fertig bin. Gelingt es mir nicht, eine neue Anforderung umzusetzen, weil die Struktur des Quellcodes dafür ungeeignet ist, habe ich einen Grund zu refaktorisieren. Sobald ich in der Lage bin, den nächsten Test zu schreiben, ohne dabei Prinzipien zu verletzen, bin ich fertig mit dem Refaktorisieren.
Eine Hinweis zum Schluss: die testgetriebene Entwicklung ist ein komplexer Prozess, den man nicht mit einem Mantra aus drei Wörtern vollständig beschreiben kann. Mir ist selbstverständlich klar, dass man nach der Implementierung kurz inne halten sollte um zu prüfen, ob kleine Mikrorefaktorisierungen anstehen. Diese sind aber so unspektakulär, dass sie keines der drei Worte im Mantra schon komplett für sich beanspruchen sollten. Wenn also bei TDD von “Refactor” die Rede ist, dann ist es wohl sinnvoll, damit die aufwändigeren strukturellen Refaktorisierungen zu bezeichnen. Und die gehören für mich an den Anfang statt ans Ende.
June 30th, 2010 at 11:52
Ich meine, zwischen [Refactor] und Red _muss_ eine Planungsschritt zwingend eingefügt werden – es geht einfach nicht ohne. Und diese Abkehr vom TDD-Dogmatismus können wir nur durch einen Extra-Schritt transportieren (meine ich zumindest).
Da Tests zum Fehlschlagen da sind, da sie nur Fehler aufzeigen können, ist auch zu Bedenken das in der Praxis ein “Aufpolieren” der Diagnose-Meldungen des Testrunners oftmals nötig ist.
June 30th, 2010 at 16:52
Die Frage, wann und wieviel ich refaktorisieren soll, habe ich mir auch schon einige Male gestellt.
Danke, guter Input!
July 1st, 2010 at 8:24
@Sebastian Habe über den Planungsschritt nachgedacht. Ich sehe hier zwei Prozesse: im äußeren Prozess beginnt es in der Tat mit der Planung. Dann kommt der innere Prozess mit den Schritten Refactor/Red/Green. Schließlich ist nicht bei jedem Test eine neue Planung erforderlich. Die beiden Prozesse sind ähnlich ineinander geschachtelt wie man es bei vielen Scrum Darstellungen sieht. Werde dazu später mal was bloggen.
July 1st, 2010 at 8:33
Hallo Stefan,
hm – um ehrlich zu sein, gefällt mir der Vorschlag nicht. Nicht wegen der Planung. Dass diese notwendig ist, da bin ich bei Dir. Aber mir gefällt das optionale Refaktorisieren nicht.
Warum nicht immer einen zwingenden Refaktorisierungsschritt einlegen? Wenn man in dessen Durchführung dann feststellt, dass nichts zu tun ist, ist man halt direkt nach der Refaktorisierungsanalyse fertig.
Ihn aber als optional darzustellen, halte ich für gefährlich. Ich erinnere an Ralfs Artikel “Wie viel Matsch macht ein Brownfield?”, den er als Sandbox-Kolumne in der dotnetpro veröffentlicht hat. Wenn ich Refaktorisieren EINMAL weglassen kann, weil es mir gerade nicht in den Kram passt, dann liefere ich damit direkt die Absolution, es auch beim nächsten Mal bleiben zu lassen. Und beim übernächsten Mal …
Du magst einwenden, dass man so viel Verantwortungsbewusstsein haben sollte, es doch ab und zu durchzuführen – doch selbst, wenn wir annehmen, dass dies in der Praxis so ist, liefert man damit immer noch dem Management Munition, so nach dem Motto: Es muss jetzt mal schnell gehen, Refaktorisierungen sind gestrichen, sie sind ja ohnehin nur optional (und damit implizit scheinbar nicht so wichtig).
Insofern: Wer TDD betreiben will, sollte sich an den TDD-Prozess halten. Nicht zu 90%, nicht zu 80%, sondern zu 100%. Sonst ist es kein TDD mehr, und verleitet zu nicht aufgeräumtem Code.
Viele Grüße,
Golo
July 1st, 2010 at 10:43
[...] Stefan Liesers Blog « Refaktorisieren im TDD Prozess [...]
July 1st, 2010 at 10:49
Dritter Versuch aufgrund von gemeinen Kommentarfressern:
Hi,
ich bin genauso wie Ihr stark am überlegen wie das Refaktoring für mich am meisten Sinn macht.
Ich stimme Stefan natürlich zu, dass man am Anfang einer Aufgabe durchaus refaktorn muss falls man seinen Test sonst nicht anlegen kann.
In einem solchen Fall hat man ja defacto keine Wahl. Aber ist das die Regel?
Was ich allerdings nicht ganz verstehe ist warum am Ende (nach “schnellem Grün”) nicht auch Refactoring betrieben werden sollte.
Normalerweise will ich ja jetzt bei Grün einchecken, da ich einen gewissen Teil der Funktionalität erreicht habe.
Laut der Pfadfinderregel, die Ihr ja beim CCD schon in den ersten Graden drin habt, sollte ich aber möglichst aufgeräumt einchecken, oder?
“Nach getaner Arbeit stimmt der Code also mit dem Clean Code Development Wertesystem mehr überein als vorher.” (http://clean-code-developer.de/wiki/CcdRoterGrad#DiePfadfinderregelbeachten)
Vielleicht betrachte ich das jetzt auch “zu klein”, aber jedes erreichte Grün ist für mich getane Arbeit und ein Grund zum commit.
Also so wie ich das bisher sehe führt mich die Pfadfinderregel direkt zu Golos Auffassung von “Refactor always”.
Wenn ich allerdings nicht bei jedem Grün commite (widerspricht allerdings “Kleine Commits”), dann könnte ich vermutlich warten bis der Smell zu groß wird.
Trotzdem muss ich am Ende aufgrund der Pfadfinderregel sauber machen. Also irgendwie muss das Refactor schon (auch) an den Schluss der Kette.
Wie seht Ihr das?
Grüße Steffen
July 1st, 2010 at 11:25
@Steffen Wie schon im Blogpost erwähnt sollten wir beim Refaktorisieren unterscheiden zwischen “Kleinkram” und “großem Zeugs”. Nach der Implementierung etwas Aufräumen, bevor man eincheckt, ist völlig in Ordnung (sofern man dann noch mal alle Tests laufen lässt). Das bezeichne ich aber eher als Aufräumen der Baustelle. Mit Refaktorisieren im Sinne von TDD verbinde ich größere Aktionen. Und die stehen für mich in Zusammenhang mit einer neu umzusetzenden Anforderung, also am Anfang einer TDD Schleife.
July 2nd, 2010 at 5:21
@ Steffen: Fullack.
July 5th, 2010 at 18:26
[...] http://www.lieser-online.de/blog/?p=227 [...]