Be Software Architecture Wise
Be Wise Apps

Read in English · Leer en español · Lire en français

Abschnitt 3.8 von 3.8 · Mittelstufe · 25 Min. Lesezeit

Einfachheit, Umkehrbarkeit und das Messen von Struktur

Videolektion

5 Min. · auf Englisch gesprochen, mit Untertiteln
Nur anhören (Audio)
Transkript lesen

In dieser letzten Lektion des Kapitels geht es darum, Systeme so einfach zu halten, wie das Problem es erlaubt, und Struktur zu messen, ohne sich selbst zu täuschen.

Manche Dinge sind schwer, weil die Aufgabe schwer ist. Eine Steuererklärung ist kompliziert, weil das Steuerrecht kompliziert ist. Andere Dinge sind schwer, weil wir sie so angehen, etwa ein Ablagesystem mit sieben Ordnern, wo zwei genügen würden. Gute Designer akzeptieren die erste Art und bekämpfen die zweite. Sie bauen, was jetzt gebraucht wird, halten jede Tatsache an einer Stelle fest, vermeiden Entscheidungen, die sie nicht rückgängig machen können, und prüfen ihre Arbeit mit ein paar ehrlichen Messungen.

Fred Brooks trennte in seinem Essay „No Silver Bullet“ diese beiden Arten. Essenzielle Komplexität gehört zum Problem: die tatsächlichen Regeln der Mehrwertsteuer, die tatsächlichen Arten, auf die eine Lieferung scheitern kann. Kein Entwurf kann sie beseitigen, ohne zu ändern, was das System tut. Akzidentelle Komplexität entsteht daraus, wie wir die Lösung bauen: Werkzeuge, Workarounds und Strukturen, die komplizierter sind, als das Problem verlangt. Architekten platzieren die essenzielle Art hinter einer klaren Grenze, etwa einem Steuerservice, und beseitigen die akzidentelle.

Drei Maximen helfen. Die erste, bekannt als KISS, sagt: Halten Sie es einfach. Ein Entwurf ist gut, wenn die Menschen, die ihn reparieren müssen, vielleicht um drei Uhr morgens, ihn verstehen können. You aren’t gonna need it, kurz YAGNI, warnt davor, für Bedürfnisse zu bauen, die Sie nur vermuten. Sie bezahlen dafür, es zu bauen, es durch jede spätere Änderung mitzuschleppen und es zu reparieren, wenn Sie falsch geraten haben. Aber YAGNI gilt für Features, nicht für die Praktiken, die Änderungen günstig machen, etwa Tests und Refactoring. Und manche Entscheidungen, etwa ein Datenmodell oder eine öffentliche API, sind Einwegtüren. Dort vorauszudenken ist die Aufgabe des Architekten. Don’t repeat yourself, kurz DRY, betrifft Wissen, nicht Text. Jede Regel, etwa der Mehrwertsteuersatz, braucht ein maßgebliches Zuhause. Doch zwei Funktionen nur deshalb zusammenzulegen, weil sie gleich aussehen, koppelt Konzepte, die sich aus verschiedenen Gründen ändern werden. Schließlich ist Flexibilität wie eine Finanzoption. Bezahlen Sie dafür, wenn die Änderung wahrscheinlich ist oder ohne sie sehr teuer wäre und die Option günstig ist. Verlassen Sie sich im Übrigen auf gute Modularität.

Nun zum Messen. Die älteste weit verbreitete Codemetrik ist die zyklomatische Komplexität, von Thomas McCabe aus dem Jahr 1976. Sie betrachtet eine Funktion als Graphen: Codeblöcke sind Knoten, mögliche Sprünge zwischen ihnen sind Kanten. Diese hier berechnet eine Liefergebühr. Sie hat drei Entscheidungen: Beträgt die Warenkorbsumme fünfundzwanzig Pfund oder mehr, ist die Entfernung größer als fünf, und ist es eine Prioritätsbestellung? Es gibt acht Knoten und zehn Kanten. Die zyklomatische Komplexität ist Kanten minus Knoten plus zwei: zehn minus acht plus zwei, also vier. Das passt zu einer schnellen Probe: drei Entscheidungen plus eins. Vier bedeutet vier unabhängige Pfade, also mindestens vier Testfälle, um jeden einmal zu durchlaufen. McCabe schlug etwa zehn als vernünftige Obergrenze vor.

Ein Jahr später hat die Funktion eine Schleife bekommen, die einen Zuschlag für sperrige Artikel addiert, und die Prioritätsprüfung fragt zusätzlich, ob ein Fahrer verfügbar ist. Das sind die ursprünglichen drei Entscheidungen plus die Schleife, die Prüfung auf sperrige Artikel und das zusätzliche „and“, falls Ihr Werkzeug es zählt: sechs. Die Komplexität ist also sieben. Das liegt unter McCabes Grenze, hat sich aber fast verdoppelt. Um jeden Pfad zu testen, braucht es jetzt mindestens sieben Fälle statt vier. Der Code vermischt nun drei Preisanliegen, die von verschiedenen Leuten geändert werden. Ziehen Sie also jeden Zuschlag in eine eigene kleine Funktion heraus. Die Gesamtkomplexität ist ähnlich, aber jedes Teil lässt sich für sich testen.

Architekten messen auch, wie Module zueinander stehen. Die Kopplungs- und Stabilitätsmetriken aus Abschnitt drei Punkt vier zeigen Abhängigkeitsrichtung und Zyklen. Kohäsionsmetriken schätzen ab, ob eine Klasse eine Verantwortlichkeit trägt oder mehrere. Eine Kundenklasse, deren Methoden in drei Gruppen ohne gemeinsame Felder zerfallen, sind in Wahrheit drei Klassen, die sich eine Datei teilen. Die Versionsverwaltung deckt Change Coupling auf. Wenn sich zwei Dateien in siebzig Prozent der Commits gemeinsam ändern, sind sie gekoppelt, ob eine die andere importiert oder nicht. Und Hotspots kombinieren Änderungshäufigkeit mit Komplexität, um Code zu finden, der zugleich schwer zu bearbeiten ist und oft bearbeitet wird.

Aber Vorsicht vor Goodharts Gesetz: Wenn ein Maß zum Ziel wird, hört es auf, ein gutes Maß zu sein. Eine harte Komplexitätsgrenze von zehn erzeugt Funktionen, die an willkürlichen Stellen aufgeteilt sind, und Logik, die schwerer nachvollziehbar ist. Fachleute beobachten deshalb Trends statt Schwellenwerte, nutzen Metriken, um Gespräche anzustoßen statt Menschen zu benoten, und kombinieren Metriken, die in verschiedene Richtungen ziehen. Wo eine strukturelle Regel klar ist, etwa keine Abhängigkeitszyklen, machen sie daraus eine Fitness Function: eine automatisierte Prüfung, die den Build scheitern lässt und eine bewusste Wahl erzwingt, den Code zu korrigieren oder die Regel zu ändern.

Der vollständige Abschnitt endet mit einer Checkliste, um den Entwurf jedes Moduls zu prüfen. Damit ist Kapitel 3 abgeschlossen. Wenn Sie bereit sind, versuchen Sie den optionalen Kapiteltest und machen Sie dann mit Kapitel 4 weiter, über das Dokumentieren und Kommunizieren von Architektur. Gut gemacht.

Was Sie wissen müssen

Zwei Arten von Komplexität

In „No Silver Bullet: Essence and Accidents of Software Engineering“ (vorgestellt 1986 auf dem IFIP World Computer Congress und 1987 in IEEE Computer veröffentlicht) teilte Fred Brooks die Schwierigkeiten der Softwareentwicklung in zwei Arten ein. Essenzielle Komplexität gehört zum Problem: die tatsächlichen Regeln der Mehrwertsteuer, die tatsächliche Vielfalt von Versicherungsprodukten, die tatsächlichen Arten, auf die eine Lieferung scheitern kann. Kein Entwurf kann sie beseitigen, ohne zu ändern, was das System tut. Akzidentelle Komplexität entsteht aus der Art, wie wir die Lösung bauen: Sprachen, Werkzeuge, Frameworks, Deployment-Maschinerie, Workarounds und Strukturen, die komplizierter sind, als das Problem verlangt. Brooks argumentierte, dass höhere Programmiersprachen und andere Fortschritte bereits viel akzidentelle Schwierigkeit beseitigt hätten, sodass keine einzelne neue Technik innerhalb eines Jahrzehnts eine Verbesserung um das Zehnfache bringen würde.

Für Architekten ist die Unterscheidung ein tägliches Werkzeug. Essenzielle Komplexität muss platziert werden: dort, wo die Menschen, die sie verstehen, daran arbeiten können, hinter einer Grenze, die verhindert, dass sie in alles andere durchsickert (ein Preismodul, ein Steuerservice). Akzidentelle Komplexität muss beseitigt oder verhindert werden: Jeder zusätzliche Service, jede Queue, jeder Cache, jede Abstraktionsschicht und jede Konfigurationsoption fügt etwas davon hinzu, und jedes Element muss seinen Platz verdienen, indem es ein echtes Problem löst. Der Essay „Out of the Tar Pit“ (2006) von Ben Moseley und Peter Marks trieb die Idee weiter und benannte veränderlichen Zustand und Kontrollfluss als wichtige Quellen akzidenteller Komplexität; das ist ein Grund, warum funktionale und ereignisgesteuerte Entwürfe, die geteilten Zustand begrenzen, beliebt sind.

Der Rest dieses Abschnitts behandelt die Prinzipien, die akzidentelle Komplexität gering halten (KISS, YAGNI, richtig verstandenes DRY, Umkehrbarkeit), und die Messungen, die Ihnen helfen, Struktur objektiv zu sehen, samt ihren Grenzen.

KISS und YAGNI

KISS, meist ausgeschrieben als „keep it simple, stupid“, ist eine Designmaxime, die 1960 in der US Navy belegt ist und mit Kelly Johnson in Verbindung gebracht wird, dem leitenden Ingenieur der Skunk Works von Lockheed, der verlangt haben soll, dass Düsenflugzeuge im Feld von einem durchschnittlichen Mechaniker mit einer Handvoll Werkzeuge repariert werden können. Für Software gilt dasselbe: Ein Entwurf ist gut, wenn die Menschen, die ihn betreiben und reparieren müssen, oft um 3 Uhr morgens unter Druck, ihn verstehen können. Einfach heißt nicht leicht oder naiv. Es heißt wenige Konzepte, wenige bewegliche Teile und wenige Sonderfälle für das zu lösende Problem.

YAGNI („you aren’t gonna need it“, „Sie werden es nicht brauchen“) stammt aus dem Extreme Programming der späten 1990er-Jahre: Bauen Sie keine Fähigkeiten für Bedürfnisse, die Sie nur vermuten. Martin Fowlers Analyse ist nützlich, weil sie die Kosten benennt, die entstehen, wenn man es ignoriert. Da sind die Kosten des Bauens des vermuteten Features; die Kosten der Verzögerung, weil stattdessen etwas Wertvolles nicht gebaut wurde; die Kosten des Mitschleppens, weil jede spätere Änderung den zusätzlichen Code berücksichtigen muss; und, falls die Vermutung falsch war, die Kosten der Reparatur. Selbst wenn die Vermutung sich als richtig erweist, wurde das Feature mit weniger Wissen gebaut, als Sie später gehabt hätten.

Zwei Einschränkungen verhindern, dass YAGNI zur Ausrede wird. Erstens gilt es für Features und spekulative Flexibilität, nicht für die Praktiken, die Änderungen günstig machen. Automatisierte Tests, Refactoring, Continuous Delivery und klare Modulgrenzen sind es, die das Aufschieben von Features sicher machen; wer sie im Namen von YAGNI weglässt, verwandelt Aufschub in Schulden. Zweitens sind manche Entscheidungen Einwegtüren (Abschnitt 1.6). Die Wahl eines Datenmodells, der Form einer öffentlichen API, eines Mandantenmodells oder einer Sicherheitsgrenze ist kein Feature, das Sie später zu denselben Kosten ergänzen können. Dort ist Vorausdenken keine Spekulation, sondern die Aufgabe des Architekten.

DRY meint Wissen, nicht Text

Andy Hunt und Dave Thomas führten DRY in The Pragmatic Programmer (1999; Jubiläumsausgabe zum 20. Jahrestag 2019) mit dieser Definition ein: Jedes Stück Wissen muss innerhalb eines Systems eine einzige, eindeutige, maßgebliche Darstellung haben. Das Schlüsselwort ist Wissen. Wenn der Mehrwertsteuersatz, die Regel für eine gültige Postleitzahl oder die Bedeutung eines Bestellstatus an mehreren Stellen steht, muss man für eine Änderung jede Kopie finden, und die Kopien werden auseinanderdriften. Die Ausgabe von 2019 betont, dass es bei DRY um die Duplizierung von Wissen und Absicht geht, nicht um Codezeilen, die zufällig gleich aussehen.

Der häufige Missbrauch geht in die andere Richtung. Zwei Funktionen, die gleich aussehen, werden zusammengelegt, weil sie gleich aussehen, obwohl sie unterschiedliches Wissen darstellen, das sich aus unterschiedlichen Gründen ändern wird: Die Liefergebührenregel und die Restaurantprovisionsregel multiplizieren heute beide eine Summe mit einem Prozentsatz, aber sie gehören zu verschiedenen Akteuren (Finanzen und Partnermanagement) und werden auseinanderlaufen. Sie zusammenzulegen koppelt zwei Konzepte, die die Domäne getrennt hält: Beide hängen nun von einer gemeinsamen Funktion ab, sodass eine für die eine Regel gewünschte Änderung stillschweigend die andere ändert. In den Begriffen von Abschnitt 3.6 erzeugt das Connascence, wo das Problem keine hat (eine gemeinsame Funktion ist nur dann die richtige Lösung, wenn beide Seiten wirklich dasselbe berechnen müssen); es ist außerdem ein Verstoß gegen das Single-Responsibility-Prinzip (Abschnitt 3.5). Sandi Metz’ bekannter Rat lautet, Duplizierung der falschen Abstraktion vorzuziehen, weil eine falsche gemeinsame Abstraktion Parameter und Bedingungen ansammelt, bis niemand mehr wagt, sie zu ändern. Viele Teams nutzen die Dreierregel: eine zweite Kopie tolerieren und eine Abstraktion herausziehen, wenn die dritte auftaucht und das gemeinsame Wissen klar ist.

Auf Architekturebene wird die Abwägung explizit. Eine gemeinsame Bibliothek, die jeder Service nutzen muss, beseitigt duplizierten Code, koppelt aber die Release-Zyklen der Services. Viele Microservice-Organisationen akzeptieren bewusst duplizierten Code (kleine Hilfsfunktionen, in jeden Service kopiert), bestehen aber auf genau einem Verantwortlichen für dupliziertes Wissen (den kanonischen Kundendatensatz, die Steuerregeln). Fragen Sie bei jeder Duplizierung: Wenn sich diese Regel ändert, müssen sich dann alle Kopien gemeinsam ändern? Wenn ja, ist es dupliziertes Wissen und sollte einen Verantwortlichen haben. Wenn nein, ist es zufällige Ähnlichkeit und kann bleiben.

Umkehrbarkeit und die Kosten von Optionen

Einfachheit hat eine zeitliche Dimension. Ein Entwurf, der heute einfach, aber unmöglich zu ändern ist, ist nicht günstig; ein Entwurf voller Flexibilität, die niemand nutzt, ist es auch nicht. Das nützliche Konzept ist Umkehrbarkeit: Wie teuer wäre es, diese Entscheidung später rückgängig zu machen oder zu ändern? Abschnitt 1.6 hat Einwegtüren und Zweiwegtüren eingeführt. Hier geht es darum, dass sich Flexibilität wie eine Finanzoption verhält: Sie hat jetzt einen Preis (zusätzlicher Code, zusätzliche Abstraktion, zusätzliche Betriebskosten) und einen Wert, der davon abhängt, wie wahrscheinlich und wie teuer die Änderung ist, gegen die sie absichert.

Daraus ergibt sich eine Faustregel, die YAGNI mit vorausschauendem Denken versöhnt. Bezahlen Sie für eine Option nur, wenn die Änderung einigermaßen wahrscheinlich ist oder ohne die Option sehr teuer wäre, und wenn die Option selbst günstig ist. Einen Zahlungsanbieter hinter einer eigenen Schnittstelle zu kapseln kostet einen Tag und schützt vor einer Änderung, die sowohl plausibel als auch teuer ist: eine gute Option. Eine Multi-Cloud-Abstraktionsschicht für ein Start-up mit einem einzigen Kunden zu bauen ist eine teure Option gegen eine unwahrscheinliche Änderung. Oft ist die günstigste Option einfach gute Modularität: Gut gesetzte Grenzen halten viele künftige Änderungen lokal, ohne dass jemand sie vorhersagen muss; das ist Parnas’ ursprüngliches Argument für Information Hiding (Abschnitt 3.1).

Struktur messen: zyklomatische Komplexität

Das Urteil über Einfachheit wird besser, wenn es sich auf Messungen stützt. Die älteste weit verbreitete Codemetrik ist die zyklomatische Komplexität, die Thomas McCabe in „A Complexity Measure“ (IEEE Transactions on Software Engineering, 1976) einführte. Sie betrachtet eine Funktion als Kontrollflussgraphen, mit Knoten für Codeblöcke und Kanten für mögliche Kontrollübergänge, und zählt die linear unabhängigen Pfade durch ihn: M = E − N + 2P, wobei E die Zahl der Kanten, N die Zahl der Knoten und P die Zahl der Zusammenhangskomponenten ist (1 für eine einzelne Funktion). Bei strukturiertem Code mit einem Einstiegspunkt entspricht dies der Zahl der Entscheidungspunkte plus eins.

McCabe schlug 10 als vernünftige Obergrenze für ein Modul vor. Der spätere NIST-Bericht zum strukturierten Testen von Arthur Watson und McCabe (1996) merkt an, dass erfahrene Teams Grenzen bis 15 erfolgreich verwendet haben, und empfiehlt eine schriftliche Begründung, wann immer ein Modul die gewählte Grenze überschreitet. Werkzeuge zur statischen Analyse wie radon oder das mccabe-Plugin für flake8 in Python, die ESLint-Regel complexity in JavaScript und SonarQube berechnen sie automatisch. Die Werkzeuge unterscheiden sich in Details, etwa ob jedes and oder or in einer Bedingung als zusätzliche Entscheidung zählt; vergleichen Sie daher nur Zahlen aus demselben Werkzeug.

def delivery_fee(total: int, distance_km: float, priority: bool) -> int:
    if total >= 2500:          # decision 1: free delivery over 25 pounds
        return 0
    fee = 299
    if distance_km > 5:        # decision 2
        fee += 100
    if priority:               # decision 3
        fee += 200
    return fee
Drei Entscheidungen, also zyklomatische Komplexität 4.

Der Kontrollflussgraph und die Zählung

Der Kontrollflussgraph einer Liefergebührenfunktion mit drei Entscheidungen und einer zyklomatischen Komplexität von viertotal >= 2500?return 0distance > 5?fee += 100priority?fee += 200return feeEndejaneinjaneinjaneinZählungKnoten N = 8Kanten E = 10M = E − N + 2M = 10 − 8 + 2 = 4Probe: 3 Entsch. + 1 = 44 unabhängige Pfadealso mindestens 4 Testfällefür Basispfad-Tests
Acht Knoten und zehn Kanten ergeben M = 10 − 8 + 2 = 4, passend zu drei Entscheidungen plus eins.

Ausgearbeitetes Beispiel

Ausgearbeitetes Beispiel: wenn die Funktion wächst

Ein Jahr später hat delivery_fee eine Schleife über den Warenkorb und eine zusammengesetzte Bedingung dazubekommen. Ermitteln Sie ihre Komplexität und entscheiden Sie, was zu tun ist.

Neuer Codefor item in items: mit if item.bulky: fee += 50 darin, und if priority and rider_available: fee += 200 ersetzt if priority.
EntscheidungenUrsprünglich 3, plus die for-Schleife (1), plus das if item.bulky (1), plus das and (1, falls das Werkzeug boolesche Operatoren zählt): 6.
Zyklomatische Komplexität6 + 1 = 7. Unter McCabes Grenze von 10, aber fast verdoppelt.
Folgen für das TestenBasispfad-Tests brauchen mindestens 7 Testfälle, um jeden unabhängigen Pfad einmal zu durchlaufen; die alte Suite mit 4 deckt nicht mehr jeden Pfad ab.
Deutung für den EntwurfDie Funktion vermischt nun drei Preisanliegen: Schwellenwert für die Warenkorbgröße, Entfernungs- und Artikelzuschläge sowie Priorität. Sie ändern sich auf Wunsch verschiedener Stellen (Marketing, Betrieb, Produkt).
RefactoringZiehen Sie distance_surcharge, bulky_surcharge und priority_surcharge heraus, jeweils mit Komplexität 2 oder 3, und lassen Sie delivery_fee sie summieren. Die Gesamtkomplexität ist ähnlich, aber jedes Teil ist klein, benannt und für sich testbar.

Refactoring senkt die Gesamtkomplexität selten; es verteilt sie auf Teile, die leichter zu verstehen, zu testen und unabhängig zu ändern sind. Die essenzielle Komplexität der Preisregeln bleibt.

Die Metrik hat einen Trend angezeigt, der ein Gespräch wert war; die Entscheidung zur Aufteilung fiel beim Lesen des Codes und mit der Frage, wer ihn ändert.

Struktur jenseits von Funktionen messen

Die zyklomatische Komplexität sieht jeweils nur eine Funktion. Architekten brauchen Maße dafür, wie Module zueinander stehen.

Kopplungs- und Stabilitätsmetriken. Abschnitt 3.4 (Abhängigkeiten steuern) hat afferente und efferente Kopplung eingeführt, die Instabilität I = Ce / (Ca + Ce), die Abstraktheit und den Abstand von der Hauptreihe. Sie lassen sich günstig aus Importgraphen berechnen und decken gut Komponenten auf, von denen viele abhängen, die aber selbst von volatilem Code abhängen; außerdem finden sie Abhängigkeitszyklen.

Kohäsionsmetriken. Die Familie der LCOM-Metriken (lack of cohesion of methods, mangelnde Kohäsion der Methoden) schätzt ab, ob eine Klasse eine Verantwortlichkeit trägt oder mehrere. Die ursprüngliche Version von Shyam Chidamber und Chris Kemerer (1994) vergleicht Methodenpaare danach, ob sie gemeinsame Felder nutzen. Eine weit verbreitete spätere Variante, LCOM4 von Martin Hitz und Behzad Montazeri, baut einen Graphen, in dem Methoden verbunden sind, wenn sie ein Feld teilen oder die eine die andere aufruft, und zählt die zusammenhängenden Gruppen. Eine Klasse mit LCOM4 = 1 ist in sich geschlossen; eine Klasse mit LCOM4 = 3 sind drei Klassen, die sich eine Datei teilen. Zum Beispiel hat eine Klasse Customer, deren update_address und format_address die Adressfelder nutzen, deren add_points und points_balance die Treuefelder nutzen und deren send_newsletter nur das E-Mail-Feld nutzt, LCOM4 = 3, was auf die Verantwortlichkeiten Adresse, Treueprogramm und Marketing hindeutet.

Historienbasierte Metriken. Die Versionsverwaltung zeichnet auf, wie sich das System tatsächlich ändert, was die statische Analyse nicht sehen kann. Change Coupling findet Dateien oder Services, die wiederholt gemeinsam committet werden: Wenn sich pricing.py und invoice_template.html in 70 % der Commits gemeinsam ändern, sind sie gekoppelt, ob eine die andere importiert oder nicht, und die Kopplung kann eine Servicegrenze überschreiten, die sie eigentlich trennen sollte. Hotspots kombinieren Änderungshäufigkeit mit Komplexität, um Code zu finden, der zugleich schwer zu bearbeiten ist und häufig bearbeitet wird; Adam Tornhills Your Code as a Crime Scene (2015; zweite Auflage 2024) hat die Technik bekannt gemacht. Abschnitt 1.7 über technische Schulden hat gezeigt, wie Hotspots die Tilgung auf die Schulden lenken, die die höchsten Zinsen kosten.

Strukturmetriken auf einen Blick

MetrikBereichWas sie aufdecktGrößter blinder Fleck
Zyklomatische KomplexitätFunktionVerzweigungslogik, die schwer zu testen und zu durchschauen istSagt nichts über Benennung, Daten oder das Zusammenspiel von Funktionen
Ca, Ce, Instabilität, AbstandPaket oder KomponenteAbhängigkeitsrichtung, Stabilität, ZyklenZählt Abhängigkeiten, nicht ihre Stärke oder Bedeutung
LCOM (zum Beispiel LCOM4)KlasseKlassen mit mehreren unzusammenhängenden VerantwortlichkeitenEmpfindlich gegenüber Zugriffsmethoden und Sprachmerkmalen; verrauscht bei kleinen Klassen
Change CouplingDatei, Modul oder ServiceVerborgene Kopplung, die sich nur darin zeigt, wie sich das System ändertBraucht genügend Historie; Sammel-Commits erzeugen Rauschen
HotspotsDatei oder ModulWo sich Verbesserungen am schnellsten auszahlenPriorisiert; sagt nicht, was falsch ist
Connascence (per Review beurteilt)BeliebigForm und Stärke jeder AbhängigkeitWeitgehend manuell; wenige Werkzeuge messen die dynamischen Formen

Goodharts Gesetz und die Grenzen von Metriken

Jede der obigen Metriken ist ein Stellvertreter für etwas, das uns wichtig ist, das wir aber nicht direkt messen können: wie leicht sich das System verstehen und ändern lässt. Goodharts Gesetz, benannt nach dem Ökonomen Charles Goodhart (1975) und am bekanntesten in der Formulierung der Anthropologin Marilyn Strathern von 1997, warnt: Wenn ein Maß zum Ziel wird, hört es auf, ein gutes Maß zu sein. Menschen optimieren die Zahl, nicht die Eigenschaft.

Softwareteams erleben das ständig. Eine harte Grenze von zyklomatischer Komplexität 10 erzeugt Funktionen, die an willkürlichen Stellen aufgeteilt sind und jeweils die Prüfung bestehen, während die Logik schwerer nachvollziehbar wird. Ein Abdeckungsziel von 80 % erzeugt Tests, die Code ausführen, ohne irgendetwas zu prüfen. Eine Regel, dass keine Klasse 200 Zeilen überschreiten darf, erzeugt Klassen, die in OrderHelper und OrderHelper2 aufgeteilt sind. Ein Ziel von „null Zyklen“ lässt sich erreichen, indem man Code in ein Sammelbecken-Paket common verschiebt, von dem alles abhängt.

Fachleute nutzen Metriken auf drei Arten, die Manipulation erschweren. Sie betrachten Trends statt absoluter Schwellenwerte (die Komplexität im Zahlungsmodul hat sich in sechs Monaten verdoppelt: warum?). Sie nutzen Metriken, um Gespräche anzustoßen und Reviews zu priorisieren, nicht, um Menschen zu benoten. Und sie kombinieren Metriken, die in verschiedene Richtungen ziehen, etwa Komplexität mit Änderungshäufigkeit oder Kopplung mit Teamverantwortung, sodass sich keine einzelne Zahl auf Kosten der anderen optimieren lässt.

Fitness Functions: Struktur prüfbar machen

Manche strukturellen Regeln sind klar genug, um sie zu automatisieren, und dann hängen sie nicht mehr von Wachsamkeit ab. Building Evolutionary Architectures von Neal Ford, Rebecca Parsons und Patrick Kua (2017; zweite Auflage 2022 mit Pramod Sadalage) nennt solche Prüfungen Fitness Functions: objektive, automatisierte Tests eines Architekturmerkmals, die in der Build-Pipeline oder gegen das laufende System laufen. Beispiele für Struktur sind: keine Abhängigkeitszyklen zwischen Paketen der obersten Ebene; das Domänenpaket importiert nichts aus den Web- oder Datenbankpaketen; keine Funktion über Komplexität 15 ohne ausdrückliche Ausnahme; kein Service liest die Datenbanktabellen eines anderen Service.

Werkzeuge wie ArchUnit (Java), import-linter (Python) und dependency-cruiser (JavaScript und TypeScript) drücken diese Regeln als Code aus; Abschnitt 3.4 hat Beispiele gegeben. Der Unterschied zu einer Dashboard-Metrik ist, dass eine Fitness Function eine Entscheidung kodiert, die das Team bereits getroffen hat; wird sie verletzt, scheitert der Build und erzwingt eine bewusste Wahl: den Code korrigieren oder die Regel ändern und festhalten, warum. Die vollständige Behandlung, einschließlich Fitness Functions für Performance, Sicherheit und andere Qualitäten, finden Sie in Fitness Functions und evolutionäre Architektur.

[importlinter]
root_package = shop

[importlinter:contract:domain-is-pure]
name = Domain must not depend on infrastructure
type = forbidden
source_modules =
    shop.domain
forbidden_modules =
    shop.infrastructure
    shop.web
Ein import-linter-Vertrag in setup.cfg oder .importlinter, der den Build scheitern lässt, wenn die Domänenschicht Infrastruktur importiert.

Checkliste: den Entwurf eines Moduls prüfen

Nutzen Sie diese Liste, wenn Sie ein neues Modul, eine Komponente oder einen Service prüfen oder eine wesentliche Änderung daran. Sie greift auf jeden Abschnitt dieses Kapitels zurück.

  • Können Sie seine Verantwortlichkeit in einem Satz angeben und den Akteur nennen, der seine Änderungen verlangt?Braucht der Satz ein „und“ oder ändern es mehrere Akteure, erwägen Sie eine Aufteilung (Modularität, SRP, CCP).
  • Verbirgt es eine Designentscheidung, die sich wahrscheinlich ändert?Ein Modul sollte ein Geheimnis besitzen, etwa ein Datenformat, einen Anbieter oder einen Algorithmus (Information Hiding).
  • Ist die Schnittstelle klein im Verhältnis zu dem, was sie verbirgt?Tiefe Module verbergen viel hinter wenig; durchreichende Schichten verursachen Kosten, ohne etwas zu verbergen.
  • Zeigen seine Abhängigkeiten auf stabilere, abstraktere Module?Prüfen Sie auf Zyklen und auf stabile Module, die von volatilen abhängen (ADP, SDP, DIP).
  • Was muss ein Client wissen, um es korrekt zu nutzen?Achten Sie auf Positionsparameter, magische Werte, vorgeschriebene Aufrufreihenfolgen und Annahmen über das Timing; schwächen Sie sie ab oder halten Sie sie lokal (Connascence).
  • Wer verantwortet es, und kann dieses Team es ändern und deployen, ohne sich mit anderen abzustimmen?Ein verantwortliches Team pro Modul; Schnittstellen zwischen Teams sollten explizit und versioniert sein (Gesetz von Conway).
  • Ist ein Teil seiner Komplexität akzidentell?Hinterfragen Sie jede zusätzliche Schicht, Option und jeden generischen Mechanismus: Welcher echten Anforderung dient er (KISS, YAGNI)?
  • Ist Wissen dupliziert oder zufällige Ähnlichkeit fälschlich geteilt?Dupliziertes Wissen braucht einen Verantwortlichen; ähnlich aussehender Code mit verschiedenen Verantwortlichen kann getrennt bleiben (DRY).
  • Welche Entscheidungen darin sind schwer umkehrbar, und sind sie bewusst getroffen?Formen öffentlicher APIs, Datenmodelle und Grenzen verdienen mehr Prüfung als interne Details.
  • Was sagen die Metriken und die Historie?Prüfen Sie Komplexitätstrends, Kopplungsmetriken, Change Coupling und Hotspots, und ergänzen Sie eine Fitness Function für jede Regel, die es wert ist, beibehalten zu werden.

Prüfen Sie Ihr Verständnis

Eine Funktion hat zwei if-Anweisungen und eine while-Schleife und keine booleschen Operatoren. Wie hoch ist ihre zyklomatische Komplexität?

Drei Entscheidungen plus eins, also 4.

Zwei Services enthalten jeweils eine identische 20-zeilige Funktion zum Formatieren von Datumsangaben. Ist das ein Verstoß gegen DRY?

Wahrscheinlich nicht. Es ist duplizierter Text, kein dupliziertes Fachwissen, und eine gemeinsame Nutzung würde die Releases der Services koppeln; duplizierte Geschäftsregeln, etwa die Steuerberechnung, sind etwas anderes.

Warum gilt YAGNI nicht für das Schreiben automatisierter Tests?

Tests gehören zu dem, was künftige Änderungen günstig und sicher macht; bei YAGNI geht es darum, keine spekulativen Features und keine spekulative Flexibilität zu bauen, und es ist auf solche Praktiken angewiesen, um zu funktionieren.

Einem Team wird gesagt, dass keine Funktion Komplexität 10 überschreiten darf, und die Komplexität sinkt, während die Fehler zunehmen. Was ist wahrscheinlich passiert?

Goodharts Gesetz: Funktionen wurden mechanisch aufgeteilt, um die Metrik zu erfüllen, wodurch die Logik schwerer nachvollziehbar wurde, ohne einfacher zu werden.

Ende von Kapitel 3Den Kapiteltest ablegenOptional: 20 Multiple-Choice-Fragen und 2 schriftliche Antworten, in der App.Zum Test →

Quellen und weiterführende Literatur

Haben Sie eine Frage zu diesem Abschnitt? ArchitectBot, der KI-Lernassistent des Kurses, antwortet zuerst aus dem Kurs.

Fragen: „Unser Team möchte in der CI eine maximale zyklomatische Komplexität und eine minimale Testabdeckung erzwingen. Wie nutzen wir diese Metriken, ohne dass Leute sie austricksen?“