Zum Inhalt springen

Habe ich im Frontend einen Big Ball of Mud?

Du kommst in ein neues Projekt. Die Anwendung läuft. Es gibt Ordner, Components, Services, vielleicht sogar Architekturdiagramme.

Und du verstehst zunächst nichts.

Das ist erst einmal kein Grund zur Sorge. Eine neue Domäne besitzt neue Begriffe, eine gewachsene Anwendung ihre Geschichte und eine größere Codebasis braucht Zeit. Selbst in einem hervorragend strukturierten System wirst du am ersten Tag nicht verstehen, warum ein bestimmter Zustand existiert, welcher fachliche Begriff was bedeutet oder weshalb ein Prozess genau diese fünf Schritte durchläuft.

Interessanter wird deshalb nicht die Frage, wie viel du am Anfang verstehst, sondern wie sich dein Verständnis entwickelt.

Wird das System mit zunehmendem Wissen einfacher? Entdeckst du Regeln, Grenzen und wiederkehrende Muster, mit denen du irgendwann auch unbekannte Teile halbwegs zuverlässig vorhersagen kannst?

Oder lernst du vor allem, warum die Regeln an dieser Stelle nicht gelten, warum dieser Service eine Ausnahme ist, warum jene Component aus historischen Gründen direkt auf drei andere Fachbereiche zugreift und warum man ausgerechnet diesen Teil des Systems besser nicht so verwendet, wie sein Name vermuten ließe?

Irgendwann entsteht ein unangenehmer Verdacht:

Du lernst nicht die Architektur. Du lernst die Ausnahmen von der Architektur.

Das bedeutet noch immer nicht automatisch, dass du einen Big Ball of Mud vor dir hast. Nicht jede schlechte Architektur ist ein Big Ball of Mud. Eine große Component ist keiner. Ein God Service ist keiner. Ein eigenes Routing-System ist keiner. Ein paar Dependency Cycles sind keiner. Und auch die Tatsache, dass ein Angular-Feature die vorgesehenen Mechanismen des Frameworks eher als unverbindliche Empfehlung interpretiert, reicht nicht.

Die entscheidende Frage lautet vielmehr:

Wie viel wirksame Ordnung besitzt dieses System noch?

Foote und Yoder beschrieben den Big Ball of Mud ursprünglich als ein beiläufig beziehungsweise wenig geplant strukturiertes System, dessen Organisation stärker durch unmittelbare Notwendigkeiten als durch einen erkennbaren Entwurf bestimmt wird. Die spätere Forschung zur Architekturerosion betrachtet entsprechend nicht nur einzelne Verstöße, sondern die zunehmende Abweichung zwischen intendierter und tatsächlich wirksamer Architektur sowie ihre Folgen für Wartung und Evolution.

Für die Diagnose im Frontend ist genau diese Unterscheidung wichtig: Ein Big Ball of Mud ist nicht einfach ein System mit vielen Architekturproblemen. Kritisch wird es dort, wo diese Probleme ihre lokalen Grenzen verlieren und Ordnung als wirksames Prinzip zunehmend verschwindet.

Nehmen wir zunächst ein Gegenbeispiel.

Stell dir ein komplexes Planungs-Feature vor. Vielleicht gibt es eine Karte, verschiedene Planungszustände, zahlreiche fachliche Regeln und eine Menge Interaktion. Im Zentrum steht eine Component, die über die Jahre auf beeindruckende Größe angewachsen ist. Um sie herum existieren ungefähr zehn kleinere Components, die eng mit ihr zusammenarbeiten.

Die zentrale Component lädt Daten, hält Zustand, orchestriert Benutzerinteraktionen, öffnet Dialoge, entscheidet fachlich, navigiert, stößt Aktualisierungen an und kümmert sich nebenbei um Dinge, die in einem anderen Leben vielleicht einmal eigene Verantwortlichkeiten hätten werden wollen. Ein sinnvolles Layering ist kaum erkennbar. Die Mechanismen des Frameworks werden dort genutzt, wo sie gerade passen. An anderen Stellen hat man eigene Wege gefunden.

Kurz gesagt: Das Ding ist ein kleiner Gott.

Das ist schlechte Frontend-Architektur. Die Component bündelt mehrere Verantwortlichkeiten, ein sinnvolles Layering ist praktisch aufgehoben und Presentation, fachliche Logik und Orchestrierung laufen an derselben Stelle zusammen. Lokal betrachtet ist sie tatsächlich eine Art God Object, und jede größere Änderung wird unnötig schwierig.

Aber jetzt kommt die wichtigere Beobachtung: Das Problem endet an einer fachlichen Grenze.

Das Planungs-Feature ist intern schlecht strukturiert, aber andere fachliche Bereiche greifen nicht beliebig in seinen Zustand hinein. Das Feature wiederum kennt nicht automatisch das halbe übrige System. Daneben existieren andere Slices, deren Grenzen weiterhin nachvollziehbar sind. Wer an Rechnungen arbeitet, landet nicht zwangsläufig in der Planungs-Component. Wer Benutzer verwaltet, muss deren innere Zustandsmaschine nicht verstehen.

Das Problem ist gravierend, aber begrenzt.

Ein God Object in einem abgegrenzten Slice ist noch kein Big Ball of Mud.

Oder allgemeiner:

Schlechte Architektur kann lokal sein. Ein Big Ball of Mud ist systemische Entgrenzung.

Das ist eine wichtige Negativprobe. Sonst würde praktisch jede hinreichend alte Anwendung irgendwann als Big Ball of Mud durchgehen. Für eine brauchbare Diagnose müssen wir deshalb weniger danach suchen, ob irgendwo schlechte Strukturen existieren, sondern danach, ob das System diese Strukturen noch begrenzen kann.

Zwei Frontend-Architekturen im Vergleich. Links ist ein schlecht strukturiertes Planungs-Feature mit einer großen zentralen Component und eng gekoppelten Satelliten klar auf einen fachlichen Slice begrenzt. Rechts durchbrechen zahlreiche Abhängigkeiten die Grenzen mehrerer Slices und Layer.

Ein lokaler God Object ist noch kein Big Ball of Mud. Kritisch wird es, wenn die Grenzen selbst ihre Wirkung verlieren.

Zurück zum neuen Projekt.

Du klickst dich zunächst ein wenig durch die Anwendung. Nicht wissenschaftlich, nicht mit Metriken. Du willst einfach sehen, wie so eine typische Component hier aussieht.

Also öffnest du eine.

Oben beginnt die Liste der Abhängigkeiten. Benutzerzustand. Navigation. Berechtigungen. Cache. Dialog-Service. Ein fachlicher Service. Noch ein fachlicher Service. Ein globaler Zustand. Dann etwas aus einer anderen Domain. Dann etwas, dessen Namen du noch nie gesehen hast und das offenbar ebenfalls ziemlich wichtig ist.

Das allein beweist wenig. Eine Shell, eine komplexe Page Component oder ein bewusst gewählter Orchestrationspunkt kann legitimerweise viele Abhängigkeiten besitzen.

Also öffnest du eine zweite Component.

Dann eine dritte.

Irgendwann wird aus der zufälligen Beobachtung ein Muster.

Die interessantere Frage lautet deshalb nicht:

Wie viele Dependencies besitzt diese Component?

Sondern:

Wenn ich zehn beliebige Components öffne: Bleiben deren Abhängigkeiten überwiegend im eigenen fachlichen Bereich und im vorgesehenen Layer?

Wenn die Antwort meistens ja lautet, hast du vermutlich lokale Ausreißer gefunden. Wenn dagegen fast jede beliebige Component Benutzerzustand, Navigation, globale Services und mehrere fremde Fachbereiche kennt, wird die Beobachtung deutlich interessanter.

Nicht wegen der Zahl der Injections. Sondern weil lokale Bausteine offenbar regelmäßig Wissen über das Gesamtsystem benötigen.

Dann begegnest du cache.service.ts.

Der Name beruhigt zunächst. Ein Cache. Das kennt man.

Du öffnest die Datei.

Tatsächlich wird dort gecacht. Unter anderem.

Außerdem befindet sich darin Benutzerzustand. Navigationslogik. Ein bisschen Initialisierung. Mehrere fachliche Ableitungen. Zustandsänderungen. Globale Kommunikation. Vielleicht noch Berechtigungen und einige Methoden, deren Zusammenhang mit Caching hauptsächlich dadurch dokumentiert ist, dass sie zufällig in cache.service.ts stehen.

5.000 Zeilen später weißt du immerhin eines: Caching ist nur eine seiner vielen Beschäftigungen.

Auch das macht die Anwendung noch nicht zum Big Ball of Mud. Große Services sind zunächst Designprobleme. Vielleicht lässt sich der Service sogar relativ isoliert zerlegen.

Wichtiger ist sein Fan-in: Wer hängt davon ab?

Wenn ein historisch gewachsener Service von immer mehr fachlich unabhängigen Bereichen verwendet wird, verändert sich seine Rolle. Er wird zu einem Gravitationszentrum des Systems. Jede weitere Funktion hat einen scheinbar guten Grund, ihn ebenfalls zu verwenden: Der Service ist ohnehin schon da, besitzt bereits Zugriff auf fast alles und wird überall injiziert.

Damit entsteht ein Verstärkungseffekt.

Der Name beschreibt irgendwann nur noch den Ursprung. Nicht mehr die Verantwortung.

Solche Knoten sind diagnostisch wesentlich interessanter als ihre reine Größe. Ein 5.000-Zeilen-Service mit drei klar begrenzten Konsumenten ist etwas anderes als ein 1.500-Zeilen-Service, von dem fast die gesamte Anwendung strukturell abhängt.

An anderer Stelle sieht die Welt überraschend ordentlich aus.

Viele Components sind klein. Hundert Zeilen. Manchmal weniger. Fast vorbildlich.

Dann fällt dir eine Zeile auf:

extends BaseComponent

Du öffnest BaseComponent.

Dort befinden sich Navigation, Benutzerinformationen, Berechtigungen, Loading-Zustände, Fehlerbehandlung, Caches, mehrere globale Services und vielleicht ein paar fachliche Hilfsfunktionen, die offenbar irgendwann zu nützlich waren, um sie nur an einer Stelle zu verwenden.

Dann suchst du nach Verwendungen.

Hundert Treffer.

Die Komplexität wurde nicht entfernt. Sie wurde nur an eine Stelle verschoben, die überall mitläuft.

Auch Vererbung ist natürlich kein Big-Ball-of-Mud-Indiz für sich. Eine wohldefinierte technische Basisklasse kann völlig legitim sein. Problematisch wird sie dort, wo sie gleichzeitig fachliche und technische Grenzen überbrückt und dadurch große Teile des Systems implizit miteinander verbindet.

Denn damit verliert eine lokale Component eine wichtige Eigenschaft: Aus ihrem sichtbaren Code lässt sich nicht mehr zuverlässig ableiten, wovon sie tatsächlich abhängt und welches Verhalten sie mitbringt.

Die sichtbare Struktur und die wirksame Struktur beginnen auseinanderzulaufen.

Spätestens irgendwann möchtest du nicht mehr einzelne Dateien ansehen, sondern einen vollständigen Ablauf verstehen.

Nehmen wir etwas vermeintlich Einfaches:

Login → Initialisierung → Navigation → erste fachliche View.

Du startest beim Login. Von dort geht es in einen Auth-Service. Dann in einen globalen Zustand. Dann in einen Initialisierungs-Service. Ein Guard kommt dazu. Ein Interceptor ebenfalls. Irgendwo wird ein Benutzer geladen, an anderer Stelle werden Berechtigungen aufgebaut. Die Navigation hängt an einer selbstgebauten Abstraktion und bevor die erste fachliche View tatsächlich erscheint, bist du durch Dateien gewandert, von deren Existenz du vor zwei Stunden noch nichts wusstest.

Irgendwann blickst du auf einen Service und stellst dir eine erstaunlich fundamentale Frage:

Warum genau bin ich eigentlich hier?

Das ist lustig, solange es gelegentlich passiert. In großen Systemen führen echte Abläufe nun einmal über mehrere technische Bausteine.

Als wiederkehrendes Muster berührt es aber eine wichtige Eigenschaft von Architektur: Locality of Reasoning.

Ich verwende den Begriff hier bewusst pragmatisch. Gemeint ist die Frage, wie groß der Kontext sein muss, den du gleichzeitig verstehen musst, um über einen lokal wirkenden Teil des Systems sinnvoll nachdenken zu können.

Schon Parnas begründete Modularisierung nicht einfach mit schöneren Dateibäumen. Gute Zerlegung sollte unter anderem die Verständlichkeit und Veränderbarkeit eines Systems verbessern, indem relevante Entwurfsentscheidungen hinter Modulgrenzen verborgen werden.

Übertragen auf ein Frontend bedeutet das: Wenn du an einer fachlich kleinen Funktion arbeitest, sollte ein erheblicher Teil des übrigen Systems für diese Aufgabe nicht relevant sein müssen.

Ein System wird verdächtig, wenn fachlich kleine Fragen regelmäßig einen systemweiten Verständnisraum benötigen.

Das ist ein wesentlich stärkeres Signal als eine einzelne große Datei. Denn jetzt geht es nicht mehr um Stil oder Codequalität. Es geht um die Fähigkeit der Architektur, den notwendigen Kontext zu begrenzen.

Zwei vereinfachte Frontend-Systeme im Vergleich. Links reduzieren klare Slices, Layer und gerichtete Abhängigkeiten den relevanten Suchraum einer Änderung. Rechts führen kreuzende Abhängigkeiten und globale Knoten dazu, dass eine lokale Änderung potenziell große Teile des Systems berührt.

Gute Architektur ordnet nicht nur Dateien. Sie reduziert den Kontext, den eine Änderung benötigt.

Im Dateibaum sieht die Anwendung vielleicht sogar hervorragend aus:

customers/
orders/
planning/
schools/
billing/
users/
administration/

Das wirkt zunächst beruhigend. Fachliche Slices sind vorhanden.

Dann arbeitest du an planning.

Dort findest du eine direkte Abhängigkeit auf schools. Das ergibt fachlich Sinn.

Außerdem braucht der Vorgang users. Ebenfalls nachvollziehbar.

Dann kommt administration dazu. Und customers. Ein gemeinsamer Service aus billing wird ebenfalls benötigt. An einer anderen Stelle importiert schools zurück aus planning, während users wiederum einen Typ aus customers verwendet.

Jede einzelne Verbindung besitzt möglicherweise sogar eine plausible Geschichte.

Genau das macht die Diagnose schwierig.

Fachliche Bereiche müssen selbstverständlich miteinander kommunizieren. Vollständige Isolation wäre weder realistisch noch automatisch gute Architektur. Entscheidend ist, wie diese Kommunikation stattfindet: Gibt es definierte Integrationspunkte? Lassen sich Abhängigkeitsrichtungen erklären? Ist klar, welcher Bereich eine Information besitzt und welcher sie lediglich konsumiert?

Oder darf im Grunde jeder jeden kennen?

Eine fachliche Grenze, die von nahezu jedem Bereich beliebig überschritten wird, ist irgendwann keine Grenze mehr. Sie ist nur noch ein Ordnername.

Hier beginnt die Unterscheidung zwischen sichtbarer und wirksamer Architektur wichtig zu werden. Die Verzeichnisstruktur kann weiterhin customers, orders und billing anzeigen. Das sagt aber wenig darüber aus, ob diese Bereiche als Module tatsächlich Informationen verbergen, Abhängigkeiten begrenzen oder Veränderungen voneinander abschirmen.

Der Dateibaum beschreibt dann zunehmend eine Wunschvorstellung. Der Dependency Graph zeigt, welche Grenzen im Code tatsächlich noch wirken.

Dasselbe Spiel lässt sich auf einer zweiten Achse beobachten.

Slices beantworten im Kern die Frage:

Wer ist fachlich wofür verantwortlich?

Layer beantworten eine andere:

Welche Art von Verantwortung darf an dieser Stelle liegen?

In einem strukturierten Frontend können konkrete Layer unterschiedlich geschnitten sein. Entscheidend ist nicht, ob sie exakt presentation, application, domain und infrastructure heißen. Entscheidend ist, dass ihre Bedeutung wiederholbar und verlässlich ist.

In einem erodierenden System sieht das anders aus.

Components laden Daten, transformieren DTOs, entscheiden fachlich, verändern globalen Zustand, orchestrieren Prozesse, navigieren und halten nebenbei ihren lokalen UI-State.

Services wiederum öffnen Dialoge, enthalten Business Rules, kümmern sich um HTTP, verwalten Caches und entscheiden, welche View als Nächstes angezeigt werden soll.

Infrastructure beginnt fachliche Entscheidungen zu treffen. Presentation besitzt fachliche Zustandsübergänge. Shared Services besitzen von allem ein bisschen.

Das Problem ist nicht, dass eine einzelne Component einmal eine Regel enthält, die besser an anderer Stelle aufgehoben wäre. Solche Verstöße gibt es in fast jeder realen Anwendung.

Relevant wird das Muster, wenn du auf die Frage

Wo liegen bei uns eigentlich die Business Rules?

keine brauchbare Antwort mehr geben kannst.

Wenn die Antwort lautet „kommt darauf an“, ist das noch normal.

Wenn sie lautet „im Prinzip überall“, ist es interessanter.

Wenn Business Rules praktisch überall liegen können, existiert ein Layering vielleicht noch als Verzeichnisstruktur – aber nicht mehr als Architektur.

Architekturerosion wird in der Forschung entsprechend nicht ausschließlich als optisches Durcheinander verstanden. Untersuchungen beschreiben strukturelle Verstöße, Abweichungen von Architekturentscheidungen und Probleme während Wartung und Evolution als miteinander verbundene Erscheinungsformen.

Eine Matrix aus fachlichen Slices und technischen Layern. Links bleiben Verantwortlichkeiten und Abhängigkeiten innerhalb nachvollziehbarer Grenzen. Rechts verteilen sich Business Rules, UI-State, HTTP und Navigation über beliebige Zellen, während zahlreiche Abhängigkeiten Slice- und Layer-Grenzen durchkreuzen.

Wenn Slices Abhängigkeiten und Layer Verantwortlichkeiten nicht mehr begrenzen, bleibt von der Architektur irgendwann nur noch die Beschriftung.

Irgendwann willst du Navigation verstehen.

Du suchst den Router.

Du findest etwas, das Router heißt.

Das war schon einmal ein guter Anfang.

Dann bemerkst du, dass die eigentliche Navigation über eine selbstgebaute Infrastruktur läuft. Routes besitzen zahlreiche Properties. Manche sind offensichtlich, andere klingen bedeutungsvoll, ohne ihre Bedeutung preiszugeben. Einige beeinflussen Berechtigungen, andere Initialisierung, wieder andere das Verhalten irgendwelcher globalen Zustände.

Du suchst nach Dokumentation.

Es gibt keine.

Aber es gibt jemanden, der seit neun Jahren im Projekt ist.

Das ist ausdrücklich kein Beweis für einen Big Ball of Mud. Ein Framework darf abstrahiert werden. Man darf Angulars Router kapseln. Unter passenden Anforderungen darf man sogar ein komplett eigenes Navigationsmodell entwickeln.

Die Frage ist wieder, ob die Komplexität begrenzt und erklärbar bleibt.

Wenn eine zentrale Infrastruktur von vielen Domains abhängt, systemweite Seiteneffekte besitzt, ihr Verhalten kaum aus dem Code oder einer Dokumentation ableitbar ist und wesentliche Regeln hauptsächlich im Gedächtnis einzelner Personen existieren, kommen mehrere vorher getrennte Probleme zusammen.

Nicht die selbstgebaute Infrastruktur ist das eigentliche Signal.

Die Kombination ist es.

Nach einigen Wochen bemerkst du noch etwas anderes.

Die Tickets sind völlig unterschiedlich. Einmal geht es um Benutzerverwaltung, dann um Planung, danach um Berechtigungen und nächste Woche um eine Änderung in einem Formular.

Trotzdem landest du erstaunlich oft in denselben fünf Dateien.

Ein globaler Store. Zwei große Services. Eine Base Class. Ein Helper, der seinen Namen vermutlich in einer Zeit bekommen hat, als er tatsächlich noch geholfen hat.

Solche Bausteine kann man sich bildlich als Gravitationszentren vorstellen. Sie ziehen Verantwortung an.

Der Mechanismus dahinter ist banal. Wenn ein zentraler Service bereits Benutzer, Navigation, Berechtigungen und mehrere fachliche Zustände kennt, ist die nächste Anforderung dort ausgesprochen bequem unterzubringen. Fast alles, was man benötigen könnte, ist bereits vorhanden.

Dadurch steigt sein Fan-in weiter. Gleichzeitig wächst häufig sein Fan-out, weil das Zentrum wiederum immer mehr andere Teile des Systems kennen muss.

Beim nächsten Ticket ist die Entscheidung noch einfacher:

„Das machen wir im globalen Service. Da liegt der Rest ja auch.“

Das ist kein physikalisches Gesetz und „schwarzes Loch“ selbstverständlich keine Softwaremetrik. Als Metapher beschreibt es aber einen realen Verstärkungseffekt: Bestehende Zentralität macht weitere Zentralisierung kurzfristig attraktiv.

Ein Hub ist dabei nicht automatisch schlecht. Anwendungen benötigen bewusst zentrale technische Mechanismen. Logging, Telemetrie oder bestimmte Infrastruktur können legitime hohe Fan-in-Werte besitzen.

Verdächtig wird ein Zentrum dann, wenn seine Zentralität nicht aus einer klaren Verantwortung, sondern aus der Ansammlung immer neuer fremder Verantwortlichkeiten entsteht.

Bis hierhin besteht vieles aus Erfahrung und Beobachtung.

Irgendwann reicht das nicht mehr.

Vielleicht möchtest du wissen, ob du lediglich selektiv auf besonders hässliche Stellen gestoßen bist. Vielleicht willst du herausfinden, ob die vermeintlichen Slices technisch tatsächlich existieren. Vielleicht möchtest du auch einfach sehen, ob dieses diffuse Gefühl einen strukturellen Hintergrund besitzt.

Also analysierst du Imports und Abhängigkeiten.

Die Hoffnung ist zunächst simpel: Im Graphen sollten sich fachliche Cluster erkennen lassen. Vielleicht gibt es einige zentrale technische Komponenten. Vielleicht ein paar unerwünschte Querverbindungen. Grundsätzlich aber müssten sich die Strukturen wiederfinden, die im Architekturdiagramm eingezeichnet sind.

Manchmal passiert genau das.

Und manchmal passiert etwas anderes.

Es erscheinen unzählige Cross-Slice-Abhängigkeiten. Pfeile laufen in beide Richtungen. Knoten mit extrem hohem Fan-in verbinden große Teile des Systems. Andere besitzen gewaltigen Fan-out. Zwischen vermeintlich unabhängigen Bereichen entstehen Cycles. Mehrere Cycles verbinden sich zu großen Strongly Connected Components.

In einem gerichteten Abhängigkeitsgraphen fasst eine Strongly Connected Component Knoten zusammen, die sich gegenseitig über gerichtete Pfade erreichen können. Für Softwareabhängigkeiten bedeutet eine große SCC damit vor allem: Die enthaltenen Teile lassen sich auf dieser Ebene nicht mehr sauber in eine azyklische Richtung ordnen. Forschung und Werkzeuge zur Analyse von Dependency Cycles verwenden SCCs deshalb als eine sinnvolle strukturelle Einheit – nicht als automatische Big-Ball-of-Mud-Diagnose.

Ein Cycle ist noch kein Big Ball of Mud.

Zehn Cycles ebenfalls nicht automatisch.

Interessant wird es, wenn ein erheblicher Teil des Systems in großen zyklischen Strukturen steckt, wenn die Cycles Slice-Grenzen durchschneiden und wenn dieselben Knoten gleichzeitig als globale Gravitationszentren auftreten.

Sangal, Jordan, Sinha und Jackson zeigten bereits 2005, wie statisch extrahierte Abhängigkeiten genutzt werden können, um die reale Struktur großer Softwaresysteme sichtbar zu machen und gegen explizite Architekturregeln zu prüfen. Genau darin liegt der diagnostische Wert solcher Modelle: Nicht das Diagramm selbst ist Architektur, aber es kann sichtbar machen, ob die im Diagramm behaupteten Grenzen im Code tatsächlich noch existieren.

Und wenn gewichtete Abhängigkeitslinien irgendwann zu einer dunklen Fläche verschmelzen, bekommt der Name „Big Ball of Mud“ zumindest optisch eine erstaunliche Plausibilität.

Das ist allerdings nur eine Pointe. Der Begriff heißt historisch nicht deshalb so.

Bleiben Änderungen dort, wo sie fachlich hingehören?

Abschnitt betitelt „Bleiben Änderungen dort, wo sie fachlich hingehören?“

Dependency Graphs zeigen statische Struktur. Eine zweite Perspektive entsteht während ganz normaler Entwicklung.

Du bekommst ein Ticket.

„Zusätzliche Validierung im Planning.“

Klingt lokal.

Du änderst etwas im Planning. Dann muss ein globaler Service angepasst werden. Anschließend eine gemeinsame Basisklasse. Danach stellt sich heraus, dass ein zweiter fachlicher Bereich denselben Zustand indirekt verwendet. Sicherheitshalber testest du noch drei andere Flows, weil du nicht genau weißt, welche Seiteneffekte der globale Mechanismus besitzt.

Das kann passieren. Manche Anforderungen sind fachlich weitreichender, als ihr Ticket zunächst vermuten lässt.

Interessant wird deshalb auch hier die Wiederholung.

Wie gut stimmt der fachliche Änderungsradius mit dem technischen Änderungsradius überein?

Wenn eine fachlich lokale Anforderung typischerweise auch technisch lokal umgesetzt werden kann, leisten die Grenzen des Systems etwas.

Wenn dagegen kleine fachliche Änderungen regelmäßig über viele weit entfernte Teile der Anwendung verteilt werden müssen, verliert das System Locality of Change.

Je weniger der fachliche Änderungsradius mit dem technischen Änderungsradius übereinstimmt, desto interessanter wird die Diagnose.

Dafür muss man sich nicht ausschließlich auf statische Imports verlassen. Gall, Hajek und Jazayeri untersuchten bereits Ende der 1990er-Jahre sogenannte logische beziehungsweise evolutionäre Kopplung: Module können strukturell zunächst getrennt aussehen und dennoch über die Historie immer wieder gemeinsam geändert werden. Gerade solche wiederkehrenden gemeinsamen Änderungen über Modul- oder Subsystemgrenzen hinweg können auf verborgene Abhängigkeiten und eine problematische Zerlegung hinweisen.

Das ist für einen Big Ball of Mud besonders interessant. Vielleicht sehen orders und customers im Dependency Graph noch einigermaßen getrennt aus. Wenn Änderungen an einem Bereich aber über Jahre hinweg regelmäßig Änderungen im anderen erzwingen, erzählt die Versionshistorie eine zusätzliche Geschichte.

Architektur zeigt sich nicht nur darin, wie Code angeordnet ist.

Sie zeigt sich auch darin, wie er sich verändern kann.

Nach der technischen Struktur folgt irgendwann eine überraschend einfache Frage:

Wer ist dafür verantwortlich?

Für den Planning-Slice?

Für den globalen State?

Für den Cache-Service?

Für die Base Class?

Für Navigation?

Für die zentrale fachliche Regel, die an fünf verschiedenen Stellen implementiert ist?

Die Antworten können unterschiedlich ausfallen:

„Das gehört irgendwie allen.“

„Das ist historisch gewachsen.“

„Da müssen wir X fragen.“

„Eigentlich niemand.“

Oder der Klassiker:

„Da fasst besser keiner etwas an.“

Ownership ist dabei nicht nur ein Organigramm-Thema. Selbst wenn keine Teams formal einzelnen Modulen zugeordnet sind, kann ein System strukturell Ownership ausdrücken.

Wenn ich eine fachliche Regel suche: Kann ich nachvollziehen, welcher Bereich sie besitzen sollte?

Wenn ein Zustand verändert wird: Gibt es einen erkennbaren Ort, der diese Entscheidung kontrolliert?

Wenn ein Modul eine Schnittstelle anbietet: Ist klar, was es nach außen verspricht und was seine interne Angelegenheit bleibt?

In einem stark entgrenzten System wird nicht nur Code verteilt. Auch Verantwortung wird diffus.

Und das verstärkt das Problem: Wo niemand genau sagen kann, wem eine Entscheidung gehört, ist es schwer, eine Grenze konsequent zu verteidigen.

Die Forschung zur Architekturerosion zeigt ebenfalls, dass ihre Ursachen nicht ausschließlich technischer Natur sind. Organisatorische Faktoren, Wissen, Entwicklungspraktiken und langfristige Architekturpflege spielen neben technischen Verstößen eine wichtige Rolle.

Architektur sollte ein System vorhersagbarer machen

Abschnitt betitelt „Architektur sollte ein System vorhersagbarer machen“

An diesem Punkt lohnt sich ein Schritt zurück.

Warum interessieren uns Slices, Layer, Abhängigkeitsrichtungen oder Ownership überhaupt?

Nicht weil Architekturdiagramme hübsch aussehen.

Architektur reduziert den möglichen Lösungs- und Suchraum.

Wenn ich weiß, dass fachliche Regeln eines Bereichs in dessen Domain liegen, muss ich sie nicht in beliebigen Components, HTTP-Services und Shared Helpers suchen.

Wenn ich weiß, dass billing nicht direkt von planning abhängen darf, kann ich bestimmte Pfade ausschließen.

Wenn ich weiß, dass Presentation nur über eine definierte Application-Schnittstelle mit einem Slice kommuniziert, muss ich für eine UI-Änderung nicht dessen gesamte Infrastruktur verstehen.

Gute Struktur produziert deshalb mit der Zeit Vorhersagbarkeit.

Du öffnest einen unbekannten Bereich und weißt ungefähr, was dich erwartet.

Du kennst nicht den Code, aber du kennst seine Spielregeln.

Genau deshalb ist das Onboarding-Erlebnis diagnostisch so interessant. Ein komplexes, gut strukturiertes System kann am Anfang überwältigend sein. Mit wachsendem Wissen sollte dein mentales Modell jedoch komprimieren: Viele einzelne Beobachtungen lassen sich durch wenige stabile Regeln erklären.

Im Big Ball of Mud kann die Lernkurve umgekehrt verlaufen.

Du weißt immer mehr.

Aber du kannst immer weniger aus diesem Wissen ableiten.

Natürlich beginnt kaum jemand seine erste Woche in einem Projekt mit einer Architekturforensik.

Es beginnt eher mit kleinen Irritationen.

„Warum hängt das davon ab?“

Dann:

„Warum bin ich jetzt in dieser Datei?“

Später:

„Warum kennt eigentlich jeder diesen Service?“

Und irgendwann:

„Warum kennt hier jeder jeden?“

Das Gefühl ist ein sinnvoller Ausgangspunkt. Es ist nur kein ausreichendes Ergebnis.

Deshalb lohnt sich die Kombination verschiedener Perspektiven:

  • sichtbare Abhängigkeiten,
  • tatsächliche Änderungsmuster,
  • Slice-Grenzen,
  • Layer-Grenzen,
  • Gravitationszentren,
  • Verständlichkeitsradius,
  • Änderungsradius,
  • Ownership.

Erst wenn mehrere davon dieselbe Geschichte erzählen, wird aus Irritation ein struktureller Verdacht.

Eine horizontale Folge typischer Beobachtungen beim Einstieg in ein unbekanntes Frontend: vom anfänglichen Nichtverstehen über unerklärliche Abhängigkeiten und verlorene Orientierung bis zur technischen Analyse, die ein systemisches Muster sichtbar macht.

Der Verdacht beginnt oft mit einem Gefühl. Die Diagnose sollte nicht beim Gefühl enden.

Damit bleibt die unangenehme Frage: Was davon ist nun tatsächlich ein starkes Indiz?

Eine wissenschaftlich validierte Big-Ball-of-Mud-Skala gibt es dafür nicht. Die folgende Gewichtung ist deshalb bewusst eine Architekturheuristik und kein diagnostischer Grenzwert.

Für sich allein sagen diese Beobachtungen erstaunlich wenig:

  • eine sehr große Component,
  • ein God Service,
  • eine problematische Base Class,
  • fehlendes Layering in einem isolierten Feature,
  • einzelne Dependency Cycles,
  • veraltete Framework-Patterns,
  • selbstgebaute Infrastruktur,
  • eine schwer verständliche fachliche Domäne.

Jeder dieser Punkte kann ein erhebliches Architekturproblem darstellen. Entscheidend ist aber: Besitzt das Problem noch eine Grenze?

Das Planungs-Feature vom Anfang kann miserabel strukturiert sein und trotzdem innerhalb eines funktionierenden fachlichen Slices liegen. Ein global verwendeter technischer Service kann hohe Zentralität besitzen und dennoch eine einzige, präzise Verantwortung erfüllen.

Interessanter wird es, wenn dieselben Probleme wiederholt und grenzüberschreitend auftreten:

  • Das gleiche Muster findet sich in vielen fachlichen Slices.
  • Components greifen systematisch auf mehrere fremde Domains zu.
  • Business Rules können praktisch überall liegen.
  • Zentrale Services werden von einem großen Teil der Anwendung verwendet und sammeln gleichzeitig unterschiedliche Verantwortlichkeiten.
  • Cross-Slice-Abhängigkeiten besitzen kaum noch eine erkennbare Richtung.
  • Kleine Änderungen benötigen regelmäßig Wissen über weit entfernte Bereiche.
  • Architekturregeln besitzen so viele etablierte Ausnahmen, dass ihre Vorhersagekraft gering wird.
  • Architekturdiagramm und tatsächliche Dependencies erzählen unterschiedliche Geschichten.

Hier verschiebt sich die Diagnose vom lokalen Designproblem zur Struktur des Gesamtsystems.

Besonders ernst wird die Kombination, wenn mehrere grundlegende Ordnungsmechanismen gleichzeitig versagen:

  • Große Teile der Anwendung befinden sich in gemeinsamen Dependency Cycles oder Strongly Connected Components.
  • Fachliche Grenzen sind technisch kaum noch wirksam.
  • Layer besitzen keine verlässliche Bedeutung mehr.
  • Ownership zentraler Bereiche lässt sich weder organisatorisch noch strukturell sinnvoll bestimmen.
  • Kleine Änderungen haben regelmäßig unerwartete systemweite Auswirkungen.
  • Neue Entwickler lernen mit der Zeit immer mehr Sonderfälle, entwickeln aber trotzdem kein stabiles mentales Modell.
  • Mehrere dieser Effekte treten gleichzeitig auf und verstärken einander.

Der entscheidende Punkt ist das Zusammenspiel.

Eine große SCC allein ist keine mathematische Definition eines Big Ball of Mud. Ein hoher Fan-in ist keine. Eine hohe Änderungsrate ist keine. Zehn Layer-Verstöße sind keine.

Es gibt deshalb auch keine seriöse Regel nach dem Muster:

Ab fünf Symptomen besitzt das System einen Big Ball of Mud.

Der Big Ball of Mud ist ein Systemzustand, kein einzelner Messwert.

Wenn ich heute ein unbekanntes Frontend untersuchen müsste, würde ich deshalb nicht mit der Frage beginnen, welche schlechten Patterns ich finde.

Davon findet man fast immer welche.

Ich würde versuchen herauszufinden, ob die wichtigsten Formen struktureller Begrenzung noch funktionieren.

Begrenzen fachliche Slices Abhängigkeiten? Nicht vollständig, aber so, dass Integrationspunkte und Richtungen nachvollziehbar bleiben?

Begrenzen Layer Verantwortlichkeiten? Kann ich halbwegs zuverlässig vorhersagen, wo Business Rules, UI-State, Orchestrierung und Infrastruktur liegen?

Besitzen Abhängigkeiten eine erkennbare Richtung? Oder entstehen große gegenseitige Abhängigkeitsräume?

Bleibt Reasoning lokal? Wie viel der Anwendung muss ich verstehen, um einen kleinen fachlichen Ablauf zu erklären?

Bleiben Änderungen lokal? Wie häufig zwingt mich ein lokales Ticket in fachlich entfernte Teile des Systems?

Sind zentrale Bausteine zentral, weil ihre Verantwortung zentral ist? Oder sind sie lediglich Sammelstellen historischer Bequemlichkeit?

Kann ich Ownership erkennen? Ist nachvollziehbar, wer eine Entscheidung besitzt und welche anderen Teile sie lediglich konsumieren?

Und schließlich:

Erklärt die sichtbare Architektur noch die reale Software?

Das ist möglicherweise die wichtigste Frage überhaupt.

Denn ein System muss nicht perfekt sein, um eine Architektur zu besitzen. Es darf Legacy enthalten. Es darf hässliche Features besitzen. Es darf Übergangslösungen, historische Kompromisse und technische Schulden geben.

Solange seine Grenzen noch Bedeutung besitzen, existiert eine Grundlage für Reasoning.

Ein Big Ball of Mud beginnt dort, wo immer weniger ausgeschlossen werden kann.

Jede Responsibility könnte überall liegen.

Jeder Bereich könnte von jedem anderen abhängen.

Jede Änderung könnte unerwartet irgendwo anders wirken.

Und jede Regel benötigt zunächst die Ergänzung:

„Außer natürlich …“

Vielleicht hast du dein eigenes Projekt inzwischen an unangenehm vielen Stellen wiedererkannt. Einzelne Symptome reichen trotzdem nicht für die Diagnose eines Big Ball of Mud. Wenn du die Beobachtungen etwas systematischer gegeneinander abwägen möchtest, gibt es dafür einen kleinen Architektur-Selbstcheck.

Habe ich einen Big Ball of Mud? →

Am Ende landen wir wieder bei deinem ersten Tag im Projekt.

Du kommst in ein großes System und verstehst zunächst nichts.

Das ist normal.

In einem strukturierten komplexen System verändert sich dieser Zustand mit der Zeit. Du lernst die Domäne. Du erkennst Verantwortlichkeiten. Du verstehst die Grenzen und die Abhängigkeitsrichtungen. Aus vielen einzelnen Dateien entsteht langsam ein Modell.

Dieses Modell macht neue Teile des Systems vorhersagbarer.

Du kennst vielleicht eine Component noch nicht, weißt aber, welche Aufgaben sie wahrscheinlich besitzen darf. Du hast einen Slice noch nie verändert, kannst aber einschätzen, wo seine fachlichen Regeln liegen sollten. Du siehst eine Abhängigkeit und kannst begründen, warum ihre Richtung sinnvoll ist.

Dein Wissen wächst, aber der notwendige mentale Kontext wird kleiner.

Im Big Ball of Mud kann das Gegenteil passieren.

Nach einigen Monaten kennst du unglaublich viele Details. Du weißt, dass Methode A vor Methode B aufgerufen werden muss. Du kennst die drei Sonderfälle im globalen Cache. Du weißt, warum eine bestimmte Navigation niemals direkt ausgelöst werden darf. Du kennst die Person, die erklären kann, warum BaseComponent diesen einen fachlichen Zustand hält.

Du weißt mehr als am ersten Tag.

Aber daraus entsteht kein verlässliches Modell.

Einen Big Ball of Mud erkennst du deshalb nicht daran, dass irgendwo eine 5.000-Zeilen-Datei liegt, ein Entwickler das Framework merkwürdig verwendet oder ein Feature schlecht strukturiert ist. Solche Probleme können gravierend sein, besitzen aber häufig noch eine erkennbare Grenze.

Verdächtig wird ein System, wenn diese Grenzen selbst verschwinden.

Ein Big Ball of Mud zeigt sich im Frontend vor allem im Zusammenspiel mehrerer Formen struktureller Entgrenzung: Fachliche Slices begrenzen Abhängigkeiten nicht mehr, Layer begrenzen Verantwortlichkeiten nicht mehr, Änderungen bleiben nicht lokal, zentrale Bausteine sammeln immer mehr fremde Zuständigkeiten, die Richtung von Abhängigkeiten geht verloren und Ownership lässt sich immer schwieriger bestimmen.

Dann ist das eigentliche Problem nicht mehr eine schlechte Component, ein God Service oder ein falscher Import.

Das Problem ist, dass die Architektur immer weniger Möglichkeiten zuverlässig ausschließt.

Und wenn du nach Monaten sehr viel über ein System weißt, aber immer noch nicht zuverlässig sagen kannst, wo eine Verantwortung beginnt, wo sie endet und wen sie kennen darf, dann hast du wahrscheinlich nicht einfach nur ein Onboarding-Problem.

  • Brian Foote, Joseph W. Yoder: Big Ball of Mud. Technical Report WUCS-97-34, 1997; später als Kapitel 29 in Neil Harrison, Brian Foote, Hans Rohnert (Hrsg.): Pattern Languages of Program Design 4. Addison-Wesley, 2000.
  • Ruiyin Li, Peng Liang, Mohamed Soliman, Paris Avgeriou: Understanding Software Architecture Erosion: A Systematic Mapping Study. Journal of Software: Evolution and Process 34(3), 2022, e2423. DOI: 10.1002/smr.2423.
  • Ruiyin Li, Peng Liang, Mohamed Soliman, Paris Avgeriou: Understanding Architecture Erosion: The Practitioners’ Perceptive. Proceedings of the 29th IEEE/ACM International Conference on Program Comprehension (ICPC), 2021, S. 24–35. DOI: 10.1109/ICPC52881.2021.00037.
  • David L. Parnas: On the Criteria To Be Used in Decomposing Systems into Modules. Communications of the ACM 15(12), 1972, S. 1053–1058. DOI: 10.1145/361598.361623.
  • Harald Gall, Karin Hajek, Mehdi Jazayeri: Detection of Logical Coupling Based on Product Release History. Proceedings of the International Conference on Software Maintenance (ICSM), 1998, S. 190–197. DOI: 10.1109/ICSM.1998.738508.
  • Neeraj Sangal, Ev Jordan, Vineet Sinha, Daniel Jackson: Using Dependency Models to Manage Complex Software Architecture. OOPSLA 2005, S. 167–176. DOI: 10.1145/1094811.1094824.