Wenn jede Änderung überall weh tut
Zwei Tage.
So lautete die Schätzung für ein Ticket, das auf den ersten Blick tatsächlich nach zwei Tagen aussah. Ein Benutzer sollte einen Modal-Dialog sehen. Seine Auswahl sollte in einem Settings-Objekt gespeichert werden. Traf er keine Auswahl, sollte der Dialog nach 30 Tagen erneut erscheinen.
Der Entwickler, der das Ticket übernahm, war ausgesprochen erfahren, technisch stark und besaß ein außergewöhnlich gutes Verständnis des bestehenden Systems. Wenn es in dieser Codebasis jemanden gab, dem ich zugetraut hätte, versteckte Zusammenhänge früh zu erkennen, dann ihm.
Nach ungefähr zwei Wochen ging er in den Urlaub. Sein Stand:
„Ich bin fertig, bitte QA testen.“
Danach folgten ungefähr drei weitere Wochen aus QA, Korrekturen und erneuten Tests. Über diesen Zeitraum fanden wir rund 40 Fehler oder unerwartete Verhaltensweisen, bevor das Feature zuverlässig funktionierte.
Das ist eine persönliche Erfahrung. Die Zahl 40 ist keine typische Kennzahl für einen Big Ball of Mud, fünf Wochen sind kein Schwellenwert und eine falsche Schätzung beweist noch keine schlechte Architektur.
Interessant an diesem Ticket war etwas anderes: Die fachliche Anforderung war nach fünf Wochen noch immer dieselbe wie am ersten Tag. Der Dialog war nicht plötzlich komplizierter geworden, die 30-Tage-Regel nicht zu einem komplexen Algorithmus mutiert. Es war keine neue Fachlichkeit hinzugekommen, die den Aufwand erklären konnte. Gewachsen war der Teil des Systems, den wir verstehen mussten.
Fachlich war die Änderung klein. Technisch besaß sie keinen verlässlichen Rand.
Nicht der Dialog war das Problem
Abschnitt betitelt „Nicht der Dialog war das Problem“Der entscheidende Fehler lag im Umgang mit den Settings.
Dasselbe fachliche Settings-Konzept wurde an zwei unterschiedlichen Stellen gespeichert. Beide Stellen konnten unabhängig voneinander persistieren. Dadurch konnten im Backend zwei Settings-Objekte entstehen, obwohl fachlich nur ein Zustand gemeint war.
Beim späteren Laden gab es wiederum keine Regel, die anhand einer fachlichen Identität eindeutig bestimmte, welches dieser Objekte maßgeblich war. Stattdessen verwendete der betreffende Code schlicht das erste Element der zurückgegebenen Collection:
index 0
Nur existierte für diese Reihenfolge kein fachlicher Vertrag.
Ob das erwartete oder das andere Settings-Objekt an Position null stand, war damit keine Eigenschaft des Geschäftsprozesses, sondern hing von einem technischen Detail ab, für das es keinen fachlichen Vertrag gab.
Das allein erklärte allerdings noch nicht alle Effekte. Der Zustand wurde an mehreren Stellen gelesen und geschrieben. Einzelne Schreibvorgänge lösten weitere Reaktionen aus. Timing spielte eine Rolle. Unter bestimmten Abläufen entstanden Race Conditions. Eine Korrektur konnte dadurch einen Fehler beseitigen und gleichzeitig einen anderen Zustand sichtbar machen, der zuvor von der bisherigen Reihenfolge oder einem anderen Timing verdeckt worden war.
Die Race Conditions waren dabei nicht das eigentliche Architekturmerkmal. Race Conditions können auch in hervorragend strukturierten Systemen auftreten. Ebenso sind mehrere Writer auf einen Zustand für sich genommen noch kein Beweis für einen Big Ball of Mud.
Entscheidend war, dass wir die Frage, die bei diesem Ticket eigentlich banal hätte sein sollen, zunehmend schlechter beantworten konnten:
Wo endet diese Änderung?
Gehörte sie zum Dialog? Zum Settings-Service? Zu einem global verwendeten Zustand? Zum Persistenzmodell? Zu den anderen Stellen, die dasselbe Objekt schrieben? Zu allen Readern? Zu den Abläufen, deren Timing sich durch eine Korrektur verändern konnte?
Das Problem war nicht der Dialog, sondern ein Zustand, dessen Ownership, Lebenszyklus und Persistenz nicht eindeutig begrenzt waren.

Die Fachlichkeit bleibt klein. Der technische Wirkungsraum kann trotzdem wachsen.
Ein Ticket beweist noch keinen Big Ball of Mud
Abschnitt betitelt „Ein Ticket beweist noch keinen Big Ball of Mud“Ein einzelner solcher Vorfall reicht für eine Diagnose nicht aus.
Auch in einem gut strukturierten System kann sich eine Zwei-Tage-Schätzung als falsch herausstellen. Anforderungen können während der Implementierung präzisiert werden. Ein scheinbar einfacher Zustand kann unerwartete Nebenbedingungen besitzen. Nebenläufigkeit kann Fehler erzeugen, die erst unter bestimmten Timings auftreten. Ein Entwickler kann eine falsche Annahme treffen. QA kann nach der Implementierung weitere Fälle entdecken.
Architektonisch interessant wird die Beobachtung deshalb erst, wenn sich ein Muster wiederholt: Eigentlich lokale Anforderungen benötigen regelmäßig Wissen über weit entfernte Teile des Systems. Zuständigkeiten lassen sich nicht eindeutig bestimmen. Auswirkungen können vor der Änderung nur schlecht begrenzt werden. Regression muss erheblich weiter gedacht werden als die Fachlichkeit des Tickets vermuten lässt.
Ein Vorfall illustriert den Mechanismus. Die Wiederholung macht ihn zu einer Systemeigenschaft.
Genau dieser Mechanismus lässt sich auch in der Forschung zur Softwarewartung wiederfinden. Change Impact Analysis beschäftigt sich mit der Frage, welche potenziellen Konsequenzen eine Änderung besitzt und welche Teile eines Systems deshalb betrachtet werden müssen. Das klingt abstrakt, beschreibt aber ziemlich genau die praktische Frage hinter jedem Wartungsticket: Was kann diese Änderung noch betreffen?
Bereits Parnas argumentierte 1972 für Modularisierung nicht primär als ästhetische Aufteilung von Quellcode, sondern als Mittel, Systeme verständlicher und gegenüber Änderungen flexibler zu machen. Entscheidend ist dabei, welche Entwurfsentscheidungen hinter einer Modulgrenze verborgen werden und damit nicht durch das gesamte System wirken müssen.
Ein Big Ball of Mud verliert zunehmend genau diese Eigenschaft. Nicht unbedingt überall und nicht bei jedem Ticket. Aber häufig genug, dass fachliche und technische Reichweite einer Änderung nicht mehr zuverlässig zusammenpassen.
Drei Radien einer Änderung
Abschnitt betitelt „Drei Radien einer Änderung“Für den Entwicklungsalltag hilft ein einfaches Modell. Eine Änderung besitzt nicht nur eine Größe.
Sie besitzt mindestens drei verschiedene Radien.
Der fachliche Änderungsradius
Abschnitt betitelt „Der fachliche Änderungsradius“Der erste Radius beschreibt das, was die Anforderung tatsächlich verlangt.
Im Beispiel war das überschaubar:
- einen Dialog anzeigen,
- eine Auswahl speichern,
- den Dialog nach 30 Tagen erneut anzeigen, wenn keine Auswahl gespeichert wurde.
Mehr Fachlichkeit steckte zunächst nicht darin.
Dieser Radius sagt noch nichts darüber aus, wie viele Dateien verändert werden. Er beschreibt die fachliche Reichweite: Welche Regel, welcher Zustand und welches Verhalten sollen sich aus Sicht des Benutzers ändern?
Der technische Änderungsradius
Abschnitt betitelt „Der technische Änderungsradius“Der zweite Radius beschreibt die Teile des Systems, die tatsächlich angepasst werden müssen.
Das können bei einer lokalen Anforderung durchaus mehrere Komponenten sein. Eine UI benötigt vielleicht einen neuen Zustand, eine API wird erweitert, Persistenz muss angepasst und ein Test ergänzt werden. Dass eine Anforderung mehrere Dateien oder Layer berührt, ist deshalb kein Architekturproblem.
In einer tragfähigen Architektur lässt sich dieser technische Radius aber normalerweise begründen.
Dieser Zustand gehört hierhin. Diese Stelle besitzt ihn. Über diese Schnittstelle wird er verändert. Dort wird er persistiert. Diese Consumer sind betroffen. Hinter dieser Grenze endet die Änderung.
Der technische Radius muss nicht identisch mit dem fachlichen Radius sein. Er sollte aber nachvollziehbar aus ihm entstehen.
Der Verständnis- und Regressionsradius
Abschnitt betitelt „Der Verständnis- und Regressionsradius“Der dritte Radius ist im Entwicklungsalltag häufig der teuerste.
Er beschreibt all jene Bereiche, die nicht unbedingt verändert werden, die aber verstanden, überprüft oder getestet werden müssen, bevor eine Änderung verantwortbar ausgeliefert werden kann.
Ein Entwickler kann am Ende nur drei Dateien committen und trotzdem zwanzig weitere lesen müssen.
Vielleicht muss er herausfinden, wer denselben Zustand noch schreibt. Vielleicht gibt es einen zweiten Ablauf, der denselben Service verwendet. Vielleicht steckt eine relevante Seiteneffektkette in einer Base Class. Vielleicht muss ein anderes Team erklären, welche implizite Annahme hinter einer API steckt. Vielleicht verlangt QA Regressionstests über mehrere User Flows, weil niemand sicher ausschließen kann, dass sie betroffen sind.
Dann kann der Patch klein bleiben und die Änderung trotzdem teuer werden.
Der teuerste Teil einer Änderung kann in einem Big Ball of Mud außerhalb des eigentlichen Patches liegen.

Der eigentliche Patch kann klein bleiben, obwohl Verständnis und Regression große Teile des Systems erfassen müssen.
Nicht die Anzahl der Dateien ist das Problem
Abschnitt betitelt „Nicht die Anzahl der Dateien ist das Problem“Damit wird auch verständlich, warum die Größe eines Diffs nur sehr begrenzt etwas über die architektonische Qualität einer Änderung aussagt.
Eine API-Umstellung kann hundert Dateien verändern und trotzdem ausgesprochen gut kontrollierbar sein. Vielleicht wird ein klar definiertes Interface mechanisch migriert. Jeder betroffene Consumer ist bekannt. Compiler, Tests und statische Analyse zeigen zuverlässig, wo Anpassungen erforderlich sind. Das Diff ist groß, der Wirkungsraum aber klar.
Umgekehrt können drei veränderte Zeilen hochriskant sein.
Ein Boolean wird gesetzt. Ein globaler Service reagiert darauf. Ein anderer Prozess liest denselben Zustand später aus. Eine Basisklasse enthält einen impliziten Seiteneffekt. Eine weitere Stelle verlässt sich auf eine bestimmte Ausführungsreihenfolge.
Das Diff zeigt davon vielleicht fast nichts.
Nicht die Größe des Diffs ist das Problem. Die Unsicherheit über seine Wirkung ist es.
Diese Unterscheidung ist wichtig, weil wir Architektur sonst anhand der falschen Oberfläche beurteilen. Viele geänderte Dateien sehen nach großer Veränderung aus. Wenige geänderte Dateien sehen nach lokaler Veränderung aus.
Aber die sichtbare Änderung ist nicht dasselbe wie ihre tatsächliche Wirkungsfläche.
Gall, Hajek und Jazayeri zeigten bereits Ende der 1990er-Jahre am Release-Verlauf eines großen Telekommunikationssystems, dass zwischen Modulen relevante „logische“ Abhängigkeiten bestehen können, die sich nicht vollständig aus offensichtlichen statischen Beziehungen ableiten lassen. Wiederkehrendes gemeinsames Änderungsverhalten kann damit Beziehungen sichtbar machen, die Quellcode oder Dokumentation allein nicht klar ausdrücken.
Für den Alltag bedeutet das: Zwei Stellen müssen nicht im selben Ordner liegen, dieselbe Klasse importieren oder über eine offensichtliche Schnittstelle verbunden sein, damit Änderungen an ihnen praktisch zusammengehören.
Das Repository kann lokal aussehen und sich trotzdem nicht lokal verändern lassen.
Locality of Change
Abschnitt betitelt „Locality of Change“Im vorherigen Diagnoseartikel dieser Serie war Locality of Change bereits ein Indikator dafür, ob fachliche Grenzen im Code noch wirksam sind.
Hier interessiert vor allem die wirtschaftliche Konsequenz.
Locality of Change bedeutet nicht:
Eine fachliche Änderung darf nur eine Datei betreffen.
Eine solche Regel wäre nicht nur unrealistisch, sondern würde bei vielen Architekturen die falschen Anreize setzen.
Gemeint ist etwas anderes:
Fachlich lokale Änderungen sollten einen technisch begrenzbaren Wirkungsraum besitzen.
Wenn ich das Verhalten eines Settings ändere, muss ich möglicherweise UI, Application Layer, State, Persistenz und Tests verändern. Das kann völlig gesund sein. Entscheidend ist, ob ich weiß, warum genau diese Bereiche dazugehören und warum andere Bereiche nicht dazugehören.
Architektur erzeugt in diesem Sinne nicht nur Struktur, sondern auch belastbare Ausschlüsse.
Sie erlaubt Aussagen wie:
Dieser Zustand wird ausschließlich hier verändert.
Diese Schnittstelle ist der einzige Weg zur Persistenz.
Dieser Slice besitzt diese fachliche Entscheidung.
Diese Consumer hängen von diesem Vertrag ab.
Hinter dieser Grenze muss ich für diese Änderung nicht weiter suchen.
Gerade der letzte Satz ist wertvoll: Ein System wird nicht nur dadurch wartbar, dass relevante Zusammenhänge auffindbar sind. Es wird auch dadurch wartbar, dass Entwickler große Teile des Systems zuverlässig als nicht relevant ausschließen können.
Je weniger solche Ausschlüsse möglich sind, desto größer wird der Raum, der vorsichtshalber betrachtet werden muss.
Die Fachlichkeit ist lokal.
Das System macht sie global.
Change Amplification
Abschnitt betitelt „Change Amplification“John Ousterhout verwendet für ein verwandtes Phänomen den Begriff Change Amplification: Eine scheinbar einfache Änderung führt zu Änderungen an vielen verschiedenen Stellen. In seinen Lehrmaterialien steht der Begriff neben zwei weiteren Symptomen von Komplexität: hohem kognitivem Aufwand und „unknown unknowns“, also relevanten Zusammenhängen, von denen ein Entwickler zunächst nicht einmal weiß, dass er sie kennen müsste.
Für einen Big Ball of Mud ist dieser Gedanke hilfreich, solange daraus keine vermeintlich exakte Metrik wird.
Change Amplification bedeutet hier nicht, dass wir Dateien zählen und ab einem bestimmten Faktor eine Architektur für schlecht erklären.
Die Verstärkung kann an unterschiedlichen Stellen stattfinden.
Eine kleine fachliche Änderung kann zusätzlichen Analyseaufwand erzeugen. Sie kann entfernte technische Änderungen notwendig machen. Sie kann den Testumfang vergrößern. Sie kann zusätzliche Reviews erfordern. Sie kann Abstimmungen mit Menschen notwendig machen, die das ursprüngliche Ticket fachlich gar nicht betrifft. Sie kann den Release riskanter erscheinen lassen und damit zusätzliche Absicherung auslösen.
Der entscheidende Punkt ist nicht, dass all diese Dinge bei jeder Änderung auftreten. Relevant wird das Muster dort, wo sie bei eigentlich lokalen Änderungen regelmäßig auftreten. Dann betrachten wir nicht mehr nur einzelne schwierige Tickets, sondern eine Eigenschaft des Systems.
Vom kleinen Ticket zur großen Wirkungsfläche
Abschnitt betitelt „Vom kleinen Ticket zur großen Wirkungsfläche“Der Weg dorthin beginnt oft unspektakulär.
Eine Anforderung ist lokal. Während der Analyse stellt sich heraus, dass ein weiterer Zustand berücksichtigt werden muss. Dieser Zustand wird an einer zweiten Stelle geschrieben. Dadurch kommt ein zusätzlicher Service in den relevanten Bereich. Dieser Service wird von einem anderen Flow verwendet. Also muss geprüft werden, ob sich dessen Verhalten verändert. Damit wächst die Regression. Ein Reviewer erkennt eine weitere Abhängigkeit. QA nimmt zusätzliche Szenarien auf.
Aus einer kleinen fachlichen Änderung entsteht so schrittweise eine wesentlich größere Absicherungsaufgabe.
Fachlich kleine Änderung
→ größerer Analysebereich
→ weitere technische Berührungspunkte
→ größere potenzielle Wirkungsfläche
→ mehr Regression
→ mehr Review und Abstimmung
→ höhere Unsicherheit beim Release
→ längere Lead Time
Diese Kette ist kein Naturgesetz.
Eine zusätzliche Abhängigkeit muss nicht zwangsläufig einen Bug verursachen. Ein größerer Regressionstest muss einen Release nicht verzögern. Ein Shared Service ist nicht automatisch schlechte Architektur.
Der Mechanismus wird problematisch, wenn technische Grenzen ihre Fähigkeit verlieren, diese Kette früh zu stoppen.
Dann ist nicht mehr klar, wann ausreichend analysiert wurde.
Und damit sind wir wieder bei derselben Frage:
Wo endet diese Änderung?
Warum QA immer neue Fehler finden konnte
Abschnitt betitelt „Warum QA immer neue Fehler finden konnte“Bei unserem Modal-Dialog war die Implementierung nach zwei Wochen nicht deshalb wertlos, weil der Entwickler schlecht gearbeitet hätte.
Das Gegenteil war der Fall.
Er hatte bereits einen erheblichen Teil der versteckten Zusammenhänge gefunden und berücksichtigt. Trotzdem konnte QA anschließend wiederholt weitere Fehlerzustände erzeugen.
Der Grund war nicht ein einzelner misslungener Patch, sondern dass die tatsächliche Wirkungsfläche vor der Änderung nicht zuverlässig bestimmbar gewesen war.
Ein Fix sorgte beispielsweise dafür, dass ein Zustand korrekt geschrieben wurde. Damit veränderte sich aber das Timing eines nachfolgenden Ablaufs. Unter diesem Ablauf wurde eine Race Condition sichtbar. Die nächste Korrektur beeinflusste wiederum einen weiteren Writer oder Reader. Dadurch trat ein Zustand auf, den der vorherige Fehler bislang verdeckt hatte.
Vereinfacht entstand das Muster:
Fix → Seiteneffekt → Fix → weiterer Seiteneffekt.
Nicht jeder Fix war falsch. Das System machte lediglich schwer vorhersagbar, wo seine Folgen endeten.
Genau deshalb ist die Zahl von ungefähr 40 gefundenen Problemen für mich heute weniger interessant als der Prozess dahinter. Eine große Zahl Bugs kann viele Ursachen haben. Der architektonisch interessante Teil war, dass immer wieder neue Teile desselben Wirkungsraums sichtbar wurden, nachdem wir glaubten, ihn bereits verstanden zu haben.

Nicht der Dialog wurde immer größer. Der für ihn relevante Systemkontext wurde sichtbar.
Locality of Reasoning wird zu Arbeitszeit
Abschnitt betitelt „Locality of Reasoning wird zu Arbeitszeit“Eng mit Locality of Change verbunden ist die bereits eingeführte Locality of Reasoning.
Kann ein fachlicher Bereich weitgehend aus sich selbst heraus verstanden werden, muss ein Entwickler für eine lokale Änderung nur ein begrenztes mentales Modell aufbauen. Natürlich existieren Abhängigkeiten nach außen. Entscheidend ist aber, dass diese Abhängigkeiten über Verträge begrenzt werden und ihre Interna für die konkrete Änderung weitgehend irrelevant bleiben dürfen.
Fehlt diese Eigenschaft, muss für eine lokale Änderung immer mehr nichtlokales Verhalten verstanden werden.
Der Entwickler arbeitet dann nicht nur am Ticket, sondern rekonstruiert zusätzlich das System.
Welche Stelle besitzt diesen Zustand? Wer schreibt noch darauf? Welche Reihenfolge wird vorausgesetzt? Ist der Service tatsächlich zustandslos? Welche Basisklasse greift ein? Welche anderen Abläufe verwenden dasselbe Objekt? Welche Tests repräsentieren echte Invarianten und welche bilden nur den heutigen Implementierungszustand ab?
Empirische Forschung zur Program Comprehension untersucht genau solche Informationsbedarfe bei Wartungs- und Änderungsaufgaben. Sillito und Kollegen analysierten beispielsweise, welche Fragen Entwickler während Change Tasks über eine bestehende Codebasis beantworten müssen. Die eigentliche Codeänderung ist damit nur ein Teil der Wartungsarbeit; zuvor muss genügend relevantes Wissen über das bestehende System aufgebaut werden.
Das ist bei Legacy-Systemen nicht grundsätzlich vermeidbar.
Problematisch wird es, wenn dieses mentale Modell bei kleinen Änderungen regelmäßig einen erheblichen Teil des Gesamtsystems umfassen muss.
Ein lokal kleines Ticket kann dann einen systemweiten Verständnisraum benötigen.
Technical Debt erzeugt Arbeit, die im Feature nicht vorkommt
Abschnitt betitelt „Technical Debt erzeugt Arbeit, die im Feature nicht vorkommt“Damit schließt sich auch der Kreis zum Technical-Debt-Begriff aus den vorherigen Artikeln.
Die fachliche Aufgabe unseres Tickets lautete:
Modal-Dialog und Setting.
Die tatsächliche Arbeit bestand zusätzlich darin, State Ownership zu rekonstruieren, mehrere Writer zu finden, das Persistenzverhalten zu verstehen, eine implizite Reihenfolgeannahme zu entdecken, Race Conditions zu reproduzieren, Regressionen zu analysieren und zusätzliche Tests durchzuführen.
Nichts davon machte den Modal-Dialog für den Benutzer wertvoller.
Diese Arbeit war notwendig, damit wir den gewünschten fachlichen Wert trotz der bestehenden Struktur zuverlässig ausliefern konnten.
Genau an dieser Stelle wird Technical Debt als ökonomischer Begriff interessant. Martini, Bosch und Chaudron untersuchten Architectural Technical Debt in mehreren großen Unternehmen und beschreiben architektonische Entscheidungen, die kurzfristige Lieferung unterstützen können, langfristig aber die weitere Feature-Entwicklung behindern.
Besker, Martini und Bosch untersuchten später konkret, wo Entwickler durch Technical Debt zusätzliche Zeit verlieren. In ihrer longitudinalen Studie und Replikation berichteten Entwickler von erheblichem zusätzlichem Aufwand; besonders häufig floss zusätzliche Zeit in Tests. Die konkreten Prozentwerte dieser Untersuchungen lassen sich nicht auf jedes Unternehmen übertragen, aber der beobachtete Mechanismus ist für unseren Zusammenhang relevant: Technical Debt zeigt sich nicht nur in sichtbarem Refactoringbedarf, sondern in zusätzlicher Arbeit während normaler Entwicklung.
Damit verändert sich die Frage.
Nicht:
Warum braucht dieser Entwickler fünf Wochen für einen Dialog?
Sondern:
Warum benötigt dieses System für einen Dialog fünf Wochen Entwicklungs- und Absicherungsarbeit?
Das ist mehr als eine höflichere Formulierung. Es verändert die Einheit, die wir untersuchen.
Die erste Frage sucht den Fehler beim Menschen oder beim Estimate. Die zweite fragt nach dem System, das aus einer kleinen fachlichen Anforderung einen großen technischen Arbeitsraum erzeugt.
Schätzungen verlieren ihre Grundlage
Abschnitt betitelt „Schätzungen verlieren ihre Grundlage“Genau deshalb werden Aufwandsschätzungen in solchen Systemen schwierig.
Eine Schätzung ist immer eine Aussage über einen erwarteten Änderungsraum.
Wer für das Modal-Ticket zwei Tage schätzt, stellt sich vielleicht folgende Arbeit vor: Dialog ergänzen, vorhandenen Settings-Service verwenden, 30-Tage-Regel implementieren, Tests schreiben.
Unter dieser Annahme können zwei Tage vollkommen plausibel sein.
Erst während der Umsetzung zeigt sich dann:
Der Settings-Zustand besitzt zwei Writer.
Im Backend können zwei Objekte für denselben fachlichen Zweck entstehen.
Beim Lesen entscheidet index 0.
Die Reihenfolge besitzt keine fachliche Garantie.
Weitere Reader reagieren auf denselben Zustand.
Timing verändert das Verhalten.
Damit wurde nicht lediglich ein bekannter Aufwand schlecht geschätzt. Der angenommene Wirkungsraum war falsch: Die sichtbare Struktur hatte eine lokale Änderung suggeriert, die technisch in dieser Form gar nicht existierte.
Ein Big Ball of Mud macht nicht nur Änderungen schwer vorhersehbar. Er macht bereits die Größe der Änderung schwer vorhersehbar.
Das ist ein wichtiger Unterschied. Natürlich kann kein Entwickler vor Beginn eines Tickets alle Probleme kennen. Schätzungen bleiben auch in guten Architekturen unsicher; Softwareentwicklung ist keine deterministische Produktionslinie.
Aber tragfähige Grenzen reduzieren zumindest eine bestimmte Form dieser Unsicherheit: Sie helfen dabei, den relevanten Bereich vor Beginn der Änderung einzugrenzen.
Wenn solche Grenzen fehlen, schätzt man nicht nur, wie aufwendig die Arbeit innerhalb eines bekannten Bereichs sein wird, sondern zusätzlich, wie groß dieser Bereich überhaupt ist.
Die Regression Surface
Abschnitt betitelt „Die Regression Surface“Für den dritten Radius verwende ich im Folgenden bewusst einen Arbeitsbegriff: Regression Surface.
Damit meine ich den Bereich des Systems, der nach einer Änderung vernünftigerweise als potenziell betroffen betrachtet werden muss.
Das ist keine harte mathematische Fläche und in diesem Artikel auch keine standardisierte Softwaremetrik.
Der Begriff hilft lediglich, eine praktische Frage zu formulieren:
Was muss ich nach dieser Änderung überprüfen, obwohl ich es möglicherweise gar nicht verändert habe?
In einem gut abgegrenzten Slice kann diese Regression Surface relativ klein sein. Eine Änderung betrifft einen fachlichen Zustand, dessen Owner bekannt ist. Die Consumer laufen über definierte Schnittstellen. Tests sichern die relevanten Invarianten ab. Andere Bereiche können mit hinreichender Sicherheit ausgeschlossen werden.
Fehlen diese Grenzen, wächst die potenzielle Wirkungsfläche.
Dann muss QA vielleicht weitere Flows testen. Ein Reviewer prüft zusätzliche Services. Ein Entwickler analysiert historische Sonderfälle. Ein anderes Team wird gefragt, ob sein Bereich von einem gemeinsam verwendeten Zustand abhängt. Releases werden vorsichtiger, weil die Liste dessen, was nicht betroffen ist, weniger belastbar wird.
Auch Change Impact Analysis setzt genau an diesem Problem an: Sie versucht potenzielle Konsequenzen einer Änderung zu bestimmen, damit relevante Teile eines Systems identifiziert werden können. Dass hierfür in Forschung und Praxis seit Jahrzehnten eigene Analyseverfahren entwickelt werden, zeigt bereits, dass der sichtbare Patch und seine mögliche Wirkung zwei unterschiedliche Dinge sind.
Die Architektur entscheidet mit darüber, wie schwierig diese Analyse ist.
Wo endet die Änderung?
Abschnitt betitelt „Wo endet die Änderung?“Damit kommen wir zur vielleicht einfachsten Architekturfrage dieses Artikels.
Wo endet diese Änderung?
Eine tragfähige Architektur sollte darauf keine perfekte, aber eine hinreichend belastbare Antwort geben können.
Dieser fachliche Zustand gehört hierhin.
Diese Stelle besitzt ihn.
Über diese Schnittstelle darf er verändert werden.
Über diesen Weg wird er persistiert.
Diese Consumer hängen davon ab.
Diese Tests sichern seine relevanten Invarianten.
Dahinter endet der Kontext, den ich für diese Änderung berücksichtigen muss.
Das ist keine Forderung nach vollständiger Isolation. Ein reales System besteht aus Abhängigkeiten. Eine Änderung kann bewusst mehrere Slices, Services oder Anwendungen betreffen. Manchmal ist der fachliche Radius selbst groß und ein großer technischer Radius vollkommen angemessen.
Der problematische Fall ist enger:
Die Fachlichkeit ist lokal. Das System macht sie global.
Dann lösen sich die drei Radien voneinander.
Der fachliche Änderungsradius bleibt klein.
Der technische Änderungsradius wächst.
Und noch stärker wächst möglicherweise der Verständnis- und Regressionsradius.

Architektur wird besonders wertvoll dort, wo sie verlässlich sagt, welche Teile des Systems nicht mehr betrachtet werden müssen.
Und was ist mit Coding Agents?
Abschnitt betitelt „Und was ist mit Coding Agents?“Der vorherige Artikel dieser Serie hat gezeigt, dass Coding Agents große Analyseflächen heute besser bearbeiten können, als Menschen es allein oft wirtschaftlich leisten können.
Das verändert den Aufwand, aber nicht den zugrunde liegenden Mechanismus.
Ein Agent kann schneller nach allen Schreibzugriffen auf ein Settings-Objekt suchen. Er kann Reader verfolgen, Services öffnen, Call Chains rekonstruieren und Tests ausführen. Damit wird ein großer Verständnisraum leichter bearbeitbar.
Er beantwortet damit jedoch nicht die architektonische Frage, warum dieser Verständnisraum für eine lokale Änderung überhaupt so groß geworden ist.
Locality of Change bleibt deshalb relevant, auch wenn sich die Werkzeuge verändern.
Der Dialog war nie fünf Wochen kompliziert
Abschnitt betitelt „Der Dialog war nie fünf Wochen kompliziert“Wenn ich heute auf das Ticket zurückblicke, ist genau das der Punkt, der mir daran wichtig erscheint.
Der Modal-Dialog war nicht fünf Wochen kompliziert.
Die fachliche Anforderung war am Ende noch genauso klein wie am Anfang: Dialog anzeigen, Auswahl speichern, nach 30 Tagen erneut fragen, wenn nichts gespeichert wurde.
Teuer wurde der Weg durch ein System, in dem derselbe fachliche Zustand mehrere Writer besaß, die Persistenz keine eindeutige Wahrheit erzeugte und selbst die Auswahl des maßgeblichen Settings von einer nicht garantierten Reihenfolge abhängen konnte.
Der Entwickler hatte nicht fünf Wochen lang Schwierigkeiten, einen Dialog zu programmieren.
Wir brauchten mehrere Wochen, um herauszufinden, was dieser Dialog im restlichen System tatsächlich berührte, und diese Auswirkungen ausreichend abzusichern.
Genau deshalb sollte man einen Big Ball of Mud nicht daran erkennen wollen, dass jede Änderung groß ist. Viele Änderungen bleiben klein. Manche funktionieren auf Anhieb. Manche Bereiche können überraschend stabil sein.
Das Problem liegt in der fehlenden Verlässlichkeit. In einer tragfähigen Architektur besitzt eine lokale Änderung normalerweise auch einen begrenzbaren technischen Wirkungsraum. Im Big Ball of Mud können sich fachlicher Änderungsradius, technischer Änderungsradius und notwendiger Verständnisraum zunehmend voneinander entfernen.
Deshalb ist die interessanteste Frage nach einem solchen Ticket nicht:
Wie viele Dateien wurden geändert?
Sondern:
Wie weit musste das System verstanden und abgesichert werden, um diese lokale Änderung verantwortbar durchzuführen?
Und noch allgemeiner:
Wo kann ich mit hinreichender Sicherheit aufhören, über diese Änderung nachzudenken?
Wenn die Antwort regelmäßig „nirgendwo“ lautet, tut irgendwann tatsächlich jede Änderung überall weh.
Quellen und weiterführende Forschung
Abschnitt betitelt „Quellen und weiterführende Forschung“- 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. Die Arbeit begründet Modularisierung unter anderem mit Verständlichkeit und Änderbarkeit sowie dem Verbergen veränderlicher Entwurfsentscheidungen hinter Modulgrenzen. - Harald Gall, Karin Hajek, Mehdi Jazayeri: Detection of Logical Coupling Based on Product Release History. ICSM 1998, S. 190–198. DOI:
10.1109/ICSM.1998.738508. Die Autoren untersuchen gemeinsame Änderungsmuster im Release-Verlauf eines großen Telekommunikationssystems und zeigen, wie sich damit logische Abhängigkeiten jenseits offensichtlicher statischer Kopplung erkennen lassen. - Cleidson R. B. de Souza, David F. Redmiles: An Empirical Study of Software Developers’ Management of Dependencies and Changes. ICSE 2008, S. 241–250. DOI:
10.1145/1368088.1368122. Die Arbeit ordnet Change Impact Analysis als Identifikation potenzieller Konsequenzen einer Änderung ein und untersucht den praktischen Umgang von Entwicklern mit Abhängigkeiten. - Jonathan Sillito, Gail C. Murphy, Kris De Volder: Questions Programmers Ask During Software Evolution Tasks. SIGSOFT FSE 2006, S. 23–34. DOI:
10.1145/1181775.1181779. Die Studie untersucht, welche Informationen Entwickler über eine Codebasis während konkreter Änderungsaufgaben benötigen. - Antonio Martini, Jan Bosch, Michel Chaudron: Investigating Architectural Technical Debt Accumulation and Refactoring over Time: A Multiple-Case Study. Information and Software Technology 67, 2015, S. 237–253. DOI:
10.1016/j.infsof.2015.07.005. Die Untersuchung umfasst sieben Standorte in fünf großen Unternehmen und betrachtet die Entstehung und langfristigen Auswirkungen von Architectural Technical Debt. - Terese Besker, Antonio Martini, Jan Bosch: Software Developer Productivity Loss Due to Technical Debt – A Replication and Extension Study Examining Developers’ Development Work. Journal of Systems and Software 156, 2019, S. 41–61. DOI:
10.1016/j.jss.2019.06.004. Die Studie untersucht zusätzliche Entwicklungsarbeit infolge von Technical Debt und identifiziert zusätzlichen Analyse- und Testaufwand als wichtige Quellen verlorener Entwicklungszeit. - John Ousterhout: The Nature of Complexity. Stanford CS 190, Lecture Notes, 2018. Ousterhout verwendet „Change Amplification“ für den Fall, dass eine einfache Änderung Modifikationen an vielen Stellen erfordert, und stellt den Begriff neben Cognitive Load und „Unknown Unknowns“.