Was sind Seams und wie können sie in der Software-Modernisierung und –pflege genutzt werden?
Seams dienen als „Einstiegspunkte“ in ein Altsystem von denen aus mit reduziertem Risiko agiert werden kann. Sie eröffnen uns die Möglichkeit, Tests einzuführen und den Code zu verbessern (Refactoring) oder Änderungen einzupflegen, ohne das Risiko großer, umfassender Änderungen eingehen zu müssen. Seams können sich auf unterschiedliche Art und Weise zeigen oder vom Entwickler eingefügt werden: Durch bedingte Anweisungen, Polymorphismus, Schnittstellen, Dependency Injection und vieles mehr. Genau diese Vielfalt erschwert das Auffinden und Arbeiten mit den Seams.
Deshalb geht das Thema Hand in Hand mit einer angemessenen Testabdeckung. Diese ist die Voraussetzung, um das volle Potenzial von Seams ausschöpfen zu können und das Altsystem auf einen hohen qualitativen Standard zu heben. Zu den verschiedenen Testarten werden wir in einem zukünftigen Artikel mehr schreiben.
Warum sind Seams wichtig?
In “Legacy Code für Altsysteme” erwähnten wir erstmals die Möglichkeit, Seams zu nutzen, um Abhängigkeiten aufzulösen. Doch nicht nur in diesem Fall sind sie wertvolle Verbündete beim Umgang mit Legacy Code. Seams können helfen, bestimmte Teile des Codes isoliert zu betrachten, die Komplexität des Problems zu senken und alle Energie auf ein einziges Problem zu lenken. Dadurch verringern sich ungewollte Effekte, die später erst mühevoll entdeckt und gefixt werden müssen.
Ein weiterer Vorteil ist, dass Seams es deutlich erleichtern, automatisierte Tests einzuführen. So lässt sich ein Sicherheitsnetz aufspannen, welches mittelfristig die Geschwindigkeit des Entwicklungsteams positiv beeinflussen kann. Mit diesen Tests kann man eine hohe Qualität beim Kunden oder in der Produktion garantieren. Der größte Vorteil ist, dass Seams es ermöglichen, den Refactoring Prozess (Strukturverbesserung des Codes) allmählich durchzuführen. Statt eine riesige und beängstigende Veränderung anzugehen, können kleine Schritte gemacht werden, um den Code im Laufe der Zeit stabiler und wartbarer zu machen.
Wie können Seams gefunden werden?
Das Auffinden von Seams in einem großen und komplexen Framework kann eine Herausforderung sein, aber es gibt mehrere Techniken, die zum Erfolg führen könnten.
Mit welchen Fragen kann ich Seams finden?
Das Formulieren von Fragen kann dabei helfen, Klarheit über den Code zu gewinnen. Fragen, um Seams zu finden können sein:
- Wo gibt es bedingte Anweisungen oder Verzweigungen im Code?
- Gibt es Abhängigkeiten zu externen Ressourcen wie Datenbanken oder Dateisystemen?
Wie liefert eine Software-Dokumentation Hinweise auf Seams?
Wie kann ich Seams gezielt mittels Code Inspektion suchen?
Bei der Code Inspektion wird der Quellcode gründlich untersucht, um potenzielle Fehler, Verbesserungsmöglichkeiten und Coding-Standards zu entdecken. Dabei können auch Abhängigkeiten und Seams gefunden werden.
Neben der Fehlererkennung ist die Code Inspektion auch eine Gelegenheit, Bereiche im Code zu identifizieren, die sich ähnlich verhalten oder ähnliche Logik aufweisen. Das weist oft auf Möglichkeiten hin, gemeinsame Funktionen in separate Module auszulagern. Auf diese Weise kann der Code besser organisiert und wiederverwendbarer gestaltet werden. Es ist wie das Lösen eines Puzzles, bei dem der Entwickler die richtigen Teile an die richtigen Stellen setzt, um den Code effizienter zu machen.
Wie arbeite ich mit Seams?
Sobald Seams identifiziert wurden, können verschiedene Strategien oder Design Patterns angewendet werden, um den Code effektiv zu verbessern. Doch sollten sie mit entsprechender Abwägung eingesetzt werden – ansonsten könnten sie am Ende die Komplexität erhöhen, statt sie zu reduzieren. Wie so oft findet sich nicht das eine Allheilmittel. Trotzdem stellen wir hier einige der Werkzeuge vor, welche häufig zum Erfolg führen:
Extrahieren und Anbieten von Schnittstellen
Idealerweise sind bereits Schnittstellen oder andere Pattern vorhanden. Wenn nicht, können diese neu eingeführt werden, um das Altsystem in kleinere, beherrschbarere Einheiten zu zerlegen.
Voraussetzung ist ein umfassendes Verständnis des Problems. Mit diesen neuen Schnittstellen kann man nun einfach Ressourcen ersetzen oder unabhängige Platzhalter für Tests erschaffen (mocken). Dies verbessert die Testbarkeit und ermöglicht zum Beispiel die Wiederholung von Tests bei Änderungen, um bestehende Funktionalität sicherzustellen (Regressionstests).
Ein hilfreiches Design Pattern kann der Wrapper sein. Das Wrappen externer Abhängigkeiten, wie Datenbanken oder APIs ermöglicht es, das Verhalten von Komponenten zu isolieren und zu kontrollieren. Vor allem die klare Trennung zwischen Anwendung und externen Komponenten kann so vollzogen werden.
Das letzte hier zu erwähnende Konzept ist der Polymorphismus. Hiermit kann eine Komponente nach außen hin möglichst einfache Schnittstellen anbieten trotz hoher innerer Komplexität. Ganz unterschiedliche Implementierungen können so mit Polymorphismus eine gemeinsame Schnittstelle nutzen. Da solch eine Vielgestaltigkeit einer Schnittstelle ein gewisses Risiko mit sich bringt, empfiehlt sich auch hier eine hohe Testabdeckung.
Dependency Injection als Seam
Idealerweise wurden Abhängigkeiten auch schon in der Vergangenheit durch bestimmte Entwurfsmuster von den Entwicklern reglementiert. Ein bekanntes Muster, mit dem man Abhängigkeiten von außen einbringen injecten) kann, ist die Dependency Injection. Falls dies nicht der Fall war, kann es sich manchmal lohnen, das Design Pattern neu einzuführen, um eine einfache Schnittstelle zu erhalten, die für das Erstellen und Verwalten der Abhängigkeiten zuständig ist.
Im Internet gibt es einige gute Beispiel, wie so etwas auf Codelevel aussehen könnte: “Techniques For Tackling Legacy Code” von Peter Morlion zeigt ein Beispiel in seinem Artikel.
Das Resultat ist ein flexibler, testbarer und wartbarer Code, bei dem Komponenten unabhängig voneinander entwickelt und getestet werden können. Bekannte Frameworks wie Dagger (für Android) oder Angular (für JavaScript) bieten Möglichkeiten, um die Dependency Injections automatisch zu verwalten und erleichtern somit deren Einführung deutlich.
Anwendung von Feature Toggles
Feature Toggles, auch als Feature Flags bekannt, bieten einen Mechanismus zum selektiven Aktivieren oder Deaktivieren bestimmter Funktionen zur Laufzeit. Dieser Mechanismus kann an Seams eingehängt werden.
Die Toggles ermöglichen kontrollierte Experimente während der Entwicklung, vor allem in Infrastrukturen mit Continuous Integration. Die Software beinhaltet bereits die Änderung, aber der User kann sie noch nicht sehen.
In Webanwendungen wie Angular oder React wird dies oft genutzt, um die Veröffentlichung eines Features gezielt zu steuern und frühzeitig für Tests aller Art zur Verfügung zu stellen. Wichtig ist bei Feature Toggles eine sehr gute Dokumentation, welche einfach zu finden ist.
Für Codebeispiele können wir den Artikel “Feature Toggles (aka Feature Flags)“ von Pete Hodgson empfehlen.
Fazit
Legacy Code in Altsystemen kann manchmal wie ein unüberwindbares Hindernis wirken, aber mit der cleveren Nutzung von Seams kann er allmählich in eine wartbare und testbare Codebase umgewandelt werden. Mit jedem kleinen Schritt vorwärts wächst das Verständnis und verringert sich die Komplexität, bis alles plötzlich beherrschbar wirkt.
Möglichst früh gefundene Seams sind hierbei wertvolle Chancen, die genutzt werden sollten. Dennoch bleibt zu sagen, dass Legacy Code immer ein Marathon und kein Sprint ist.