Von Johannes Seitz
Voraussichtliche Lesedauer: 12 Minuten
IT-Sanierung: Große Änderungen strukturieren
Im zweiten Teil der Artikelserie zur IT-Sanierung (zum Artikel von Johannes Seitz geht es hier) ging es darum, große Änderungen in kleinen Schritten durchzuführen. Dazu wurden Techniken gezeigt, wie man während der Durchführung auslieferungsfähig bleibt. Wie aber kommt man zu diesen kleinen Schritten? Wie zerlegen wir unser großes Ziel in mehrere kleine? In welcher Reihenfolge…
Im zweiten Teil der Artikelserie zur IT-Sanierung (zum Artikel von Johannes Seitz geht es hier) ging es darum, große Änderungen in kleinen Schritten durchzuführen. Dazu wurden Techniken gezeigt, wie man während der Durchführung auslieferungsfähig bleibt.
Wie aber kommt man zu diesen kleinen Schritten? Wie zerlegen wir unser großes Ziel in mehrere kleine? In welcher Reihenfolge führen wir die Schritte dann durch? Auch hierbei kann man sich leicht verzetteln. Zur Strukturierung hilft die sogenannte Mikado-Methode.
How about a nice game of… mikado?
Wer das namensgebende Geschicklichkeitsspiel Mikado nicht kennt: Ziel ist es, am meisten Holzstäbchen zu sammeln. Die Teilnehmer nehmen reihum Stäbchen von einem chaotischen Stapel. Wenn man mit dem berührten Stäbchen weitere in Bewegung setzt, ist der nächste Spieler dran, ansonsten darf man weiter Stäbchen aus dem Stapel ziehen.
Übertragen auf Code-Änderungen heißt das: Unser Ziel ist es, eine größere Änderung durchzuführen. Wenn wir sie naiv und direkt durchführen, merken wir, welche anderen Code-Stücke „wackeln“ – also von der Änderung betroffen sein werden. Diese Stellen notieren wir uns, da sie anscheinend Vorbedingungen für unsere große Änderung darstellen. Unsere ursprüngliche Änderungen verwerfen wir vorerst, da noch nicht alle Vorbedingungen erfüllt sind.
Nun gehen wir eine der Vorbedingungen an, versuchen uns also an einem anderen Mikado-Stäbchen. Falls auch dieses nicht direkt herauszuziehen ist, notieren wir auch hier eine Erkenntnis zu den Vorbedingungen. Wir ordnen die Abhängigkeiten in einer baumartigen Struktur an. Unser Ziel ist es, an einem Endpunkt des Baumes anzukommen, einem “Blatt”. Ein Blatt hat keine Vorbedingung und kann gefahrlos durchgeführt werden. In der Mikado-Analogie handelt es sich um ein Stäbchen, dass oben auf dem Stapel liegt: Man kann es gefahrlos anfassen.
Beispiel: Kohäsion erhöhen
Was müssen wir nun konkret nach der Mikado-Methode machen? Dies hängt natürlich von der Programmiersprache und dem sonstigen technologischen Kontext der Code-Basis ab. Nehmen wir der Einfachheit halber Java als weit verbreitete Sprache, dazu diese kleine Methode als Ausgangspunkt:
private String createRentalRecord() { String record = "Rental Record for " + getName() + "n"; double totalAmount = 0; for (Rental rental : rentals) { double amount = rental.determineAmount(); totalAmount += amount; record += "t" + rental + "t" + String.valueOf(amount) + "n"; } record += "You owed " + String.valueOf(totalAmount) + "n"; return record; }
Inhaltlich kümmert sich die Methode darum, einen Bericht über den Vorgang “Ausleihen” und die damit verbundenen Gesamt-Leihkosten zu erstellen. Wem das Beispiel bekannt vorkommt: Es ist Teil der Videostore-Codebasis.
Aus Sanierungssicht hat die Methode verschiedene Probleme, das gewichtigste scheint die schwache Kohäsion des Codes: Die Methode vereint in sich die beiden Themen „Ausgabe-String des Berichts erstellen“ und „Berechnung der Gesamt-Leihkosten“. Dies erschwert die Wartung des Codes, da er durch die Mischung unnötig schwer zu verstehen ist und aus ganz unterschiedlichen Gründen geändert werden könnte.
Das Designprinzip Separation of Concerns weist auf eine bessere Implementierung der Methode hin. Als Ziel können wir uns diese neue Struktur der bisherigen Methode vornehmen:
private String createRentalRecord() { double totalAmount = determineTotalAmount(); return createRentalRecord(totalAmount); }
Dazu benötigen wir zwei Hilfsmethoden, die sich dann gemäß dem Designprinzip separat um Kostenberechnung und Berichterstellung kümmern:
private double determineTotalAmount() { ... } private String createRentalRecord(double totalAmount) { ... }
Nun könnten wir mit dem Ziel im Kopf drauf los programmieren, laufen aber Gefahr uns zu verzetteln. Ganz im Sinne des „Plädoyers für Babyschritte“ aus dem zweiten Teil der Artikelserie sollten wir zuerst diese geplante Änderung strukturieren, etwa mit der Mikado-Methode.
Schritt für Schritt
Unser Ziel notieren wir uns als untersten Knoten eines Diagramms zum Beispiel auf einem Blatt Papier:
Anschließend versuchen wir, diesen Knoten abzuarbeiten, merken aber direkt, dass zur Trennung mehrere, vermutlich nicht-triviale Schritte nötig sind – die Themen sind im Code noch zu sehr miteinander vermischt.
Würde der Wert totalAmount
außerhalb der Methode berechnet, wäre die Mischung zum großen Teil aufgehoben. Gemäß der Mikado-Methode schreiben wir uns diese Vorbedingung als neuen Knoten auf:
Nun konzentrieren wir uns auf diesen neuen Knoten – er stellt momentan nach der Mikado-Analogie ein Stäbchen dar, das wir aus dem Stapel gefahrlos(er) herausziehen können.
Wir ersetzen also die Zeile
double totalAmount = 0;
durch einen Methodenaufruf
double totalAmount = determineTotalAmount();
und implementieren die neue Methode:
private double determineTotalAmount() { int totalAmount = 0; for (Rental rental : rentals) { totalAmount += rental.determineAmount(); } return totalAmount; }
Nach dieser Änderung stellen wir allerdings fest, dass die berechneten Kosten falsch, nämlich zu hoch sind: Innerhalb der alten createRentalRecord
-Methode wird totalAmount
weiterhin aufsummiert. Wir haben uns verzettelt, da wir in einem Schritt die neue Methode erstellt und die alte geändert haben.
Alles auf Anfang
Wir haben offensichtlich bei der experimentellen Umsetzung dieses Knotens einen zu großen Schritt gemacht. Die falsch berechneten Kosten geben uns das Feedback, dass wir noch nicht genug über das System wussten.
Bei diesem kleinen Beispiel erscheint das fehlende Wissen vielleicht trivial, da nur eine Zeile Code in der Schleife gelöscht werden müsste. In realen Szenarien ist es aber entscheidend, durch solche erfolgreichen oder fehlschlagenden Versuche Wissen zu erlangen. Michael Feathers nennt diese Technik des Ausprobierens in seinem Standardwerk zur Sanierung auch passenderweise „Scratch Refactoring“, da der restrukturierte Code anschließend weggeschmissen wird.
Wir lernen also aus diesem Feedback und trennen sauber zwischen „alte Methode ändern“ sowie „neue Methode erstellen“. Dazu ergänzen wir letzteres als weitere Voraussetzung im Mikado-Diagramm:
Bevor wir uns nun an den neuen Knoten machen, sollten wir wieder zu einem sicheren, also fehlerfreien Zustand zurückkehren. Das bedeutet auch nach der Mikado-Methode: Alle bisherigen Änderungen rückgängig machen. Dieser ungewöhnliche Schritt dient nicht dem Bestrafen von Fehlern, sondern einer strukturierten Vorgehensweise, wie oben begründet. Mit einer Versionskontrolle wie git im Hintergrund ist das auch schnell getan, mit einem
git checkout -- .
Für den nächsten Schritt wenden wir uns wieder den Blättern des Mikado-Diagramms zu. Wir mussten bisher keine Verzweigungen erstellen, da wir immer nur genau eine Voraussetzung für einen Schritt identifizert haben. Mehrere Voraussetzungen sind prinzipiell möglich, und so könnte man jetzt beliebig aus mehreren Blättern auswählen. In unserem Fall bleibt nur der eben erstellte Knoten als nächster Schritt. Wir erzeugen also die neue Methode, lassen die ursprüngliche aber unverändert:
private String createRentalRecord() { String record = "Rental Record for " + getName() + "n"; double totalAmount = 0; for (Rental rental : rentals) { double amount = rental.determineAmount(); totalAmount += amount; record += "t" + rental + "t" + String.valueOf(amount) + "n"; } ... } private double determineTotalAmount() { double totalAmount = 0; for (Rental rental : rentals) { totalAmount += rental.determineAmount(); } return totalAmount; }
Dies erzeugt keine Fehler, da die neue Methode ja noch gar nicht verwendet wird. Der Schritt hat also eigentlich den Code verschlechtert, bringt uns aber unserem ursprünglichen Ziel näher. Gemäß der Mikado-Methode markieren wir unseren Knoten im Diagramm als erledigt und checken die Änderung ein.
Nun ist der Knoten „totalAmount außerhalb erstellen“ im Diagramm ein Blatt geworden, hier arbeiten wir weiter.
totalAmount
wird aktuell noch innerhalb der alten Methode an zwei Stellen verwendet: zum einen wird der Wert initial auf 0 gesetzt, zum anderen innerhalb der Schleife aufaddiert. Das Ziel des Knotens lässt sich also einfach umsetzen – Zeile in der Schleife löschen und die Variable mit dem Rückgabewert der neuen Methode initialisieren:
private String createRentalRecord() { String record = "Rental Record for " + getName() + "n"; double totalAmount = determineTotalAmount(); for (Rental rental : rentals) { double amount = rental.determineAmount(); record += "t" + rental + "t" + String.valueOf(amount) + "n"; } record += "You owed " + String.valueOf(totalAmount) + "n"; return record; }
Diese Änderung ist anscheinend erfolgreich und erzeugt anscheinend keinen Fehler. Entsprechend checken wir sie ein und haken den Knoten ab.
Damit kommen wir zu unserem ursprünglichen Ziel zurück: der Trennung von Kostenberechnung und Berichterstellung. Formal sind beide Themen nun im Code getrennt, wir könnten also diese Umstrukturierung beenden.
Unsere initiale Idee weiter oben beinhaltete allerdings auch, dass die createRentalRecord
-Methode nur noch beide Themen orchestriert, die Berichterstellung also auch ausgelagert ist – wir fügen also einen weiteren Knoten ins Diagramm ein:
Die Abarbeitung dieses Knotens sei als Übung dem Leser überlassen 😉
Alternativer Anfang
Am Anfang der Umstrukturierung war uns die Trennung in zwei neue Methoden bewusst. Gemäß der Mikado-Methode hätten wir also auch zunächst dieses Diagramm zeichnen und uns dann dem rechten Blatt, also der Berichterstellung widmen können:
So kommt man mit der Methode je nach Priorität zu unterschiedlichen Schritten für die der Strukturierung.
Literaturhinweis
Die Mikado-Methode wurde ausführlich in einem Buch von Ola Ellnestam und Daniel Brolund beschrieben.