Softwaresysteme gehören mit Sicherheit zu den komplexesten Konstruktionen, die Menschen erdacht und erbaut haben. Insofern ist es nicht verwunderlich, dass Softwareprojekte scheitern und Altsysteme – aus Angst, dass sie ihren Dienst einstellen – nicht mehr angerührt werden. Die Frage nach den Ursachen für das langfristige Gelingen oder Scheitern von Softwareentwicklung und Softwarewartung lässt sich auf vielen Ebenen beantworten: das Anwendungsgebiet und die involvierte Organisation, die eingesetzte Technologie, die fachliche Qualität des Softwaresystems oder auch die Qualifikation der Anwender und Entwickler.
In diesem Artikel lege ich den Fokus auf verschiedene Techniken, die Entwicklungsteams helfen können, technische Schulden in ihrer Architektur zu reduzieren beziehungsweise gar nicht erst entstehen zu lassen. Wenn sich das ganze Team der Architektur und der Gefahr technischer Schulden bewusst ist, bleibt die Architektur länger wartbar und erweiterbar.
Technische Schulden
Gehen wir einmal davon aus, dass wir zu Beginn unseres Softwareentwicklungsprojekts eine qualitativ hochwertige Architektur entworfen haben. Dann kann man hoffen, dass sich unser Softwaresystem am Anfang gut warten lässt. In diesem Anfangsstadium befindet sich unser Softwaresystem also in dem Korridor geringer technischer Schulden mit gleichbleibendem Aufwand für die Wartung (siehe grüne Klammer in Abbildung 1).
Je länger unser System lebt, um so mehr erweitern wir es und fügen neue Funktionalität hinzu. Bleibt uns keine Zeit, bei diesen Erweiterungen auf den Erhalt der modularen Architektur zu achten, so erodiert diese Architektur mit der Zeit immer mehr. Wir nehmen immer mehr technische Schulden auf (gelbe Pfeile in Abbildung 1, die aus dem Korridor für gleichbleibenden Aufwand herausgehen und dann rot werden) und nähern uns immer mehr einem verknäulten Monolithen mit hohem, unplanbarem Aufwand in der Wartung. Abbildung 1 macht diesen langsamen Verfall dadurch deutlich, dass die roten Pfeile immer kürzer werden. Pro Zeit oder Budget schaffen wir immer weniger Funktionalität. Folgefehler sind immer schwerer nachvollziehbar, bis zu dem Punkt, an dem jede Änderung zu einer schmerzhaften Anstrengung wird.
Besser wäre es, wenn unser Management und wir akzeptieren würden, dass Softwareentwicklung ein ständiger Lernprozess ist, bei dem der erste Wurf einer Lösung selten der endgültige ist. Eine Erweiterung im Korridor für gleichbleibenden Aufwand für Wartung führt also immer auch erst einmal zu mehr Schulden (siehe Abbildung 1, gelber Pfeil „Wartung und Erweiterung“). Die Überarbeitung der Architektur (Architekturerneuerung, grüne Pfeile in Abbildung 1 im Korridor für gleichbleibenden Aufwand für Wartung) muss in regelmäßigen Abständen durchgeführt werden, wenn vermieden werden soll, dass ein unwartbarer Monolith entsteht.
Folgt man diesem Prinzip, so läuft die Entwicklung in einer stetigen Folge von Erweiterung und Refactoring ab. Kann ein Team diesen Zyklus von Erweiterung und Refactoring dauerhaft verfolgen, so wird das System im Korridor geringer technischer Schulden bleiben (gelbe und grüne Pfeile in Abbildung 1 im Korridor „Gleichbleibender Aufwand für Wartung“).
Um für Softwaresysteme eine Bewertungsmöglichkeit zu schaffen, haben wir in den vergangenen Jahren den Modularity Maturity Index (MMI) entwickelt. In Abbildung 2 ist eine Auswahl von 21 Softwaresystemen dargestellt, die in einem Zeitraum von fünf Jahren analysiert wurden (jedes System hat auf der X-Achse eine Einheit). Für jedes System ist die Größe in Lines of Code dargestellt (Größe des Punktes) und die Modularität auf einer Skala von 0 bis 10 (Y-Achse). Die Farbgebung Rot, Gelb und Grün entspricht dabei den jeweiligen Bereichen der technischen Schulden aus Abbildung 1.
Abb. 1: Entwicklung und Effekt von technischen Schulden
Liegt ein System in der Bewertung zwischen 8 und 10, so ist es im Inneren bereits modular aufgebaut und wird sich mit wenig Aufwand fachlich zerlegen lassen. Systeme mit einer Bewertung zwischen 4 und 8 haben gute Ansätze, aber hier sind einige Refactorings notwendig, um die Modularität zu verbessern. Systeme unterhalb der Marke 4 würde man nach Domain-Driven Design als Big Ball of Mud bezeichnen. Hier ist kaum fachliche Struktur zu erkennen und alles ist mit allem verbunden. Solche Systeme sind nur mit sehr viel Aufwand in fachlich Module zerlegbar.
Ist man erst einmal im Korridor hoher, unplanbarer Wartung (siehe Abbildung 1) oder beim MMI im Range 0 bis 4 (siehe
Abbildung 2) angelangt, gibt es zwei Möglichkeiten, um aus dem Dilemma wieder herauszukommen: Das System wird durch ein neues ersetzt (Zyklus in Abbildung 1) oder es wird „refactored“ (rote und gelbe abwärts gerichtete Pfeile in Abbildung 1). Damit es gar nicht erst soweit kommt, empfehlen wir Entwicklungsteams regelmäßig Mob Architecting zu machen.
Abb. 2: Modularity Maturity Index (MMI)
Vom Pair Programming zum Mob Architecting
Immer wieder begegnen mir Projektteams, die ihre Softwaresysteme unabhängig von ihrem Anwendungsgebiet, ihrer Größe und ihrem Alter im Griff haben. Erweiterungen und Fehlerbehebung sind in akzeptabler Zeit machbar. Neue Mitarbeiter können mit vertretbarem Aufwand eingearbeitet werden. Was machen diese Projektteams anders? Wie schaffen sie es, mit ihren Softwarearchitekturen langfristig und gut zu leben?
Alle Teams, bei denen ich diese paradiesischen Zustände beobachten konnte, setzen sehr stark auf Pair Programming und Mob Programming. Bei unseren eigenen Teams haben wir als weitere Technik Mob Architecting hinzugefügt.
Pair Porgramming wurde 1999 von Kent Beck in seinem Buch zu Extreme Programming vorgestellt [Beck99]. Dabei arbeiten zwei Programmierer zusammen an einem Rechner. Der sogenannte Pilot hat die Kontrolle über die Tastatur, schreibt den Code und erklärt dem zweiten, was er warum tut. Der zweite Entwickler – er wird auch als Navigator bezeichnet – unterstützt den ersten, indem er beim Entwickeln reviewed und Verbesserungsvorschläge macht. Dabei wechseln die beiden Programmierer häufig die Rollen.
Mob Programming dehnt die Idee des Pair Programmings auf das gesamte Team aus. In diesem Fall arbeitet das gesamte Team gemeinsam an einer Aufgabe. Es gibt nur eine Tastatur, auf der getippt wird, und einen großen Bildschirm, auf dem alle verfolgen können, was der Pilot programmiert [Jan]. Genau wie beim Pair Programming erklärt der Pilot, während er programmiert, was er gerade vorhat. Der Rest des Teams beobachtet, was der Pilot tut, und bringt Ideen für Verbesserungen und potenzielle Probleme der Lösung in die Diskussion ein. Genau wie beim Pair Programming wird die Rolle des Piloten regelmäßig gewechselt.
Mob Architecting hebt das Konzept von Mob Programming auf ein anderes Niveau. Beim Mob Architecting wird das Team durch einen Berater unterstützt, der ein Architekturanalyse-Tool zur Visualisierung und Modellierung der Architektur einsetzt. Das Team kann dadurch die Architektur seines Systems sehr fokussiert diskutieren und verbessern.
Mob Architecting
Bei Mob Architecting wird als erstes der Quellcode des Systems in das Architekturanalyse-Tool geladen. Es gibt eine Reihe verschiedener Tools, mit denen Architekturen visualisiert und Refactorings entwickelt werden: zum Beispiel Lattix, Sonargraph, Sotograph, Stucture101. Da diese Tools meist komplexe Funktionalitäten anbieten, ist es sinnvoll, einen Tool-Experten als Piloten zu engagieren [Lil15].
Schritt 1: Tool-Pilot liest den Quellcode in das Tool ein gemeinsam mit dem Entwicklungsteam
Der externe Pilot führt das Entwicklungsteam durch den Prozess des Mob Architecting (Schritt 1 bis 4 in Abbildung 3). Im ersten Schritt wird der Quellcode untersucht, ob sich dort ein Mapping auf die Architektur findet. Tools für Architekturanalyse können in der Regel die Strukturen im Quellcode (Package-Baum, Namespaces, Directory-Baum usw.) darstellen und auch Informationen aus dem Build-Prozess verwenden, wie Build-Units, Maven-Module und dlls. Wenn die Architektur für das Entwicklungsteam wichtig ist, dann findet man in der Regel in den Quellcode-Strukturen eine Repräsentation der Architektur.
Abb. 3: Mob Architecting
In Abbildung 4 auf der linken Seite sieht man den Package-Baum einer Open-
Source-Java-Anwendung, wie der Sotograph sie darstellt. Die Kreise links repräsentieren Packages, die Dreiecke stehen für Dateien und die grünen Bögen für Abhängigkeiten. Bögen auf der linken Seite der Achse gehen von oben nach unten. Zum Beispiel gibt es im Package „plugin“ eine oder mehrere Klassen, die Funktionalität aus einer Klasse brauchen, die sich im „net“-Package befindet.
Die Bögen auf der rechten Seite der Achse stehen für Abhängigkeiten, die aufwärts gehen. Zum Beispiel gibt es im Package „util“ eine oder mehrere Klassen, die Funktionalität von Klassen aus den Packages „entities“, „controllers“ und „security“ brauchen. Alle Kreise für Packages, bei denen es ein Pluszeichen vor dem Kreis gibt, enthalten andere Packages und Klassen. Das Package ganz unten mit dem Namen „com.frovi.ss.Tree“ ist ganz ausgeklappt, sodass man die vier Klassen sieht, die zu diesem Package gehören [Lil15].
Schritt 2: Tool-Pilot modelliert die Architektur auf den Quellcode gemeinsam mit dem Entwicklungsteam
Der zweite Schritt beim Mob Architecting beschäftigt sich damit, verschiedene Architektursichten auf den Quellcode zu modellieren. In Abbildung 4 sieht man auf der rechten Seite, wie die Architektur auf den Quellcode modelliert wurde. In diesem Fall ist die Architektur eine technische Schichtung, die aus vier Schichten besteht: „App”, „Service”, „Entities” und „Util”.
In jeder Schicht befinden sich mehrere Module. Die App-Schicht enthält zum Beispiel die Module „managementtool”, „plugins” und „applications”. Auch die Schichten „Service” und „Util” enthalten mehrere Module. Nur die Schicht „Entities” besteht lediglich aus einem Modul mit demselben Namen wie die Schicht. Diese Module und der Package-Baum entsprechen sich in diesem Fall, was man sieht, wenn man die beiden Seiten von Abbildung 4 vergleicht. Das ist nicht immer der Fall und macht das Modellieren der Architektur (Schritt 2 in Abbildung 3) manchmal zu einem zeitaufwendigen Unterfangen.
Beim Vergleich der beiden Seiten von Abbildung 4 kann man erkennen, dass drei der technischen Schichten mit den Namen „App”, „Service”, „Entities” und „Util” nicht im Package-Baum existieren. Der Package-Baum enthält lediglich Packages mit den Namen der Module, die in den Schichten existieren (z. B. „plugin“, „applications“, „treeservice“ usw.). Nur „entities“ ist als Package vorhanden. Die anderen drei Schichten „App“, „Service“ und „Util“ sind nur in der Dokumentation und in den Gehirnen einiger Entwickler zu finden.
Beim Mob Architecting wird meistens sehr schnell klar, dass ein Teil der Architektur im Quellcode nicht sichtbar ist. In diesem Moment lernen Entwicklungsteams etwas über ihre Architektur und deren Abbildung in den Quellcode. Neue Teammitglieder fangen in diesem Moment an, einen Überblick zu bekommen, und können die Idee und die Leitplanken der Architektur, die ihnen im täglichen Geschäft von älteren Entwicklern vorgelebt werden, endlich nachvollziehen. Gleichzeitig fangen die Mitglieder des Teams, die schon lange dabei sind, an, miteinander über die verschiedenen Sichten auf die Architektur zu diskutieren. Dieser Moment macht dem Entwicklungsteam und auch dem Piloten normalerweise viel Spaß, denn das ist echte Architekturarbeit! Man diskutiert Architekturvorstellungen und hinterfragt sie dabei gemeinsam.
In Abbildung 4 sieht man auf der rechten Seite einige rote Bögen. Diese Bögen sind rot, weil sie die Regel der technischen Schichtung dieser Architektur verletzen. Einige Klassen aus dem Modul „util“ in der Schicht „Util“ brauchen Funktionalität von den Klassen in „Entities“ und „Service“. Der dritte Schritt beim Mob Architecting ist es, diese Verletzungen zu untersuchen und gegebenenfalls Refactorings zu definieren.
Abb. 4: Quellcode-Strukturen und modellierte Architektur
Schritt 3: Tool-Pilot entwickelt Refactorings für die Verletzungen der Architektur mit dem Entwicklungsteam
Die Verletzung zwischen den Schichten „Service“ und „App“ werden uns hier als ein Beispiel dienen, wie Verletzungen refaktoriert werden können. In Abbildung 5 zeigen sowohl die linke als auch die rechte Seite eine gefilterte Sicht auf den Quellcode. Links sieht man drei Klassen aus dem Package „org.infoglue.cms.controllers.kernel.impl.simple”, die die Verletzung zwischen „controllers“ und „applications“ (roter Bogen) verursachen. Diese drei Klassen rufen eine Methode aus der Klasse VisualFormatter.java.
Die Beziehungen, die von den drei Controller-Klassen ausgehen, entstehen durch eine einzige Zeile:
value = new VisualFormatter()
.escapeHTML(value);
In der weiteren Diskussion beschließt das Team, dass der VisualFormatter eher eine Utility-Klasse ist als eine Klasse, die zur Applikation gehört.
Deshalb entscheidet sich das Team, die Klasse VisualFormatter aus der App-Schicht in die Schicht „Util“ zu verschieben. Auf der rechten Seite von Abbildung 5
sieht man das Refactoring. VisualFormatter (rotes Dreieck unten) ist in die Schicht „Util” verschoben worden und die Verletzung (roter Bogen) ist verschwunden.
Natürlich ist dieses Refactoring nur ein virtuelles Refactoring im Architekturanalyse-Tool und gleichzeitig ist es ein sehr einfaches Refactoring. Keine Klasse wurde neu entworfen oder reimplementiert. Komplizierte Refatorings kann man in der Regel nicht dadurch erledigen, dass man eine oder mehrere Klassen verschiebt. Sondern nur durch echte Refactorings, bei denen Klassen reimplementiert oder verändert werden. In einem solchen Fall speichern wir mit unserem Team ein Bild der Verletzung in ein Jira-Ticket und beschreiben das Refactoring detailliert, mit dem die Verletzung behoben werden kann.
Abb. 5: Verletzungen und Refactoring für ein einfaches Problem
Schritt 4: Tool-Pilot sammelt und priorisiert Refactorings im Absprache mit dem Entwicklungsteam
Der letzte Schritt beim Mob Architecting ist, dass der externe Pilot das Refactoring auf einem Flipchart oder Whiteboard oder eben auch direkt im Jira beziehungsweise einem anderen Tool dokumentiert.
Dieser Prozess, bei dem man Architektur modelliert, untersucht und Verletzungen sammelt, ist ein iterativer (agiler) Prozess, der mehrfach beim Mob Architecting durchlaufen wird. Nicht nur eine technische Schichtung wird erarbeitet, sondern auch eine fachliche Zerlegung des Systems in Microservices und die Verwendung von Design- und Architektur-Patterns sollte untersucht werden.
Eine Untersuchung verschiedener Sichten auf die Architektur führt dazu, dass das Team anfängt, über die Architektur zu diskutieren. Das Team kann seine Architektur auf einem anderen Niveau als bisher betrachten und so übergreifende Refactorings in Angriff nehmen. Die gesammelten Refactorings erlauben es schließlich, Verbesserungen an der Architektur konkret zu planen und zu priorisieren.
Fazit
In diesem Artikel haben wir gesehen, wie sich Pair Programming inzwischen zu Mob Programming und Mob Architecting weiterentwickelt hat. Mob Architecting hilft dem ganzen Team, die Architektur des Systems im Blick zu behalten und technische Schulden zu vermeiden. Schon bei den ersten noch sehr kleinen Versionen von Systemen ist Mob Architecting ein sinnvolles Mittel, um Architekturarbeit im Team zu etablieren.
Detaillierter Informationen, wie Mob Architecting für verschiedene Arten von Architekturen durchgeführt werden kann, finden sich im Buch „langlebige Softwarearchitekturen“[Lil15].||
Mehr Informationen:
[Beck99] K. Beck, Extreme Programming Explained: Embrace Change, Addison-Wesley, 1999
[Jan] P. Jansson, Get a good start with mob programming, 15.9.2013, siehe:
https://thecuriousdeveloper.com/2013/09/15/get-a-good-start-with-mob-programming/
[Lil15] C. Lilienthal, Langlebige Softwarearchitektur, Technische Schulden analysieren, begrenzen und abbauen, dpunkt.verlag, 2015, english version in preparation