© Excellent backgrounds/Shutterstock.com
Monolithische Legacy-Systeme warten

Langlebigkeitstherapien


Angemessene Strukturen und gut voneinander abgegrenzte einzelne Komponenten sind bestimmt das wichtigste Merkmal eines gut wartbaren und langlebigen Softwaresystems. Bei schlecht strukturiertem Code tauchen immer wieder die gleichen Schwierigkeiten auf. Aber es gibt natürlich verschiedene Möglichkeiten, diese in den Griff zu bekommen. Microservices sind dabei nur eine Antwort von vielen.

Lassen Sie mich mit einem Szenario beginnen, das ich im Zuge meiner Coachingtätigkeit so oder so ähnlich schon einige Male erlebt habe: Im Zuge der Einführung einer neuen Version eines Softwarepakets kommt es zu Problemen. Manchmal in Teilen der Software, die gar nicht geändert wurden. Das Management ist sehr unglücklich über diese Situation und verlangt nach mehr Unit-Tests in der Hoffnung, damit die unerwünschten Seiteneffekte der Änderungen in den Griff zu bekommen. Das Team tut sich aber mit dieser Art der Testautomatisierung aus irgendeinem Grund schwer.

Die Probleme, die man in so einer Situation mit der Codebasis hat, lassen sich meiner Erfahrung nach nicht mit Technik allein lösen. Im Zuge einer Verbesserung ist auch ein kultureller Wandel im Entwicklerteam nötig. Wie schwierig so etwas sein kann, darf dabei nicht unterschätzt werden. Auch wenn die bestehende Entwicklermannschaft den Legacy-Code nur geerbt hat und es gerne schon immer anders gemacht hätte. Am besten geht der kulturelle Wandel im Team dabei Schritt für Schritt mit der Verbesserung der Codebasis einher. Zur Unterstützung möchte ich an dieser Stelle eine Lanze für die Clean-Code-Developer-Initiative von Ralf Westphal und Stefan Lieser brechen [1]. Um ein neues Wertesystem zu verinnerlichen, genügt es nämlich nicht, ein paar Regeln auswendig zu lernen. Deshalb ist es am besten, die Entwicklung in einzelne Stufen zu unterteilen, die von den Entwicklern eine nach der anderen erklommen werden. Jeder Entwicklungsstufe ist dabei eine Farbe zugeordnet. Wer möchte, trägt ein Armband in der jeweiligen Farbe, die dem Grad entspricht, an dem er gerade arbeitet. An jeder Stufe sollte man dabei für mindestens drei Wochen arbeiten, um die diversen Prinzipien auch wirklich zu verinnerlichen. Wer am ersten, roten Grad arbeitet, beschäftigt sich dabei beispielsweise mit DRY (Don’t repeat yourself) und KISS (Keep it simple and stupid). In dieser ersten Phase lernt man bereits auch Uncle Bobs Developer Boy Scout Rule [2] kennen, die man zu Beginn jeder kontinuierlichen Verbesserung verinnerlicht haben sollte: „Always leave the code behind in a better state than you found it.“

Das Dilemma mit den Unit-Tests

Aber warum ist es so schwierig, für Legacy-Code Unit-Tests zu schreiben? Zu diesem Problem kommt es, weil sich einzelne Teile des Codes (Klassen, Packages und Module) nicht sauber durch ihre ein- und ausgehenden Schnittstellen vom Rest des Systems abgrenzen. Das macht also nicht nur die Wartung der Codebasis generell schwierig, sondern vermindert obendrein noch die Testbarkeit des Systems. Falls Ihnen das bekannt vorkommt, befinden Sie sich vermutlich in einem sogenannten Legacy-Code-Dilemma. Ein Dilemma ist es, weil einerseits ein Refactoring dringend Not tut, wofür es aber andererseits an der Absicherung durch automatisierte Unit-Tests mangelt. Nur durch automatisierte Tests kann man mit einer Schritt-für-Schritt-Verbesserung der Codebasis beginnen, da man dann jederzeit auf Knopfdruck ermitteln kann, ob es durch die Änderungen nicht zu unerwünschten Seiteneffekten gekommen ist. Dadurch, dass der Code aber nicht strukturiert ist, ist es aber gar nicht erst möglich, Unit-Tests zu schreiben. Der einfachste Ausweg aus diesem Dilemma ist der, der auch im zweiten, dem orangenen Grad, der Clean-Code-Developer-Initiative empfohlen wird: Zuerst automatisierte Systemtests erstellen, die das System in seiner Gesamtheit auf Fehlerlosigkeit überprüfen. Das kann beispielsweise mit Selenium [3] passieren. Sobald Sie solche Systemtests erst einmal erstellt haben, können Sie beruhigt das Refactoring beginnen. Später können Sie sich dann überlegen, ob das Erstellen von Unit-Tests überhaupt noch notwendig ist. Früher ging man davon aus, dass jedes System in erster Linie durch Unit-Tests abgesichert sein sollte, weil System- und Integrationstests teuer in der Ausführung waren. Heute gehen die Meinungen dazu auseinander. Es ist unter anderem durch Technologien wie Docker genauso gut möglich, die Testautomatisierung in erster Linie auf Ebene des gesamten Systems auszuführen und nur in Ausnahmefällen auch auf Unit-Tests zu setzen (Abb. 1).

dowalil_legacy_1.tif_fmt1.jpgAbb. 1: Es ist möglich, nur in Ausnahmefällen auf Unit-Tests zu setzen

Kennzahlen nutzen: Software Package Metrics

Bei unklaren Strukturen im Code kommt es fast immer auch zu einer hohen Zahl an Abhängigkeiten zwischen den einzelnen Komponenten und dadurch immer wieder zu unerwünschten Seiteneffekten bei Änderungen. Diese potenziellen Probleme lassen sich gut in Kennzahlen ausdrücken. Werfen Sie bitte einen Blick auf Abbildung 2. Was Sie dort sehen, ist ein einfaches System, das aus vier sauber voneinander abgegrenzten Komponenten besteht.

dowalil_legacy_2.tif_fmt1.jpgAbb. 2: Beispielsystem aus sauber voneinander abgegrenzten Komponenten

Für jede der Komponenten wurden drei Kennzahlen berechnet, die alle aus der Familie der Software Package Metrics von Robert C. Martin [4] sind: Efferent Coupling (Ce), Afferent Coupling (Ca) und Instability (I). Coupling, auch bekannt als Outgoing Dependencies, gibt die Anzahl der Komponenten an, die dieser Komponente bekannt sind oder von der sie abhängig ist. Es beantwortet also die Frage: „In wie vielen anderen Komponenten oder Bausteinen können Änderungen potenziell Auswirkungen auf diese Komponente haben?“ Afferent Coupling, auch bekannt als Incoming Dependencies, gibt wiederum an, wie vielen anderen Komponenten diese Komponente bekannt ist. Es geht dabei also darum, auf wie viele andere Komponenten es potenziell Auswirkungen gibt, wenn diese Komponente geändert wird. Und Instability gibt das Verhältnis der ausgehenden Abhängigkeiten (Ce) zu allen Abhängigkeiten an, also ein- und ausgehende Ce plus Ca. Sie wird wie folgt berechnet: Ce / (Ce + Ca). Ein Wert nahe 1 bedeutet demnach, dass eine Komponente besonders anfällig für Änderungen anderer Komponenten ist, während ein Wert bei 0 bed...

Neugierig geworden? Wir haben diese Angebote für dich:

Angebote für Gewinner-Teams

Wir bieten Lizenz-Lösungen für Teams jeder Größe: Finden Sie heraus, welche Lösung am besten zu Ihnen passt.

Das Library-Modell:
IP-Zugang

Das Company-Modell:
Domain-Zugang