Posts mit dem Label Refactoring werden angezeigt. Alle Posts anzeigen
Posts mit dem Label Refactoring werden angezeigt. Alle Posts anzeigen

Dienstag, 1. Dezember 2009

Dieser Blog ist tot. Ich blogge weiter auf dem «Agile Trail».

Der grüne Pfad


Schön, wenn einem die Arbeit abgenommen wird: Kollege Stefan Roock schreibt in seinem neuesten Blogeintrag vom grünen Pfad, den ich beim Refactoring gerne gehe. Auf einem grünen Pfad gibt es minimales Risiko, sich im Code zu verlaufen. Aber der grüne Pfad ist nicht ganz einfach zu finden, auch wenn seine Regeln sehr einfach sind:
  1. Einen atomares, d.h. nicht weiter unterteilbares, Refactoring durchführen.
  2. Tests grün laufen lassen.
  3. Weiter mit 1.
Laufen die Tests bei 2. nicht grün, sondern rot, hat man den grünen Pfad verlassen und findet sich unweigerlich in der roten Testhölle wieder, aus der es nur durch Strg-Z oder CMD-Z einen sicheren Ausweg gibt.

Was sind atomare Refactorings? Solche, die nicht weiter durch andere Refactorings unterteilbar sind, etwa Umbenennen, Extrahieren, oder Inlinen. Die meisten anderen Refactorings sind aus atomaren Refactorings zusammen gesetzte Refactorings.

Stefan beschreibt sehr schön, dass es gar nicht darauf ankommt, immer auf dem grünen Pfad zu weilen. Eigentlich braucht man den grünen Pfad nur dann, wenn es knifflig wird. Aber wer nicht oft auf dem grünen Pfad gelaufen ist, der wird ihn nur sehr schwer finden, und wer nicht oft kleine Schritte beim Refactoring gegangen ist, der wird sich unter Druck oder in sehr krudem Big-Ball-of-Mud-Code nicht mehr daran erinnern können, also genau dann, wenn es einem besonders helfen würde.

Stefan hat ein schönes Beispiel aufgeschrieben, bei dem ich ihn quasi auf dem grünen Pfad vor mich hergeschoben habe - und er zu einer, wie ich finde, sehr cleveren Lösung gekommen ist.

Gerne gebe ich den Aufruf von Stefan weiter: Habt ihr Code, der nicht auf dem grünen Pfad zu refaktorisieren sei? Immer her damit: bernd.schiffer@gmail.com oder hier in die Kommentare. Wir freuen uns über jede Herausforderung :-)

Freitag, 21. August 2009

Dieser Blog ist tot. Ich blogge weiter auf dem «Agile Trail».

Pecha Kucha "Inkrementelles Design" (Folien und Video)

Auf der JAX '09 im Frühling diesen Jahres habe ich für it-agile in einer Pecha Kucha-Session auf dem Agile Day einen Vortrag "Inkrementelles Design" gehalten. Host war Stefan Roock (Homepage, Twitter, Xing). Das sind die Folien:

View more documents from Bernd Schiffer.

Das alles ist schon ein wenig länger her, aber jetzt hat die Konferenz-Organisation das Video auf JAX TV veröffentlicht. Danke JAX-Team! Hier das Video:


Ich rede zwar viel zu schnell, trotzdem: Viel Spaß damit :-)

Apropos Pecha Kucha I: Auf den XP Days Germany 2009 im November diesen Jahres gibt es gleich 9 Pecha Kucha-Vorträge in drei Blöcken (siehe Programm). Das sind für mich die Perlen dieser Konferenz, also nicht verpassen!

Apropos Pecha Kucha II: Auf der WJAX '09, auch im November diesen Jahres, gibt es wieder eine Pecha Kucha-Session, die ich zusammen mit Martin Heider (Xing, Twitter) auf dem Agile Day halten werde. Thema: "Mein agiler Koffer - Reisetipps mit Pecha Kucha".

Links:

Freitag, 7. August 2009

Dieser Blog ist tot. Ich blogge weiter auf dem «Agile Trail».

Beispiel für Refactoring von Beispiel für Closures

Vor ein paar Wochen hat mein Kollege Stefan Roock (Xing, Twitter) auf seinem Blog von einem "Beispiel für Closure" geschrieben. Wie so oft geht mir beim Lesen (fremden) Codes mein Das-ist-nicht-DRY-Alarm an. So auch bei Stefans Post. Es folgt ein Refactoring.

Stefan will die Laufzeit je Methode für eine Menge von Methoden messen. Die Menge hat er so bechrieben:


Ich konnte in seinem Post die Implementierung von tueA(), tueB() und tueC() nicht finden. Meine Annahme ist, dass sie ein wenig Laufzeit verbrauchen und etwas ausgeben:

Führt man nun die Menge an Methoden aus, so ist das die Ausgabe:

Stefan misst nun die Laufzeit der Methoden, und sein Output sieht (mit meiner Implementierung der Methoden) so aus:

Da ich ein Refactoring beabsichtige, darf sich das Verhalten des Codes, in diesem Falle der Output, nicht ändern.

Aber warum eigentlich Refaktorisieren? Mit Stefans Implementierung als solcher bin ich eigentlich zufrieden. Naja, da kann man noch ein Semikolon weglassen sowie eine Variable duration einführen und im GString hinterm println substituieren. Aber das ist Kleinkram, nebenbei machbar und hier kaum der Rede wert. Stefans Implementierung der Closure:

Nein, es geht mir um den Aufruf von Stefans Lösung (siehe Stefans komplette Lösung). Was muss ich tun, um die Laufzeit einer Menge von Methoden zu messen? Wo ist der Schalter, den ich einschalten muss? Stefan legt diesen Schalter um:

Seht Ihr die Redundanz? Stefan legt nicht einen Schalter um, sondern drei. Drei Mal muss er vor die Methoden timeLogged schreiben, inklusive geschweifte Klammer vor und nach der zu messenden Methode. Das ist nicht DRY.

DRY wäre es, eine Menge von Methoden einzeln zu messen. Das hier schwebt mir vor:

Und das geht. Die Closure-Magie, mit der das geht, hat Ted Naleid (Twitter, LinkedIn) in seinem Blogpost "Groovy closures make unit testing with “soft asserts” simple" ausführlich beschrieben. Hier ist meine Lösung:

Keep on DRYin', Stefan! "Bleib trocken, Stefan!" lag mir auf der Zunge, ging aber hier nun echt nicht... :-)

Links:

Montag, 25. Juni 2007

Dieser Blog ist tot. Ich blogge weiter auf dem «Agile Trail».

Benutze immer die Haustür

Ich lese seit einiger Zeit den Google Testing Blog. Da gab's jetzt schon des öfteren gute bekannte Testentwurfs- und -umgangsmuster im markanten Testing on the Toilette-Stil und ich hätte das bislang ohne Einwände unterstützt - bis heuer: Das TotT-Paper Extracting Methods to Simplify Testing kann ich so nicht unkommentiert lassen.

In diesem Paper wird geraten, eine lange und komplexe Methode durch Extrahieren von Submethoden einfacher testbar zu machen. Das Beispiel ist eine Methode zur Berechnung von Testergebnissen, also wieviele Tests fehlgeschlagen oder erfolgreich gelaufen sind. Dabei macht diese Methode drei Dinge:

  1. Einen Cache nach den Ergebnissen fragen.
  2. Falls 1. zu keinem brauchbaren Ergebnis geführt hat, eine Datenbank nach den Ergebnissen fragen.
  3. Ausrechnen, wieviele Tests fehlgeschlagen sind und wieviele erfolgreich durchgelaufen sind.
Jede dieser Aktionen ist mit einem Kommentar versehen; durch das Extrahieren dieser drei Aktionen in eigenständige Methoden gehen die Kommentare in die Methodennamen auf. So weit, so gut. Aber dann steht da
Now, tests can focus on each individual piece of the original method by testing each extracted method. This has the added benefit of making the code more readable and easier to maintain.
Die Methoden sind nicht auf dem gleichen Abstraktionslevel: Die ersten beiden Methoden delegieren die Aktionen (Ergebnisse aus dem Cache holen, Ergebnisse aus der DB holen) an dafür zuständige Objekte (cache bzw. db). Die dritte Methode dagegen berechnet die Anzahl fehlgeschlagener und erfolgreicher Tests (Filtern der Ergebnismenge nach erfolgreichen Tests, Subtrahieren der erfolgreichen Tests von der Ergebnismenge) und delegiert nicht. Damit befindet sich die dritte Methode auf einem niedrigeren Abstraktionslevel, als die beiden anderen Methoden es sind. Angebracht ist hier, eine Klasse zu extrahieren, die die dritte Methode aufnehmen kann. Eine Delegation auf diese neue Klasse würde alle drei Methoden nivellieren auf ein gleiches Abstraktionslevel.

Dadurch, dass die dritte Methode nichts mehr berechnet, entfällt der Nutzen, diese zu veröffentlichen, um sie separat testen zu können, denn die Logik in der dritten Methode existiert jetzt in der extrahierten Klasse und kann von deren Tests geprüft werden. Das Zusammenspiel der vier Objekte (der getesteten Klasse, dem Cache, der DB und der neuen, extrahierten Klasse) ist das eigentlich Interessante und Testwürdige, entweder per Integrationstest oder Unittest, letzterer mit gemockten Kollaborateuren.

Ich habe derartiges Verhalten schon öfter beobachten können, also das Verbreitern der Schnittstelle einer Klasse, damit sie scheinbar einfacher getestet werden kann. Ich habe nie einen Sinn darin erkannt. Auf der XP 2004 hat auf einer BoF-Session ein Teilnehmer ein Pattern vorgestellt, dass ich online leider nicht wiederfinden kann: Always use the Frontdoor. Eine breitere Schnittstelle ist eben nicht einfacher, sondern schwerer zu warten, muss man sich doch um viel mehr Löcher in den Wänden kümmern, als nur vor der Haustür zu fegen. Und lesbarer ist so eine große Schnittstelle auch nicht, denn der Betrachter muss viel mehr Öffnungen im Auge behalten, als er eigentlich bräuchte. Somit hat weder der Entwickler noch der Benutzer einen Vorteil davon.

"Aber das sind doch nur die Tests..." höre ich schon die ersten Unkenrufe. Ja, genau, es sind die Tests: die ersten Benutzer der Klassen und Objekte; die, die zuerst Feedback geben; die, die zuerst signalisieren, dass ihnen etwas stinkt an dem, was ihnen da vorgesetzt wird. Wenn schon die Klagelaute der Tests nicht beachtet werden, wie sensibilisiert man sich dann für das Knirschen im produktiven Gebälk?

Testgetrieben dürfte diese Art von Code auch schwer entstehen, und so ist das nachträgliche Verlangen nach zugänglicheren privaten Methoden fast immer ein Hinweis auf eine zusätzliche Indirektion, z.B. in Form einer Klassenextraktion, mindestens jedoch ein Hinweis darauf, dass man sich testgetrieben ins Aus manövriert hat. Vorher gibt es aber schon Hinweise bei den Tests, nämlich genau dann, wenn die Fixture breiter wird, wenn also ein Cache und eine DB gemockt werden müssen, um die Berechnung der Ergebnisse zu testen, obwohl eigentlich weder Cache noch DB dafür benutzt werden. So entstehen oftmals mehrere Tests innerhalb einer Testklasse, die das gleiche Subset an Objekten der Fixture benutzen. Das deutet auf eine Klassenextraktion hin, sowohl im Test als auch im produktiven Code!

Zusammenfassend läßt sich schreiben: Methoden extrahieren auf gleichem Abstraktionslevel; Abstraktionslevel gegebenenfalls angleichen durch z.B. Klasse extrahieren; keine privaten Methoden veröffentlichen und damit Löcher in die Schnittstellen hauen; auf die Tests hören; Indirektionen bewußt einbauen; Haustüren mit Tests entwickeln.