Erkunde John Ousterhouts Ideen zu praktischem Softwaredesign, Tcls Vermächtnis, der Debatte Ousterhout vs Brooks und wie Komplexität Produkte versenkt.

John Ousterhout ist Informatiker und Ingenieur, dessen Arbeit sowohl Forschung als auch reale Systeme umfasst. Er hat die Programmiersprache Tcl geschaffen, an modernen Dateisystemen mitgewirkt und später Jahrzehnte Erfahrung in eine einfache, leicht unbequeme These destilliert: Komplexität ist der primäre Feind der Software.
Diese Botschaft ist weiterhin aktuell, weil die meisten Teams nicht wegen fehlender Features oder mangelnden Einsatzes scheitern — sie scheitern, weil ihre Systeme (und Organisationen) schwer zu verstehen, schwer zu ändern und einfach zu zerstören werden. Komplexität verlangsamt nicht nur Entwickler. Sie schlägt sich in Produktentscheidungen, Roadmap-Vertrauen, Kundenvertrauen, Vorfallhäufigkeit und sogar im Recruiting nieder — weil Onboarding zu einer monatelangen Aufgabe wird.
Ousterhouts Sicht ist praktisch: wenn ein System Sonderfälle, Ausnahmen, versteckte Abhängigkeiten und „nur diesmal“-Fixes anhäuft, beschränkt sich der Preis nicht auf den Code. Das ganze Produkt wird teurer in der Evolution. Features dauern länger, QA wird schwieriger, Releases riskanter, und Teams vermeiden Verbesserungen, weil sich alles gefährlich anfühlt.
Das ist kein Ruf nach akademischer Reinheit. Es ist eine Erinnerung daran, dass jede Abkürzung Zinszahlungen nach sich zieht — und Komplexität ist die Schuld mit dem höchsten Zinssatz.
Um die Idee konkret (und nicht nur motivierend) zu machen, betrachten wir Ousterhouts Botschaft aus drei Blickwinkeln:
Dies richtet sich nicht nur an Sprach-Nerds. Wenn du Produkte baust, Teams leitest oder Roadmap-Entscheidungen triffst, findest du umsetzbare Wege, Komplexität früh zu erkennen, zu verhindern, dass sie sich institutionalisiert, und Einfachheit als erstklassige Einschränkung zu behandeln — nicht als Nice-to-have nach dem Launch.
Komplexität ist nicht „viel Code“ oder „schwere Mathematik“. Es ist die Lücke zwischen dem, was du glaubst, das System bei einer Änderung zu tun wird, und dem, was es tatsächlich tut. Ein System ist komplex, wenn kleine Änderungen sich riskant anfühlen — weil du die Blast-Radius nicht vorhersagen kannst.
In gesundem Code kannst du beantworten: „Wenn wir das ändern, was könnte sonst noch kaputtgehen?“ Komplexität macht diese Frage teuer.
Sie versteckt sich oft in:
Teams spüren Komplexität als langsameres Liefern (mehr Zeit für Untersuchung), mehr Bugs (weil Verhalten überraschend ist) und fragile Systeme (Änderungen erfordern Koordination über viele Personen und Services). Sie belastet auch das Onboarding: neue Teammitglieder können kein mentales Modell aufbauen und meiden daher Kernpfade.
Ein Teil der Komplexität ist essenziell: Geschäftsregeln, Compliance-Anforderungen, Edge-Cases in der realen Welt. Die kannst du nicht löschen.
Vieles ist aber akzidentell: verwirrende APIs, duplizierte Logik, „temporäre“ Flags, die permanent werden, und Module, die interne Details lecken. Das ist die Komplexität, die Designentscheidungen erzeugen — und die einzige, die du systematisch abbauen kannst.
Tcl begann mit einem praktischen Ziel: es sollte leicht sein, Software zu automatisieren und bestehende Anwendungen zu erweitern, ohne sie neu zu schreiben. John Ousterhout entwarf es so, dass Teams „genug Programmierbarkeit“ in ein Tool einbauen konnten und diese Macht dann an Nutzer, Operatoren, QA oder jeden, der Workflows skripten musste, weitergaben.
Tcl popularisierte das Konzept einer Glue-Sprache: eine kleine, flexible Skripting-Schicht, die Komponenten verbindet, die in schnelleren, niedrigeren Sprachen geschrieben sind. Statt jedes Feature in einen Monolithen zu bauen, konnte man eine Reihe von Befehlen exportieren und sie zu neuen Verhalten zusammensetzen.
Dieses Modell war einflussreich, weil es zu der Art passt, wie Arbeit tatsächlich passiert. Menschen bauen nicht nur Produkte; sie bauen Build-Systeme, Test-Harnesses, Admin-Tools, Datenkonverter und einmalige Automatisierungen. Eine leichte Skripting-Schicht verwandelt diese Aufgaben vom „Ticket stellen“ in „ein Script schreiben“.
Tcl machte Einbettung zu einer erstklassigen Sorge. Du konntest einen Interpreter in eine Anwendung fallen lassen, eine saubere Befehlsschnittstelle exportieren und sofort Konfigurierbarkeit und schnelle Iteration gewinnen.
Dasselbe Muster taucht heute in Plugin-Systemen, Konfigurationssprachen, Erweiterungs-APIs und eingebetteten Skript-Runtimes auf — unabhängig davon, ob die Script-Syntax wie Tcl aussieht oder nicht.
Es verstärkte auch eine wichtige Designgewohnheit: stabile Primitiven (Kernfähigkeiten der Host-App) von veränderlicher Komposition (Skripte) trennen. Wenn das funktioniert, können Tools schneller evolvieren, ohne ständig den Kern zu destabilisieren.
Tcls Syntax und das Modell „alles ist ein String“ konnten unintuitiv wirken, und große Tcl-Codebasen wurden ohne starke Konventionen schwer zu durchschauen. Als neuere Ökosysteme reichhaltigere Standardbibliotheken, bessere Tools und größere Communities boten, migrierten viele Teams natürlich.
Das löscht Tcls Vermächtnis nicht aus: es normalisierte die Idee, dass Erweiterbarkeit und Automatisierung keine Extras sind — sie sind Produktfeatures, die die Komplexität für Nutzer und Wartende drastisch reduzieren können.
Tcl war um eine trügerisch strikte Idee gebaut: halte den Kern klein, mache Komposition mächtig und halte Skripte lesbar genug, damit Menschen ohne ständige Übersetzung zusammenarbeiten können.
Anstatt eine riesige Menge spezialisierter Features zu liefern, setzte Tcl auf eine kompakte Menge an Primitiven (Strings, Befehle, einfache Auswertungsregeln) und erwartete, dass Nutzer sie kombinieren.
Diese Philosophie lenkt Designer zu weniger Konzepten, die in vielen Kontexten wiederverwendet werden. Die Lehre für Produkt- und API-Design ist klar: Wenn du zehn Bedürfnisse mit zwei oder drei konsistenten Bausteinen lösen kannst, schrumpfst du die Oberfläche, die Menschen lernen müssen.
Eine Schlüssel-Fehlerquelle im Software-Design ist, für den Komfort der Entwickler zu optimieren. Ein Feature kann leicht zu implementieren sein (kopiere eine bestehende Option, füge ein spezielles Flag hinzu, patch einen Eckfall) und gleichzeitig das Produkt schwerer nutzbar machen.
Tcls Schwerpunkt war das Gegenteil: halte das mentale Modell eng, auch wenn die Implementierung im Hintergrund mehr Arbeit leisten muss.
Wenn du einen Vorschlag prüfst, frage: reduziert das die Anzahl der Konzepte, die ein Nutzer sich merken muss, oder fügt es eine weitere Ausnahme hinzu?
Minimalismus hilft nur, wenn Primitiven konsistent sind. Wenn zwei Befehle ähnlich aussehen, sich aber in Grenzfällen unterschiedlich verhalten, merken sich Nutzer Trivia. Eine kleine Menge Werkzeuge kann zu „scharfen Kanten“ werden, wenn Regeln subtil variieren.
Denk an eine Küche: gutes Messer, Pfanne und Backofen lassen dich viele Gerichte machen, indem du Techniken kombinierst. Ein Gadget, das nur Avocados schneidet, ist ein One-off — leicht zu verkaufen, aber es verstopft die Schubladen.
Tcls Philosophie plädiert für Messer und Pfanne: allgemeine Werkzeuge, die sich sauber kombinieren lassen, sodass du nicht für jedes neue Rezept ein neues Gadget brauchst.
1986 schrieb Fred Brooks einen Essay mit einer bewusst provokanten Schlussfolgerung: Es gibt keinen einzelnen Durchbruch — keine „Silberkugel“ — die Softwareentwicklung um eine Größenordnung schneller, billiger und zuverlässiger macht.
Sein Punkt war nicht, dass Fortschritt unmöglich ist. Sondern dass Software bereits ein Medium ist, in dem wir fast alles tun können, und diese Freiheit eine einzigartige Bürde mit sich bringt: Wir definieren das Ding ständig, während wir es bauen. Bessere Werkzeuge helfen, aber sie tilgen nicht den härtesten Teil der Arbeit.
Brooks teilte Komplexität in zwei Eimer:
Werkzeuge können akzidentelle Komplexität zerschlagen. Denk an das, was wir durch höherstufige Sprachen, Versionskontrolle, CI, Container, Managed-Datenbanken und gute IDEs gewonnen haben. Aber Brooks argumentierte, dass essenzielle Komplexität dominiert, und sie verschwindet nicht einfach, weil die Tools besser werden.
Selbst mit modernen Plattformen verbringen Teams den Großteil ihrer Energie damit, Anforderungen auszuhandeln, Systeme zu integrieren, Ausnahmen zu behandeln und Verhalten über die Zeit konsistent zu halten. Die Oberfläche ändert sich (Cloud-APIs statt Gerätetreiber), aber die Kernherausforderung bleibt: menschliche Bedürfnisse in präzises, wartbares Verhalten zu übersetzen.
Das stellt die Spannung her, auf die Ousterhout setzt: Wenn essenzielle Komplexität nicht eliminiert werden kann, kann diszipliniertes Design dann sinnvoll reduzieren, wie viel davon in den Code und in die Köpfe der Entwickler durchsickert?
Manchmal wird „Ousterhout vs Brooks“ als Kampf zwischen Optimismus und Realismus dargestellt. Nützlicher ist es, es als zwei erfahrene Ingenieure zu lesen, die verschiedene Seiten desselben Problems beschreiben.
Brooks’ „No Silver Bullet“ sagt, dass es keinen einzelnen Durchbruch gibt, der den harten Teil der Software magisch entfernt. Ousterhout bestreitet das nicht wirklich.
Sein Gegenargument ist schmaler und praktisch: Teams behandeln Komplexität oft als unvermeidbar, obwohl viel davon selbstverschuldet ist.
Aus Ousterhouts Sicht kann gutes Design Komplexität bedeutend reduzieren — nicht indem es Software „einfach“ macht, sondern indem es sie weniger verwirrend zu ändern macht. Das ist eine große Behauptung und wichtig, weil Verwirrung den Alltag in Langsamkeit verwandelt.
Brooks konzentriert sich auf das, was er essenzielle Schwierigkeit nennt: Software muss unordentliche Realitäten, sich ändernde Anforderungen und außerhalb des Codes existierende Edge-Cases modellieren. Selbst mit großartigen Tools kannst du das nicht löschen. Du kannst es nur managen.
Sie überschneiden sich mehr, als die Debatte vermuten lässt:
Statt zu fragen „Wer hat recht?“, frage: Welche Komplexität können wir dieses Quartal kontrollieren?
Teams können Marktveränderungen oder die Kerndomänenkomplexität nicht kontrollieren. Aber sie können steuern, ob neue Features Sonderfälle hinzufügen, ob APIs Aufrufer zwingen, versteckte Regeln zu merken, und ob Module Komplexität verstecken oder nach außen lecken.
Das ist der handlungsfähige Mittelweg: essenzielle Komplexität akzeptieren und akzidentelle gnadenlos minimieren.
Ein tiefes Modul ist eine Komponente, die viel tut und dabei eine kleine, leicht verständliche Schnittstelle anbietet. Die „Tiefe“ ist die Menge an Komplexität, die das Modul dir abnimmt: Aufrufer müssen die unsauberen Details nicht kennen, und die Schnittstelle zwingt sie auch nicht dazu.
Ein flaches Modul ist das Gegenteil: es kapselt vielleicht ein kleines Stück Logik, schiebt die Komplexität aber nach außen — durch viele Parameter, Spezialflags, erforderliche Aufrufreihenfolge oder „du musst daran denken…“-Regeln.
Stell dir ein Restaurant vor. Ein tiefes Modul ist die Küche: du bestellst „Pasta“ von einer einfachen Karte und interessierst dich nicht für Lieferantenwahl, Kochzeiten oder Anrichten.
Ein flaches Modul ist eine „Küche“, die dir rohe Zutaten mit einem 12-Schritte-Handbuch übergibt und dich bittet, deine eigene Pfanne mitzubringen. Die Arbeit findet zwar statt — aber sie ist zum Kunden verschoben worden.
Zusätzliche Schichten sind gut, wenn sie viele Entscheidungen in eine offensichtliche Wahl zusammenfallen lassen.
Beispielsweise ist eine Speicherschicht, die save(order) anbietet und intern Retries, Serialisierung und Indexierung handhabt, tief.
Schichten schaden, wenn sie hauptsächlich umbenennen oder Optionen hinzufügen. Wenn eine neue Abstraktion mehr Konfiguration einführt, als sie entfernt — etwa save(order, format, retries, timeout, mode, legacyMode) — ist sie wahrscheinlich flach. Der Code wirkt zwar „organisiert“, aber die kognitive Last zeigt sich an jedem Aufrufort.
useCache, skipValidation, force, legacy.Tiefe Module kapseln nicht nur Code. Sie kapseln Entscheidungen.
Eine „gute" API ist nicht nur eine, die viel kann. Es ist eine, die Menschen beim Arbeiten im Kopf behalten können.
Ousterhouts Design-Linse zwingt dich, eine API danach zu beurteilen, wie viel mentale Arbeit sie verlangt: wie viele Regeln man sich merken muss, wie viele Ausnahmen man voraussehen muss und wie leicht man versehentlich etwas falsch macht.
Menschengerechte APIs sind meist klein, konsistent und schwer falsch zu verwenden.
Klein bedeutet nicht kraftlos — es bedeutet, die Oberfläche auf wenige, gut kombinierbare Konzepte zu konzentrieren. Konsistent heißt, dass dasselbe Muster im ganzen System gilt (Parameter, Fehlerbehandlung, Benennung, Rückgabetypen). Schwer falsch zu verwenden bedeutet, dass die API den Anwender in sichere Pfade führt: klare Invarianten, Validierung an Grenzen und Typ- oder Laufzeitprüfungen, die früh fehlschlagen.
Jedes zusätzliche Flag, jeder Modus oder „für den Fall der Fälle“-Konfigurationspunkt ist eine Steuer für alle Nutzer. Selbst wenn nur 5 % der Aufrufer es benötigen, müssen 100 % nun wissen, dass es existiert, sich fragen, ob sie es brauchen, und das Verhalten interpretieren, wenn es mit anderen Optionen interagiert.
So akkumuliert eine API versteckte Komplexität: nicht in einem einzelnen Aufruf, sondern in der Kombinatorik.
Defaults sind eine Freundlichkeit: sie lassen die meisten Aufrufer Entscheidungen weglassen und trotzdem sinnvolles Verhalten erhalten. Konventionen (ein offensichtlicher Weg) vermindern Verzweigungen im Kopf des Nutzers. Benennung leistet ebenfalls echte Arbeit: wähle Verben und Substantive, die der Intention des Nutzers entsprechen, und halte ähnliche Operationen in der Benennung konsistent.
Noch ein Hinweis: interne APIs sind genauso wichtig wie öffentliche. Die meiste Produktkomplexität lebt im Hintergrund — Service-Grenzen, gemeinsam genutzte Bibliotheken und „Hilfs“-Module. Behandle diese Schnittstellen wie Produkte, mit Reviews und Versionierungsdisziplin (siehe auch /blog/deep-modules).
Komplexität kommt selten als eine einzige „schlechte Entscheidung“. Sie häuft sich durch kleine, vernünftig aussehende Patches — besonders wenn Teams unter Zeitdruck stehen und das unmittelbare Ziel „shippen“ ist.
Eine Falle sind Feature-Flags überall. Flags sind nützlich für sichere Rollouts, aber wenn sie bestehen bleiben, multipliziert jeder Flag die möglichen Verhaltensweisen. Entwickler denken dann nicht mehr über „das System“ nach, sondern über „das System, außer wenn Flag A an ist und der Nutzer in Segment B ist“.
Eine andere sind Sonderfall-Logiken: „Enterprise-Kunden brauchen X“, „Außer in Region Y“, „Außer wenn das Konto älter als 90 Tage ist“. Diese Ausnahmen verteilen sich oft im Code, und nach ein paar Monaten weiß niemand mehr, welche noch benötigt werden.
Drittens leakende Abstraktionen. Eine API, die Aufrufer zwingt, interne Details zu kennen (Timing, Speicherformat, Caching-Regeln), verlagert Komplexität nach außen. Statt dass ein Modul die Last trägt, lernt jeder Aufrufer die Eigenheiten.
Taktisches Programmieren optimiert für diese Woche: schnelle Fixes, minimale Änderungen, „einfach patchen".
Strategisches Programmieren optimiert für das nächste Jahr: kleine Redesigns, die dieselbe Klasse von Bugs verhindern und zukünftige Arbeit reduzieren.
Die Gefahr ist die „Wartungs-Zinszahlung“. Ein schneller Workaround fühlt sich jetzt günstig an, aber du bezahlst ihn zurück mit Zins: langsameres Onboarding, fragile Releases und ängstliche Entwicklung, bei der niemand alten Code anfassen will.
Füge leichte Fragen in Code-Reviews ein: „Fügt das einen neuen Sonderfall hinzu?" „Kann die API dieses Detail verbergen?" „Welche Komplexität hinterlassen wir?“
Führe kurze Entscheidungsaufzeichnungen für nicht-triviale Trade-offs (ein paar Stichpunkte reichen). Und reserviere ein kleines Refactor-Budget pro Sprint, damit strategische Fixes nicht als Extracurricular behandelt werden.
Komplexität bleibt nicht in der Technik. Sie sickert in Zeitpläne, Zuverlässigkeit und die Art und Weise, wie Kunden dein Produkt erleben.
Wenn ein System schwer zu verstehen ist, dauert jede Änderung länger. Time-to-Market leidet, weil jeder Release mehr Koordination, mehr Regressionstests und mehr „nur um sicherzugehen“-Review-Zyklen erfordert.
Die Zuverlässigkeit leidet ebenfalls. Komplexe Systeme erzeugen Interaktionen, die niemand vollständig vorhersagen kann, sodass Bugs als Edge-Cases auftreten: Der Checkout schlägt nur fehl, wenn Gutschein, gespeicherter Warenkorb und eine regionale Steuerregel in einer bestimmten Kombination auftreten. Das sind Vorfälle, die am schwersten zu reproduzieren und am langsamsten zu beheben sind.
Onboarding wird zu einem versteckten Hemmnis. Neue Teammitglieder können kein nützliches mentales Modell aufbauen, meiden riskante Bereiche, kopieren Muster, die sie nicht verstehen, und fügen unbeabsichtigt mehr Komplexität hinzu.
Kunden interessiert nicht, ob ein Verhalten durch einen „Sonderfall“ im Code verursacht wird. Sie erleben es als Inkonsistenz: Einstellungen, die nicht überall gelten, Abläufe, die sich je nach Weg unterscheiden, Features, die „meistens“ funktionieren.
Vertrauen sinkt, Churn steigt, Adoption stockt.
Support-Teams bezahlen Komplexität durch längere Tickets und mehr Hin-und-Her, um Kontext zu sammeln. Betrieb bezahlt durch mehr Alerts, ausführlichere Runbooks und vorsichtigere Deployments. Jede Ausnahme wird etwas, das überwacht, dokumentiert und erklärt werden muss.
Stell dir eine Anforderung für „noch eine Benachrichtigungsregel“ vor. Das Hinzufügen wirkt schnell, aber es führt zu einem weiteren Verzweigungsweg im Verhalten, mehr UI-Text, mehr Testfälle und mehr Möglichkeiten, dass Nutzer falsch konfigurieren.
Vergleiche das mit der Vereinfachung des bestehenden Benachrichtigungsflusses: weniger Regeltypen, klarere Defaults und konsistentes Verhalten über Web und Mobile. Du lieferst vielleicht weniger Schalter, aber du reduzierst Überraschungen — das Produkt ist einfacher zu nutzen, leichter zu unterstützen und schneller weiterzuentwickeln.
Behandle Komplexität wie Performance oder Security: etwas, das du planst, misst und schützt. Wenn du Komplexität erst bemerkst, wenn die Lieferung langsamer wird, zahlst du bereits Zinsen.
Neben Feature-Umfang definiere, wie viel neue Komplexität ein Release einführen darf. Das Budget kann einfach sein: „kein Netto-Neukonzept, es sei denn wir entfernen eines" oder „jede neue Integration muss einen alten Pfad ersetzen".
Mache Trade-offs explizit in der Planung: Wenn ein Feature drei neue Konfigurationsmodi und zwei Ausnahmen braucht, sollte das mehr „kosten“ als ein Feature, das in bestehende Konzepte passt.
Du brauchst keine perfekten Zahlen — nur Signale, die in die richtige Richtung zeigen:
Verfolge diese pro Release und verknüpfe sie mit Entscheidungen: „Wir haben zwei neue öffentliche Optionen hinzugefügt; was haben wir entfernt oder vereinfacht, um das auszugleichen?"
Prototypen werden oft an der Frage „Können wir das bauen?“ gemessen. Nutze sie stattdessen, um zu beantworten: „Fühlt sich das einfach zu benutzen und schwer falsch zu verwenden an?"
Lass jemand Unbekanntes mit dem Prototyp eine realistische Aufgabe ausführen. Miss Zeit bis zum Erfolg, gestellte Fragen und falsche Annahmen. Das sind Hotspots für Komplexität.
Hier können moderne Build-Workflows akzidentelle Komplexität reduzieren — wenn sie Iteration eng halten und das Zurücksetzen von Fehlern einfach machen. Zum Beispiel können Teams mit einer Plattform wie Koder.ai intern ein Tool oder einen neuen Flow per Chat skizzieren; Features wie planning mode (um die Absicht vor der Generierung zu klären) und snapshots/rollback (um riskante Änderungen schnell rückgängig zu machen) machen frühe Experimente sicherer — ohne sich auf eine Ansammlung halb fertiger Abstraktionen einzulassen. Wenn der Prototyp „reift“, kannst du den Source-Code exportieren und dieselbe Disziplin für tiefe Module und API-Design anwenden, die oben beschrieben wurde.
Mach „Komplexitätsbereinigung" periodisch (vierteljährlich oder bei jedem großen Release) und definiere, was „fertig" bedeutet:
Das Ziel ist nicht schöner Code abstrakt — es sind weniger Konzepte, weniger Ausnahmen und sicherere Änderungen.
Hier ein paar Moves, die Ousterhouts Idee „Komplexität ist der Feind" in Wochen-gewohnheiten übersetzen.
Wähle ein Subsystem, das regelmäßig Verwirrung stiftet (Onboarding-Schmerz, wiederkehrende Bugs, viele „wie funktioniert das?“-Fragen).
Interne Follow-ups, die du durchführen kannst: eine „Komplexitäts-Review“ in der Planung (/blog/complexity-review) und eine kurze Prüfung, ob euer Tooling akzidentelle Komplexität verringert oder nur Schichten hinzufügt (/pricing).
Welche eine Komplexität würdest du als Erstes entfernen, wenn du diese Woche nur einen Sonderfall löschen dürftest?
Komplexität ist die Lücke zwischen dem, was du erwartest, dass beim Ändern des Systems passiert, und dem, was tatsächlich passiert.
Du spürst sie, wenn kleine Änderungen riskant wirken, weil du den möglichen Umfang der Auswirkungen (Tests, Services, Konfigurationen, Kunden oder Edge-Cases) nicht vorhersagen kannst.
Achte auf Signale, die darauf hinweisen, dass das Nachdenken teuer ist:
Essenzielle Komplexität kommt aus der Domäne (Regeln, reale Edge-Cases, Geschäftslogik). Sie lässt sich nicht entfernen — nur gut modellieren.
Akzidentelle Komplexität ist selbstverschuldet (leakende Abstraktionen, duplizierte Logik, zu viele Modi/Flags, unklare APIs). Diesen Teil können Teams durch gutes Design und Vereinfachung zuverlässig reduzieren.
Ein tiefes Modul macht viel im Inneren, bietet aber eine kleine, stabile Schnittstelle. Es „schluckt“ die unsauberen Details (Retries, Formate, Reihenfolgen, Invarianten), sodass Aufrufer diese nicht kennen müssen.
Ein praktischer Test: Wenn die meisten Aufrufer das Modul korrekt nutzen können, ohne interne Regeln zu kennen, ist es tief; wenn Aufrufer Reihenfolgen und Details auswendig lernen müssen, ist es flach.
Typische Symptome:
legacy, skipValidation, force, mode).Bevorzuge APIs, die:
Wenn du versucht bist, „nur noch eine Option“ hinzuzufügen, frage zuerst, ob sich die Schnittstelle so umgestalten lässt, dass die meisten Aufrufer darüber nicht nachdenken müssen.
Nutze Feature-Flags für kontrollierte Rollouts, behandle sie aber als Schuld mit Enddatum:
Langfristig vorhandene Flags vervielfachen die Anzahl der „Systeme“, über die Entwickler nachdenken müssen.
Mache Komplexität in der Planung sichtbar, nicht nur in Code-Reviews:
Ziel ist, Trade-offs offen zu legen, bevor Komplexität institutionalisiert wird.
Taktisches Programmieren optimiert für diese Woche: schnelle Patches, minimale Änderungen, „ship it“.
Strategisches Programmieren optimiert für das nächste Jahr: kleine Redesigns, die wiederkehrende Fehlerklassen entfernen und zukünftige Arbeit reduzieren.
Eine nützliche Regel: Wenn eine Lösung Aufruferwissen erfordert („rufe X zuerst auf“ oder „setze dieses Flag nur in Prod“), brauchst du vermutlich eine strategischere Änderung, die diese Komplexität im Modul versteckt.
Tcl zeigt die Kraft einer kleinen Menge von Primitiven plus starker Komposition — oft als eingebettete „Glue“-Schicht.
Moderne Äquivalente sind z. B.:
Das Designziel bleibt gleich: den Kern einfach und stabil halten und Änderungen über saubere Schnittstellen ermöglichen.
Flache Module sehen oft organisiert aus, verlagern aber die Komplexität zu jedem Aufrufer.