Wie Hund und Katze: Legacy Code und Agil

Artikel von Urs Enzler im Sybit Agil Nr. 14  pdf «Wie Hund und Katze: Legacy Code und Agil» als pdf lesen.

Legacy Code verdient viel Geld. Legacy Code steckt in vielen Systemen, die zwar in die Jahre gekommen, aber immer noch erfolgreich sind. Da können noch so viele Softwareentwickler fluchen und sich die Haare raufen, Legacy Code ist die Realität. Aber nicht das Ende…

Legacy Code

Es gibt verschiedene Definitionen[1] für Legacy Code. Allen gemeinsam ist die Unzufriedenheit mit der Änderbarkeit des Codes. Der Code ist unverständlich. Und jede Änderung führt lawinenartig zu verheerenden Folgefehlern. Und manchmal passt das aktuelle Problem auch nicht mehr zur existierenden Lösung.

Juhuu, wir sind agil! Nur die Software lässt sich nicht ändern.

Wir haben agile Prozesse, wir wollen schnell auf veränderte Anforderungen und Marktsituationen reagieren können, wir arbeiten in kurzen Iterationen, mit schnellen Feedbackzyklen. Aber jede Änderung am Code dauert eine gefühlte Ewigkeit. So verkümmert die ganze Agilität.

Dann schreiben wir es halt neu!

«Das System ist so verkorkst, dass wir es neu schreiben müssen!» hallt dann gerne mal durch die Entwicklerbüros. Allerdings ist das in den seltensten Fällen eine gute Lösung. Ein komplettes Neuschreiben birgt grosse Risiken und dauert sehr lange. Woher wissen wir, dass das System danach wirklich besser ist und nicht nur anders? Macht es überhaupt noch das, was es soll? Haben wir noch Kunden, wenn wir ein Jahr lang keine neue Funktionalität bringen? Wenn diese Risiken tragbar sind, dann ist ein Neubau die schnellste Art von Alt zu Neu. Ansonsten bietet ein schrittweiser Umbau tiefere Risiken und das System ist immer lauffähig.

Holt mich hier raus

Meist sind die Hauptursachen für schwer änderbaren Code fehlende Tests, eine monolithische Architektur und starke Kopplung im Design. Darum legen wir beim Umbau die Schwerpunkte auf diese drei Aspekte. Tests bieten ein Sicherheitsnetz um Änderungen ohne ungewünschte Nebeneffekte durchführen zu können. Der Umbau eines Legacy-Systems ohne Tests ist sehr fehleranfällig, weil der Code nur schwer verständlich ist. Auswirkungen von Änderungen sind oft schwer abschätzbar.

Idealerweise können diese Tests automatisch ausgeführt werden. So dienen sie den Entwicklern als Navigationsgerät und können jederzeit Rückmeldung geben, ob der Weg der richtige ist. Die damit entstehenden Regressionstests behalten ihren Nutzen weit über den Umbau hinaus.

Eine Architektur, welche das System aus einzelnen grösstenteils autonomen Bausteinen zusammensetzt, bietet mehr Flexibilität, klare Sollbruchstellen für Veränderung und eine leichtere Verständlichkeit des Gesamtsystems. Darum liegt der Schwerpunkt zu Beginn des Umbaus auf der Aufteilung des Gesamtsystems in überschaubare Bausteine.

Die Entkopplung von Komponenten im Design führt ebenfalls zu einer grösseren Flexibilität und einfacheren Abhängigkeiten und damit zu leichterer Veränderbarkeit. Einzelne Komponenten können dann mit minimalen Auswirkungen auf den Rest des Systems ausgetauscht werden. Dies ermöglicht ein schrittweises Vorgehen.

7Schritte

Abb. 1: Die sieben Schritte aus dem Sumpf: Aufteilung und Verbesserung des Systems in Einzelteilen.

7 Schritte aus dem Sumpf

Der Grundsatz ist: Das System ist immer lauffähig. Wir ändern das System in kleinen Schritten. Von einem lauffähigen Zustand in einen neuen lauffähigen Zustand. So können die Verbesserungen auch Stück für Stück in Sprints aufgeteilt werden.

Zuerst sorgen wir für ein Sicherheitsnetz aus Tests, welche sicherstellen, dass sich das System nach dem Umbau noch gleich verhält. Dann splitten wir das Gesamtsystem in überschaubare Bausteine (Komponenten). Anschliessend verbessern wir diese  Einzelteile. Die folgenden sieben Schritte helfen beim Umbau eines Legacy-Systems hin zu mehr Verständlichkeit und Flexibilität.

1. Features identifizieren

Identifizieren Sie die existierenden Features in der Software und priorisieren Sie diese anhand deren Relevanz für die zukünftige Weiterentwicklung (Wahrscheinlichkeit einer Änderung, Risiko einer Änderung). Damit eine Priorisierung überhaupt möglich ist braucht Ihr Team eine Vision: Wohin soll sich das System in der Zukunft bewegen? Ohne Vision ist es ein Blindflug. Beginnen Sie mit dem höchsten priorisierten Feature.

2. Schnittstellen an Systemgrenzen einführen

Refaktorisieren Sie die Systemgrenze des Features mit Hilfe von Schnittstellen so um, dass Sie die Umgebung mit Test Doubles simulieren können.

3. Feature-Akzeptanztest schreiben

Decken Sie das Feature mit Akzeptanztests ab, um ein Sicherheitsnetz für Änderungen zu erhalten.

4. Komponenten identifizieren

Identifizieren Sie die Komponenten, welche das Feature ermöglichen. Priorisieren Sie die Komponenten an Hand der Relevanz für die zukünftige Weiterentwicklung.

5. Schnittstellen zwischen den Komponenten refaktorisieren

Refaktorisieren Sie die Schnittstellen zwischen den Komponenten, sodass jede Komponente in Isolation von ihrer Umgebung getestet werden kann.

6. Komponenten-Akzeptanztests schreiben

Decken Sie die Funktionalität der Komponente mit Akzeptanztests ab.

7. Refaktorisieren, Neuschreiben oder Behalten

Entscheiden Sie für jede Komponente, ob diese refaktorisiert, neu geschrieben oder behalten werden soll.

  1. Refaktorisieren:
    Redesignen Sie die Klassen innerhalb der Komponente und refaktorisieren Sie Schritt für Schritt vom alten zum neuen Design. Fügen Sie Unit Tests für
    jede neue oder umgebaute Klasse hinzu. Refaktoring macht es unwahrscheinlich, Funktionalität zu verlieren.
  2. Neu schreiben:
    Nutzen Sie Akzeptanz-Test-getriebene Entwicklung[2] (ATDD) und Test-getriebene Entwicklung[3](TDD), um die Komponente neu zu schreiben. So haben Sie gleich die Tests für einen späteren Umbau mit dabei. Schreiben Sie Komponenten nur dann komplett neu, wenn Refaktoring nicht möglich ist, weil der Ist-Zustand nicht sinnvoll in Schritten zum Soll-Zustand überführbar ist.
  3. Behalten:
    Wenn Sie nur wenige Änderungen in der Komponente erwarten und die Komponente hatte wenige Fehler in der Vergangenheit, dann behalten Sie die Komponente wie sie ist. Wiederholen Sie die Schritte für die nächste Komponente, das nächste Feature.

Fazit

Legacy Code ist ein grosser Bremsklotz für agile Teams, weil Änderungen nur langsam umgesetzt werden können. Nur durch ein stetiges Aufräumen und Verbessern lässt sich Stück für Stück ein big-ball-of-mud[4] in ein verständliches und flexibles System überführen. Aber genau diese Verständlichkeit und Flexibilität brauchen agile Teams, um mit den sich ändernden Anforderungen mithalten zu können und um schnelles Feedback zu ermöglichen. Nur so können die Versprechen des Agilen Manifests eingelöst werden.

Quellenverzeichnis

[1] Definitionen Legacy Code – http://en.wikipedia.org/wiki/Legacy_code
[2] Acceptance Test Driven Development – www.planetgeek.ch/2012/06/12/acceptance-test-driven-development/
[3] Kent Beck, Test-Driven Development – By Example, Addison-Wesley Professional, 2003
[4] Big ball of mud – www.laputan.org/mud

This entry was posted in Agile, Artikel, Clean Code and tagged , , , . Bookmark the permalink.

Leave a Reply

Your email address will not be published. Required fields are marked *