Komplexe Refactorings mit der Mikado-Methode durchführen

Das Fehlen automatisierter Tests ist nur ein Stolperstein in Legacy-Projekten. Viel größer ist die Herausforderung, wenn selbst mit ihnen keine klare Vorstellung davon existiert, wie man komplexe Refactorings überhaupt angehen könnte. Oft fehlt ein strukturierter Prozess.

Know-how  –  4 Kommentare
Komplexe Refactorings mit der Mikado-Methode durchführen

Damit ein Team ein Projekt sinnvoll weiterentwickeln kann, ist ein grundlegendes Verständnis seines Aufbaus und der implementierten Funktionen nötig. Refactorings können nicht nur dabei helfen, ein solches zu erlangen, sondern sich auch positiv auf Softwareeigenschaften wie die Performance auswirken. Ohne passende Strategie kann es jedoch sein, dass Erfolge selbst nach längeren Versuchen, den Code unter Kontrolle zu bringen, ausbleiben. Eine Folge ist häufig, dass Teams komplexe Refactorings nicht angehen oder abbrechen, um die Funktionsfähigkeit des Programms nicht zu gefährden. Allerdings tragen schon einfache Überarbeitungen dazu bei, den Code verständlicher und damit wandelbarer zu gestalten. Statt alles hinzuwerfen, sollten Entwickler daher zumindest bei der Codeuntersuchung erlangte Erkenntnisse im Code festhalten.

Allgemein gibt es zwei Gründe, komplexe Refactorings anzusetzen: Entweder soll das Team ein neues Feature ergänzen beziehungsweise ein altes modifizieren oder es muss einen Fehler beheben. Da Auftraggeber von der Annahme ausgehen, dass ihre Software dem Stand der Technik entsprechend test- und wandelbar ist, lohnt sich ein umfassendes Refactoring aus wirtschaftlicher Sicht nur, wenn solche Änderungen anstehen. Anders wird sich beispielsweise das Ergänzen von Prüfmaßnahmen nicht abrechnen lassen.

Der Überarbeitungsprozess beginnt typischerweise mit einer Änderung am Code. Sie löst eventuell an anderer Stelle Fehlfunktionen aus. Diese müssen die Entwickler beheben, wobei nicht auszuschließen ist, dass auch die Lösung Fehler nach sich zieht, denen es entgegenzuwirken gilt. Die Schritte wiederholen sich so oft, bis keine Schwierigkeiten mehr auftreten und die gewünschte Änderung anstandslos funktioniert.

Da sich der Vorgang über Wochen hinziehen kann, arbeitet das Team meist auf einem Branch der Versionskontrolle. So stören die Refactoring-Maßnahmen nicht die Weiterentwicklung, die gleichzeitig auf dem Trunk oder einem weiteren Branch stattfindet. Allerdings entstehen beim späteren Merge der Änderungen neue Herausforderungen: Da das Zusammenfügen aller Modifikationen teilweise nicht ohne weiteres möglich ist, führt das Refactoring-Team einzelne Schritte erneut aus, scheint solch ein Vorgehen doch häufig einfacher als ein Merge. Kein Wunder, dass Entwickler von solchen Refactorings lieber die Finger lassen.

Mikado für Entwickler

Ola Ellnestam und Daniel Brolund haben in ihrem Buch "The Mikado Method" die Mikado-Methode vorgestellt, um Refactorings den Schrecken zu nehmen. Am Anfang des Prozesses steht die angstrebte Änderung, die die Entwickler im sogenannten Mikado-Graph notieren (dazu später mehr). Sollten nach dem Umsetzen Fehler auftreten, hält das Team sie als Erkenntnisgewinn im Graph fest, statt sie sofort zu beheben. Anschließend versetzt es den Code mit der Versionskontrolle in den Ursprungszustand, wodurch erneut ein definierter Ausgangspunkt vorliegt. Mit ihm als Grundlage geht das Team den nächsten Lösungsversuch an. Anschließend bemerkte Fehler notiert es erneut und setzt wieder alles zurück. Der Prozess ist beendet, sobald das Mikado-Ziel erreicht ist.

Nun versuchen sich die Entwickler an der Lösung eines der notierten Probleme. Neue Herausforderungen hält es im Graphen fest. Am Ende des Prozesses steht eine Visualisierung der für die Änderung geeigneten Vorgehensweise zur Verfügung (siehe Abb. 1).

Nach und nach entsteht ein Graph, an dem sich eine geeignete Vorgehensweise ablesen lässt (Abb. 1).


Durch die versuchten Änderungen und den Mikado-Graph ist der Refactoring-Prozess in kleine Schritte zerlegt. Beim Erarbeiten erlangte Erkenntnisse lassen sich in der Regel nicht durch eine Analyse gewinnen, da sich einige Details erst während des Versuchs offenbaren.

Sonderheft "Altlasten im Griff"

Mehr Artikel zum Thema Legacy-Code sind im Sonderheft iX Developer 01/2017 zu finden, dass sich unter anderem im heise Shop erwerben lässt.

Beim Refactoring von Legacy-Code handelt es sich folglich um eine komplexe Aufgabe. Es lässt sich nicht allein durch Nachdenken feststellen, ob ein erarbeiteter Lösungsansatz tatsächlich die gewünschte Änderung umsetzt. Erst ein Experiment klärt, ob sich das Verfahren eignet.

Ein weiterer Vorteil des Mikado-Graphen liegt in der Visualisierung. Mit ihm lässt sich im Team viel besser besprechen, wer welche Aufgaben übernimmt, wann einzelne Schritte durchzuführen sind, et cetera. Ferner können die Entwickler das ehemals große Refactoring, das sich nur "ganz oder gar nicht" angehen ließ, nun über einen längeren Zeitraum strecken. Mit seinen Blättern bietet der Graph eine Hilfestellung beim Festlegen der Reihenfolge der Aufgabe. Dadurch ist es möglich, einzelne Refactoringschritte konkret einzuplanen und mit der Weiterentwicklung des Systems zu verzahnen.

Umsetzung

Umsetzen des Refactorings

Alle Probleme, die sich beim experimentellen Umsetzen der gestellten Aufgabe gezeigt haben, sind als Vorbedingungen für das Mikado-Ziel in den Graphen übernommen worden. Da die einzelnen Vorbedingungen aufeinander aufbauen, sich also in Abhängigkeit voneinander befinden, bildet der Graph auch sie ab. Es handelt sich folglich um einen gerichteten Graph, an dessen Wurzel das Mikado-Ziel steht, das durch die einzelnen Refactoring-Maßnahmen erreicht werden soll. Das Ziel kann so etwas sein wie "Speichern der Daten in der Cloud statt lokal" oder "Fehler beim Neuanlegen eines Produkts beheben".

Das Refactoring erfolgt in der Gegenrichtung der Abhängigkeiten, von den Blättern zur Wurzel. Entwickler arbeiten die einzelnen Anforderungen ab und übertragen die Ergebnisse jeweils in die Versionskontrolle. Alle Schritte erfolgen auf dem Trunk -- ein Branch ist nicht erforderlich, da die Änderungen nicht lange dauern und das Ergebnis potenziell auslieferbar sein soll. Fehlen automatisierte Tests, müssen die Entwickler sie im Zuge des Refactoring ergänzen. So lässt sich außerdem vor dem Commit in die Versionskontrolle sicherstellen, dass die Änderung keine Defekte erzeugt hat.

Abbildung 2 gibt einen Überblick über den Ablauf der Mikado-Methode. Zu Beginn notiert ein Verantwortlicher das Ziel der Änderung, etwa ein neues Feature oder das Beseitigen eines Bugs. Grundlage hierfür ist die Anforderung des Kunden. Im Anschluss versuchen die Entwickler, das Ziel zu erreichen. Dabei geht es nicht darum, eine endgültige Implementierung vorzunehmen, sondern herauszufinden, was der Umsetzung im Weg steht. Daher ist es in Ordnung, das eine oder andere Prinzip sauberen Codes zu verletzen, da der Quelltext ohnehin wieder verworfen wird. Wichtig ist, nicht wild herumzuprobieren, sondern immer wieder zu prüfen, ob der aktuelle Stand einen Punkt darstellt, der einen Erkenntnisgewinn für den Mikado-Graph bietet. Sollte sich etwa das System plötzlich nicht mehr wie gewünscht verhalten oder die Änderung viele Compilerfehler verursachen, sind das sichere Anzeichen für einen festzuhaltenden Schritt.

Die Schritte der Mikado-Methode lassen sich durch ein Diagramm schnell veranschaulichen (Abb. 2).


Auf Fehlersuche

Für das Aufspüren von Fehlern sind automatisierte Tests empfehlenswert. Sollten keine vorhanden sein, bleibt das manuelle Vorgehen. In dem Fall ist bei jeder Verhaltensänderung davon auszugehen, dass etwas kaputt gegangen ist. Darüber hinaus lassen sich beim Experimentieren Schwachstellen im Code aufspüren. Typische Herausforderungen sind Abhängigkeiten oder das Vermischen von Aspekten. Um weiterzukommen, wäre also zu überlegen, wie sich Abhängigkeiten auflösen oder die Aspekte trennen lassen, damit nur noch eine Methode oder Klasse dafür zuständig ist. An der Stelle sollte das Team die Herausforderung als Vorbedingung im Mikado-Graph notieren und das Ergebnis der Änderung als ein Problem betrachten.

Sofern kein Fehler aufgetreten ist, muss geprüft werden, ob die Änderung sinnvoll war. Lässt sich die Frage mit ja beantworten, kann man den neuen Stand der Versionskontrolle übergeben. Unfertige Zwischenschritte hingegen sollten die Entwickler nicht einchecken. Der Sinn lässt sich typischerweise erst beurteilen, wenn das blattweise Umsetzen der Erkenntnisse des Mikado-Graphs begonnen hat. Änderungen sind durch automatisierte Tests abzusichern.

Ist eine Änderung implementiert und sinnvoll, überträgt sie das Team in die Versionskontrolle. Im Mikado-Graph kann es die Vorbedingung nun abhaken. Nach jedem Commit müssen die Entwickler entscheiden, ob das Mikado-Ziel erreicht ist. Ist das der Fall, ist der Refactoring-Prozess für die aktuelle Aufgabe abgeschlossen.

Wenn es Fehler gab, das Programm etwa nach dem Entfernen einer Abhängigkeit nicht mehr kompiliert, ist eine Lösung zu suchen und im Graphen zu vermerken. Dabei muss das Team darauf achten, dass die notierte Vorbedingung nicht zu umfangreich ist. Komplexere Schritte sollten aufgeteilt werden. Wichtig ist, die Abhängigkeiten der einzelnen Schritte korrekt darzustellen.

Alles auf Anfang

Um sicherzustellen, dass nach einer Änderung probierte Schritte auf einem bekannten Zustand stattfinden, sollten vor jedem neuen Experiment alle Änderungen verworfen werden. Würde das Team auf den Änderungen aufbauen, fiele es schwer, den Überblick zu behalten, was ja das Ziel des Mikado-Graphen ist. Auf einem unbekannten Stand ist es schwieriger herauszufinden, welche Manipulation welche Verhaltensänderung bewirkt hat und ob das System noch korrekt funktioniert. Letztlich bliebe an dem Punkt nichts anderes übrig, als alle Änderungen ohne Gewinn rückgängig zu machen.

Ist ein Blatt abgearbeitet, suchen die Entwickler im Mikado-Graph eine neue Vorbedingung, die sie im nächsten Schritt umsetzen. Dabei müssen sie darauf achten, nur Blätter zu wählen, da diese nicht von anderen Vorbedingungen abhängen.

Fazit

Mit der Mikado-Methode steht eine einfache und gleichzeitig leistungsfähige Herangehensweise für komplexe Refactorings zur Verfügung. Die Herausforderung besteht weniger darin, die Methode zu verstehen, sondern sie konsequent anzuwenden. Die größte Schwierigkeit dürfte sein, beim naiven Ändern rechtzeitig anzuhalten, die Erkenntnis als Vorbedingung im Mikado-Graph zu notieren und dann konsequent ein Revert in der Versionskontrolle vorzunehmen. Für das Entwickeln einer entsprechende Disziplin, findet sich im Internet allerlei Übungsmaterial. Für erste Schritte eignen sich Open-Source-Projekte: Sobald sich die Idee für ein neues Feature entwickelt oder ein Fehler auftaucht, lässt sich die Methode gut ausprobieren. (jul)

Stefan Lieser
ist Informatiker aus Leidenschaft und arbeitet als Trainer/Berater/Autor. Gemeinsam mit Ralf Westphal hat er die Clean Code Developer Initiative ins Leben gerufen. Außerdem schreibt er rund um das Thema Refactoring von Legacy-Code.