8 Min

Dijkstra & Strukturierte Programmierung: Disziplin, die skaliert

Edsger Dijkstras Ideen zur strukturierten Programmierung erklären, warum disziplinierter, einfacher Code korrekt und wartbar bleibt, wenn Teams, Features und Systeme wachsen.

Dijkstra & Strukturierte Programmierung: Disziplin, die skaliert

Warum Dijkstra noch wichtig ist, wenn Software wächst

Software scheitert selten, weil sie nicht geschrieben werden kann. Sie scheitert, weil ein Jahr später niemand sie sicher verändern kann.

Wenn Codebasen wachsen, fängt jede „kleine“ Änderung an zu wellen: Ein Bugfix bricht ein entferntes Feature, eine neue Anforderung erzwingt Umschreibungen, und ein einfacher Refactor wird zu einer Woche sorgfältiger Abstimmung. Die Schwierigkeit liegt nicht darin, Code hinzuzufügen — sondern Verhalten vorhersehbar zu halten, während sich alles um ihn herum ändert.

Das Versprechen: Korrektheit und Einfachheit senken die langfristigen Kosten

Edsger Dijkstra argumentierte, dass Korrektheit und Einfachheit erstklassige Ziele sein sollten, nicht nur nette Zusatzaspekte. Die Rendite ist nicht nur akademisch. Wenn ein System leichter zu durchdenken ist, verbringen Teams weniger Zeit mit Feuerlöschen und mehr Zeit mit Bauen.

  • Korrektheit reduziert die Kosten von Fehlern: weniger Vorfälle, weniger Regressionen, weniger „Finger weg“-Code.
  • Einfachheit reduziert die Kosten der Änderung: weniger versteckte Annahmen, weniger Spezialfälle, weniger Überraschungen in Reviews.

Was „Skalierung“ wirklich bedeutet

Wenn Leute sagen, Software müsse „skalieren“, meinen sie oft Leistung. Dijkstras Punkt ist anders: Komplexität skaliert ebenfalls.

Skalierung zeigt sich als:

  • Mehr Features: neue Abläufe, Randfälle und Nutzererwartungen.
  • Mehr Menschen: Übergaben, verschiedene Stile und unterschiedlicher Kontext.
  • Mehr Integrationen: externe APIs, Datenquellen und Fehlermodi.
  • Mehr Zeit: historische Entscheidungen, wechselnde Anforderungen und Teil-Umschreibungen.

Die Kernidee: Struktur macht Verhalten leichter vorhersagbar

Strukturierte Programmierung geht nicht darum, streng um der Strenge willen zu sein. Es geht darum, Kontrollfluss und Zerlegung so zu wählen, dass zwei Fragen leicht zu beantworten sind:

  • „Was passiert als Nächstes?“
  • „Unter welchen Bedingungen?“

Wenn Verhalten vorhersehbar ist, werden Änderungen Routine statt Risiko. Deshalb ist Dijkstra noch relevant: Seine Disziplin zielt auf das echte Nadelöhr wachsender Software — sie so zu verstehen, dass man sie verbessern kann.

Eine einfache Einführung in Edsger Dijkstra und sein Ziel

Edsger W. Dijkstra (1930–2002) war ein niederländischer Informatiker, der beeinflusste, wie Programmierende zuverlässige Software bauen. Er arbeitete an frühen Betriebssystemen, trug zu Algorithmen bei (inklusive des nach ihm benannten kürzesten-Pfad-Algorithmus) und — für Alltagsentwickler am wichtigsten — vertrat die Idee, dass Programmieren etwas sein sollte, das wir nachvollziehen können, nicht nur etwas, das wir solange probieren, bis es scheint zu funktionieren.

Sein Kernfokus: Nachvollziehen statt „läuft auf meinem Rechner“

Dijkstra interessierte sich weniger dafür, ob ein Programm für einige Beispiele die richtige Ausgabe erzeugt, und mehr dafür, ob wir erklären können, warum es für die relevanten Fälle korrekt ist.

Wenn du sagen kannst, was ein Codeabschnitt tun soll, solltest du Schritt für Schritt argumentieren können, dass er es tatsächlich tut. Diese Denkweise führt natürlich zu Code, der leichter zu folgen, leichter zu reviewen und weniger abhängig von heldenhaften Debugging-Aktionen ist.

Warum er streng klingen kann — und warum das hilft

Manche von Dijkstras Schriften wirken kompromisslos. Er kritisierte „clevere“ Tricks, schlampigen Kontrollfluss und Programmiergewohnheiten, die Nachvollziehbarkeit erschweren. Die Strenge ist keine Stilpolizei; sie zielt darauf ab, Mehrdeutigkeiten zu reduzieren. Wenn die Bedeutung des Codes klar ist, verschwendet man weniger Zeit darauf, Absichten zu diskutieren, und mehr Zeit darauf, Verhalten zu validieren.

Was „strukturierte Programmierung“ bedeutet (auf hohem Niveau)

Strukturierte Programmierung ist die Praxis, Programme aus einer kleinen Menge klarer Kontrollstrukturen aufzubauen — Sequenz, Auswahl (if/else) und Iteration (Schleifen) — statt sich in verworrenen Sprüngen zu verlieren. Das Ziel ist einfach: den Pfad durch das Programm verständlich zu machen, damit man ihn erklären, warten und mit Zuversicht ändern kann.

Korrektheit: Das versteckte Feature, auf das deine Nutzer bauen

Menschen beschreiben Softwarequalität oft als „schnell“, „schön“ oder „feature-reich“. Nutzer erleben Korrektheit anders: als die stille Gewissheit, dass die App sie nicht überrascht. Wenn Korrektheit vorhanden ist, fällt das niemandem auf. Fehlt sie, ist der Rest egal.

„Es funktioniert jetzt“ vs. „es funktioniert weiter“

„Es funktioniert jetzt“ bedeutet meist, dass du ein paar Pfade ausprobiert und das erwartete Ergebnis gesehen hast. „Es funktioniert weiter“ bedeutet, dass es sich über die Zeit, Randfälle und Änderungen hinweg so verhält, wie beabsichtigt — nach Refactorings, neuen Integrationen, höherer Last und wenn neue Teammitglieder den Code anfassen.

Ein Feature kann „jetzt funktionieren“ und trotzdem fragil sein:

  • Es setzt voraus, dass Eingaben immer sauber sind.
  • Es geht davon aus, dass ein Netzwerkaufruf immer schnell antwortet.
  • Es besteht Tests, die nur den Happy Path abdecken.

Korrektheit bedeutet, diese versteckten Annahmen zu entfernen — oder sie explizit zu machen.

Wie kleine Bugs in großen Systemen multiplizieren

Ein kleiner Bug bleibt selten klein, wenn die Software wächst. Ein falscher Zustand, ein Off-by-One-Grenzwert oder eine unklare Fehlerbehandlung wird in neue Module kopiert, von anderen Services umschlossen, zwischengespeichert, erneut versucht oder „umgangen“. Mit der Zeit hören Teams auf zu fragen „Was ist wahr?“ und fragen stattdessen „Was passiert normalerweise?". Dann wird Incident-Response zu Archäologie.

Der Multiplikator ist Abhängigkeit: Ein kleines Fehlverhalten wird zu vielen nachgelagerten Fehlverhalten, jedes mit seiner eigenen Teilbehebung.

Klarheit ist ein Werkzeug für Korrektheit (nicht nur Stil)

Klarer Code verbessert Korrektheit, weil er Kommunikation verbessert:

  • Code-Reviews finden echte Probleme, wenn die Absicht offensichtlich ist.
  • Onboarding geht schneller, wenn Regeln lesbar sind und keine Stammeskultur notwendig ist.
  • Incidents werden schneller gelöst, wenn Kontrollfluss und Fehlerszenarien leicht nachzuverfolgen sind.

Eine praktische Definition von Korrektheit für Produktteams

Korrektheit bedeutet: für die Eingaben und Situationen, die wir unterstützen wollen, erzeugt das System konsistent die versprochenen Ergebnisse — und schlägt bei Nichtmöglichkeit auf vorhersehbare, erklärbare Weise fehl.

Einfachheit als Strategie, nicht als Stilvorliebe

Einfachheit heißt nicht, Code „süß“, minimal oder clever zu machen. Es geht darum, Verhalten vorhersehbar, erklärbar und änderbar zu machen, ohne Angst. Dijkstra schätzte Einfachheit, weil sie unsere Fähigkeit verbessert, über Programme zu schlussfolgern — besonders wenn Codebasis und Team wachsen.

Was Einfachheit ist (und was nicht)

Einfacher Code hält nur eine kleine Anzahl von Ideen gleichzeitig in Bewegung: klarer Datenfluss, klarer Kontrollfluss und klare Verantwortlichkeiten. Er zwingt den Leser nicht, viele Alternativpfade im Kopf zu simulieren.

Einfachheit ist nicht:

  • Weniger Zeilen um jeden Preis
  • „Schlaue“ Tricks, dichte Einzeiler oder übermäßige Abstraktion
  • Strukturvermeidung, um flexibel zu wirken

Accidental Complexity: was du unbeabsichtigt hinzufügst

Viele Systeme werden schwer änderbar, nicht weil die Domäne inhärent komplex ist, sondern weil wir versehentlich Komplexität hinzufügen: Flags, die unerwartet interagieren, Spezialpatches, die nie entfernt werden, und Schichten, die hauptsächlich dazu dienen, frühere Entscheidungen zu umgehen.

Jede zusätzliche Ausnahme ist eine Steuer auf das Verständnis. Die Kosten tauchen später auf, wenn jemand einen Bug beheben will und entdeckt, dass eine Änderung in einem Bereich subtil mehrere andere bricht.

Einfache Designs reduzieren den Bedarf an Heldentaten

Bei einem einfachen Design kommt Fortschritt durch stetige Arbeit: überprüfbare Änderungen, kleinere Diffs und weniger Notfallfixes. Teams brauchen keine „Helden“, die sich an jeden historischen Randfall erinnern oder um 2 Uhr nachts unter Druck debuggen können. Stattdessen unterstützt das System normale menschliche Aufmerksamkeitsspannen.

Faustregel: weniger Spezialfälle, weniger Überraschungen

Ein praktischer Test: Wenn du ständig Ausnahmen hinzufügst („außer…“, „außer wenn…“, „nur für diesen Kunden…“), sammelst du wahrscheinlich versehentliche Komplexität. Bevorzuge Lösungen, die Verzweigungen im Verhalten reduzieren — eine konsistente Regel schlägt fünf Spezialfälle, selbst wenn die Regel etwas allgemeiner ist als zuerst gedacht.

Strukturierte Programmierung: klarer Kontrollfluss, dem du vertrauen kannst

Strukturierte Programmierung ist eine einfache Idee mit großen Folgen: schreibe Code so, dass sein Ablauf leicht nachzuvollziehen ist. In einfachen Worten lassen sich die meisten Programme aus drei Bausteinen bauen — Sequenz, Auswahl und Wiederholung — ohne auf verworrene Sprünge zurückzugreifen.

Die drei Bausteine (in menschlichen Worten)

  • Sequenz: mache Schritt A, dann Schritt B, dann Schritt C.
  • Auswahl: wähle einen Pfad basierend auf einer Bedingung (z. B. if/else, switch).
  • Wiederholung: wiederhole eine Menge Schritte, solange eine Bedingung gilt (z. B. for, while).

Wenn Kontrollfluss aus diesen Strukturen zusammengesetzt ist, kannst du meist erklären, was das Programm tut, indem du von oben nach unten liest, ohne „im Dateiinhalt zu springen“.

Was es ersetzt hat: Spaghetti-Ausführungswege

Bevor strukturierte Programmierung zur Norm wurde, setzten viele Codebasen stark auf beliebige Sprünge (klassischer goto-Stil). Das Problem war nicht, dass Sprünge immer böse sind; das Problem ist, dass unbeschränkte Sprünge Ausführungswege erzeugen, die schwer vorherzusagen sind. Du fragst dich dann: „Wie sind wir hierher gekommen?“ und „In welchem Zustand ist diese Variable?“ — und der Code beantwortet das nicht klar.

Warum Klarheit für reale Teams wichtig ist

Klarer Kontrollfluss hilft Menschen, ein korrektes mentales Modell zu bauen. Dieses Modell ist das, worauf du zurückgreifst beim Debuggen, beim Review eines Pull Requests oder wenn du Verhalten unter Zeitdruck änderst.

Wenn Struktur konsistent ist, werden Änderungen sicherer: du kannst einen Zweig ändern, ohne versehentlich einen anderen zu beeinflussen, oder eine Schleife refactoren, ohne einen versteckten Exit-Pfad zu übersehen. Lesbarkeit ist keine Ästhetik — sie ist die Grundlage dafür, Verhalten mit Zuversicht zu ändern, ohne das Bestehende zu brechen.

Werkzeuge zum Nachvollziehen: Invarianten, Vorbedingungen und Nachbedingungen

Übernimm den Code-Bestand
Behalte, was du baust: exportiere den Quellcode, wenn du volle Kontrolle willst.

Dijkstra vertrat eine einfache Idee: Wenn du erklären kannst, warum dein Code korrekt ist, kannst du ihn mit weniger Angst ändern. Drei kleine Werkzeuge machen das praktisch — ohne dein Team in Mathematiker zu verwandeln.

Invarianten: „Fakten, die wahr bleiben“

Eine Invariante ist eine Tatsache, die während eines Codesegments wahr bleibt, besonders innerhalb einer Schleife.

Beispiel: Du summierst Preise in einem Warenkorb. Eine nützliche Invariante ist: „total entspricht der Summe aller bisher verarbeiteten Positionen.“ Wenn das in jedem Schritt wahr bleibt, ist das Ergebnis am Ende vertrauenswürdig.

Invarianten sind mächtig, weil sie deine Aufmerksamkeit auf was niemals brechen darf lenken, nicht nur darauf, was als Nächstes passieren sollte.

Vorbedingungen und Nachbedingungen: alltägliche Verträge

Eine Vorbedingung legt fest, was vor dem Ausführen einer Funktion wahr sein muss. Eine Nachbedingung ist, was die Funktion nach Beendigung garantiert.

Alltägliche Beispiele:

  • Vorbedingung: „Du kannst nur Geld abheben, wenn dein Konto ausreichend gedeckt ist.“
  • Nachbedingung: „Nach dem Abheben ist der Kontostand genau um diesen Betrag reduziert und nie negativ."

Im Code könnte eine Vorbedingung sein: „Die Eingabeliste ist sortiert“, und die Nachbedingung: „Die Ausgabeliste ist sortiert und enthält dieselben Elemente plus das Eingefügte."

Wie das Aufschreiben das Coden und Reviews verändert

Wenn du diese Dinge (auch informell) notierst, wird das Design schärfer: du entscheidest, was eine Funktion erwartet und verspricht, und machst sie natürlicherweise kleiner und fokussierter.

In Reviews verlagert das die Debatte weg von Stil („Ich würde das anders schreiben“) hin zur Korrektheit („Erhält diese Funktion die Invariante?“ „Erzwingen wir die Vorbedingung oder dokumentieren wir sie?“).

Leichtgewichtige Praxis: kommentiere, wo Bugs häufig auftreten

Du brauchst keine formalen Beweise. Wähle die fehleranfälligste Schleife oder die kniffligste Zustandsaktualisierung und füge eine Ein-Zeilen-Invarianzhinweis darüber ein. Wenn später jemand den Code ändert, wirkt dieser Hinweis wie eine Leitplanke: Bricht eine Änderung diese Tatsache, ist der Code nicht mehr sicher.

Tests vs. Nachvollziehen: Was jedes leisten kann und nicht leisten kann

Tests und Nachvollziehen zielen auf dasselbe Ergebnis — Software, die sich wie beabsichtigt verhält — aber sie arbeiten sehr unterschiedlich. Tests entdecken Probleme durch Beispiele. Nachvollziehen verhindert ganze Klassen von Problemen, indem die Logik explizit und prüfbar gemacht wird.

Wobei Tests großartig sind

Tests sind ein praktisches Sicherheitsnetz. Sie fangen Regressionen, verifizieren reale Szenarien und dokumentieren erwartetes Verhalten so, dass das ganze Team sie ausführen kann.

Aber Tests können nur die Präsenz von Bugs zeigen, nicht deren Abwesenheit. Kein Testpaket deckt jede Eingabe, jede Timing-Variation oder jede Interaktion zwischen Features ab. Viele „funktioniert auf meinem Rechner“-Fehler kommen von ungetesteten Kombinationen: einer seltenen Eingabe, einer speziellen Reihenfolge von Operationen oder einem subtilen Zustand nach mehreren Schritten.

Was Nachvollziehen garantieren kann (und was nicht)

Nachvollziehen bedeutet, Eigenschaften des Codes zu beweisen: „diese Schleife terminiert immer“, „diese Variable wird nie negativ sein“, „diese Funktion liefert nie ein ungültiges Objekt zurück“. Gut gemacht schließt es ganze Defektklassen aus — besonders an Grenzen und Randfällen.

Die Einschränkung ist Aufwand und Umfang. Vollständige formale Beweise für ein komplettes Produkt sind selten ökonomisch. Nachvollziehen wirkt am besten selektiv: auf Kernalgorithmen, sicherheitskritische Abläufe, Geld- oder Abrechnungslogik und Nebenläufigkeit.

Ein ausgewogener Ansatz, der skaliert

Verwende Tests breit und wende tieferes Nachvollziehen dort an, wo Fehler teuer sind.

Eine praktische Brücke ist, die Absicht ausführbar zu machen:

  • Assertions für interne Annahmen (z. B. „Index liegt im Bereich“).
  • Vor- und Nachbedingungen (Verträge) für Funktions-Inputs/Outputs.
  • Invarianten für persistente Wahrheiten (z. B. „Warenkorb-Gesamt entspricht der Summe der Items").

Diese Techniken ersetzen Tests nicht — sie ziehen das Netz enger. Sie verwandeln vage Erwartungen in prüfbare Regeln und machen Bugs schwerer zu schreiben und leichter zu diagnostizieren.

Disziplin: Wie Teams „Cleverness Debt“ vermeiden

„Cleverer“ Code fühlt sich oft kurzfristig wie ein Gewinn an: weniger Zeilen, ein netter Trick, ein Einzeiler, der dich schlau fühlen lässt. Das Problem ist, dass Cleverness über Zeit und über Personen hinweg nicht skaliert. Sechs Monate später vergisst der Autor den Trick. Ein neues Teammitglied liest ihn wörtlich, übersieht die versteckte Annahme und ändert ihn so, dass Verhalten kaputtgeht. Das ist „Cleverness Debt“: kurzfristige Geschwindigkeit erkauft mit langfristiger Verwirrung.

Disziplin ist ein Team-Beschleuniger

Dijkstras Punkt war nicht „schreibe langweiligen Code“ als Stilvorgabe — sondern dass disziplinierte Einschränkungen Programme leichter nachvollziehbar machen. In einem Team reduzieren Einschränkungen auch Entscheidungserschöpfung. Wenn jeder bereits die Defaults kennt (Benennungen, Struktur von Funktionen, was „done“ bedeutet), verhandelt man nicht jedes Mal Basics in jedem Pull Request neu. Diese Zeit fließt zurück in Produktarbeit.

Disziplin zeigt sich in Routinepraktiken:

  • Code-Reviews, die Klarheit über Neuheit belohnen („Kann jemand anderes das sicher ändern?“).
  • Geteilte Standards (Formatierung, Benennung, Fehlerbehandlung), sodass die Codebasis wie eine Stimme liest.
  • Refactoring als Wartung, nicht als Rettungsmission — kleine Bereinigungen kontinuierlich.

Wie „diszipliniert“ im Code aussieht

Einige konkrete Gewohnheiten verhindern das Ansammeln von Cleverness Debt:

  • Kleine Funktionen, die eine Aufgabe tun, mit offensichtlichen Eingaben und Ausgaben.
  • Klare Namen, die die Absicht erklären (bevorzuge calculate_total() über do_it()).
  • Kein versteckter Zustand: minimiere Globals und überraschende Seiteneffekte; übergebe Abhängigkeiten explizit.
  • Gerader Kontrollfluss: vermeide Logik, die auf subtilem Ordering, magischen Werten oder „funktioniert, wenn du den Trick kennst“ basiert.

Disziplin ist nicht Perfektion — es geht darum, die nächste Änderung vorhersehbar zu machen.

Modularität und Grenzen: Änderungen lokal halten

Plane, bevor du baust
Teile die Arbeit in Module, Schnittstellen und Verantwortlichkeiten auf, bevor du Code generierst.

Modularität ist nicht nur „Code in Dateien aufteilen“. Es bedeutet, Entscheidungen hinter klaren Grenzen zu isolieren, sodass der Rest des Systems nichts über interne Details wissen (oder sich darum kümmern) muss. Ein Modul versteckt die unordentlichen Teile — Datenstrukturen, Randfälle, Performance-Tricks — und bietet eine kleine, stabile Oberfläche.

Wie Module den Explosionsradius verkleinern

Wenn eine Änderungsanforderung kommt, ist das ideale Ergebnis: ein Modul ändert sich, und der Rest bleibt unberührt. Das ist die praktische Bedeutung von „Änderung lokal halten“. Grenzen verhindern unbeabsichtigte Kopplung — wo das Aktualisieren eines Features stillschweigend drei andere bricht, weil sie Annahmen teilen.

Eine gute Grenze macht Nachvollziehen auch einfacher. Wenn du sagen kannst, was ein Modul garantiert, kannst du über das größere Programm nachdenken, ohne dessen gesamte Implementierung jedes Mal neu zu lesen.

Schnittstellen als Versprechen (und wie sie paralleles Arbeiten ermöglichen)

Eine Schnittstelle ist ein Versprechen: „Gib mir diese Eingaben, und ich liefere diese Ausgaben und halte diese Regeln ein.“ Wenn dieses Versprechen klar ist, können Teams parallel arbeiten:

  • Eine Person implementiert das Modul.
  • Eine andere baut einen Aufrufer, der die Schnittstelle nutzt.
  • QA entwirft Tests um das versprochene Verhalten.

Das ist keine Bürokratie — es schafft sichere Koordinationspunkte in einer wachsenden Codebasis.

Einfache Modul-Checks, die Drift verhindern

Du brauchst keine große Architektur-Review, um Modularität zu verbessern. Probiere diese Leichtgewichtschecks:

  • Eingaben/Ausgaben: Kannst du die Inputs, Outputs und Seiteneffekte des Moduls in wenigen Zeilen auflisten? Wenn nicht, macht es wahrscheinlich zu viel.
  • Ownership: Wer ist verantwortlich für Verhalten und Änderungen? Ungepflegte Module werden zu Abladeplätzen.
  • Abhängigkeiten: Hängt es von „allen“ Dingen ab oder nur von dem, was es wirklich braucht? Weniger Abhängigkeiten bedeuten weniger Überraschungsbrüche.

Gut gezogene Grenzen verwandeln „Änderung“ von einem System-Ereignis in eine lokale Bearbeitung.

Warum diese Ideen bei Wachstum gewinnen (Teams, Codebasen und Zeit)

Wenn Software klein ist, kannst du „alles im Kopf behalten“. Bei Skalierung hört das auf wahr zu sein — und die Ausfallmodi werden vertraut.

Typische Symptome sehen so aus:

  • Ausfälle, die auf einen überraschenden Randfall zurückzuführen sind
  • Releases, die langsamer werden, weil jede Änderung riskant wirkt
  • Fragile Integrationen, bei denen ein kleines Update drei nachgelagerte Systeme bricht

Struktur senkt die kognitive Last

Dijkstras Kernwette war, dass Menschen der Engpass sind. Klarer Kontrollfluss, kleine wohldefinierte Einheiten und Code, über den man schlussfolgern kann, sind keine ästhetischen Entscheidungen — sie multiplizieren Kapazität.

In einer großen Codebasis wirkt Struktur wie Kompression für Verständnis. Wenn Funktionen explizite Eingaben/Ausgaben haben, Module Grenzen, die man benennen kann, und der „Happy Path“ nicht mit jedem Randfall verknotet ist, verbringen Entwickler weniger Zeit damit, Absicht zu rekonstruieren, und mehr Zeit damit, gezielte Änderungen vorzunehmen.

Es skaliert mit Teams, nicht nur mit Code

Wenn Teams wachsen, steigen die Kommunikationskosten schneller als die Zeilenzahl. Disziplinierter, lesbarer Code reduziert die Menge an Stammeswissen, die nötig ist, um sicher beizutragen.

Das zeigt sich sofort beim Onboarding: neue Entwickler folgen vorhersagbaren Mustern, lernen ein kleines Set an Konventionen und können Änderungen vornehmen, ohne eine lange Tour durch „Fallstricke“ zu brauchen. Der Code lehrt das System.

Vorfälle werden einfacher zu debuggen — und sicherer zurückzunehmen

Während eines Vorfalls sind Zeit und Vertrauen knapp. Code mit expliziten Annahmen (Vorbedingungen), sinnvollen Prüfungen und geradem Kontrollfluss ist unter Druck leichter nachzuverfolgen.

Wichtiger noch: disziplinierte Änderungen sind leichter rückgängig zu machen. Kleinere, lokalisierte Änderungen mit klaren Grenzen reduzieren die Chance, dass ein Rollback neue Fehler auslöst. Das Ergebnis ist keine Perfektion — sondern weniger Überraschungen, schnellere Erholung und ein System, das über Jahre und Mitwirkende hinweg wartbar bleibt.

Dijkstra anwenden, ohne dogmatisch zu werden

Liefere eine lesbare Web-App
Wandle eine klare Spezifikation per Chat in eine React-Web-App um – in einfachen, prüfbaren Teilen.

Dijkstras Kern war nicht „schreibe Code auf die alte Art“. Er sagte: „schreibe Code, den du erklären kannst.“ Du kannst diese Denkweise übernehmen, ohne jedes Feature in ein formales Beweis-Exercise zu verwandeln.

Prinzipien in tägliche Gewohnheiten verwandeln

Fange mit Entscheidungen an, die das Nachvollziehen billig machen:

  • Bevorzuge einfachen Kontrollfluss: wenige kleine Funktionen statt einer großen Multi-Branch-„Alles tun“-Routine.
  • Reduziere Seiteneffekte: halte Mutation nah dort, wo sie nötig ist, und lass Funktionen nicht stillschweigend globalen Zustand ändern.
  • Nutze klare Verträge: mache Eingaben, Ausgaben und Fehlerverhalten explizit (in Typen, Namen und Kommentaren).

Eine gute Heuristik: Wenn du nicht in einem Satz zusammenfassen kannst, was eine Funktion garantiert, macht sie wahrscheinlich zu viel.

Kleine „Struktur-Upgrades“ (ohne Rewrites)

Du brauchst keinen großen Refactor-Sprint. Füge Struktur an den Nahtstellen hinzu:

  • Extrahiere eine komplexe Schleife in eine benannte Funktion und definiere, was in jeder Iteration wahr bleiben muss.
  • Ersetze „magische" Conditionals durch wohlbenannte Prädikate (z. B. isEligibleForRefund).
  • Kapsle einen kniffligen Zustandsübergang hinter einer einzigen Funktion, damit der Rest des Codes ihn nicht missbrauchen kann.

Diese Upgrades sind inkrementell: Sie senken die kognitive Last für die nächste Änderung.

Code-Review-Prompts, die dich ehrlich halten

Beim Review (oder Schreiben) einer Änderung frage:

  • „Was muss hier wahr sein?“ (Invarianten, Annahmen, notwendiger Zustand)
  • „Was kann sich sicher ändern?“ (welche Teile dürfen variieren, ohne Callers zu brechen)

Wenn Reviewer das nicht schnell beantworten können, signalisiert der Code versteckte Abhängigkeiten.

Dokumentiere das Nachvollziehen, nicht nur die Schritte

Kommentare, die den Code wiederholen, werden schnell veraltet. Schreibe stattdessen warum der Code korrekt ist: die Schlüsselfannahmen, die bewachten Randfälle und was schiefgehen würde, wenn sich diese Annahmen ändern. Eine kurze Notiz wie „Invariante: total entspricht immer der Summe der verarbeiteten Items“ kann wertvoller sein als ein Absatz Narration.

Wenn du einen leichten Ort möchtest, um diese Gewohnheiten festzuhalten, sammle sie in einer geteilten Checkliste (siehe /blog/practical-checklist-for-disciplined-code).

Wo KI-gestütztes Bauen hineinpasst (ohne die Disziplin zu verlieren)

Moderne Teams nutzen zunehmend KI, um die Lieferung zu beschleunigen. Das Risiko ist vertraut: Geschwindigkeit heute kann zu Verwirrung morgen werden, wenn der generierte Code schwer zu erklären ist.

Ein Dijkstra-freundlicher Weg, KI zu nutzen, ist sie als Beschleuniger für strukturiertes Denken zu behandeln, nicht als Ersatz dafür. Beispielsweise beim Arbeiten mit Koder.ai — einer Vibe-Coding-Plattform, auf der du Web-, Backend- und Mobile-Apps per Chat erstellst — kannst du die „Nachvollziehen zuerst“-Gewohnheiten beibehalten, indem du Prompts und Review-Schritte explizit machst:

  • Fordere klare Verträge an: „Definiere Vorbedingungen, Nachbedingungen und Fehlerverhalten für diesen Endpoint."
  • Frage nach Invarianten in zustandsbehafteten Abläufen: „Was muss nach jedem Schritt dieser Checkout-State-Maschine wahr bleiben?"
  • Nutze Planungsmodus, um zuerst in kleine, reviewbare Teile (Module, Schnittstellen, Verantwortlichkeiten) zu zerlegen, bevor du Implementierungsdetails generieren lässt.
  • Verlasse dich auf Snapshots und Rollbacks, um Änderungen klein und rückgängig machbar zu halten — das spiegelt die Disziplin von lokalisierten Änderungen und sicheren Undo-Pfaden wider.

Auch wenn du den Code schließlich exportierst und anderswo ausführst, gilt dasselbe Prinzip: Generierter Code sollte erklärbar sein.

Eine praktische Checkliste für korrekten, einfachen, disziplinierten Code

Das ist eine leichtgewichtige „Dijkstra-freundliche“ Checkliste für Reviews, Refactors oder vor dem Mergen. Es geht nicht darum, den ganzen Tag Beweise zu schreiben — sondern darum, Korrektheit und Klarheit zur Default-Einstellung zu machen.

Schneller Selbstcheck (neuer Code und Refactors)

  • Kann ich den Code einem Teamkollegen in 60 Sekunden erklären? Wenn die Erklärung viel „Vertrau mir“ braucht, vereinfache.
  • Ist der Kontrollfluss offensichtlich? Bevorzuge geradlinigen Code; halte Schleifen und Verzweigungen klein; vermeide versteckte Exits und tief verschachtelte Branches.
  • Was sind Vor- und Nachbedingungen? Schreibe sie als Kommentar, Docstring oder in den Funktionsnamen. Wenn du sie nicht benennen kannst, macht die Funktion wahrscheinlich zu viel.
  • Macht jede Funktion genau eine Sache und hat eine klare Grenze? Eingaben rein, Ausgaben raus — minimale Abhängigkeit von globalem Zustand.
  • Welche Invariante hält diese Schleife ehrlich? Selbst eine Ein-Zeilen-Notiz wie „total entspricht der Summe der verarbeiteten Items“ verhindert subtile Fehler.
  • Gibt es weniger „clevere“ Tricks als nötig? Wenn der Code eine Führung benötigt, häufst du Cleverness Debt an.

Qualitativ zu messende Dinge

  • Leichtigkeit der Erklärung: Kann jemand Unbekanntes das Modul erklären und sagen, warum es korrekt ist?
  • Leichtigkeit des Testens: Sind Randfälle natürlich testbar oder braucht es aufwändige Setups und Mocking?
  • Änderungsrisiko: Wenn Anforderungen sich verschieben, kannst du vorhersagen, was bricht? Wenn jede Änderung beängstigend wirkt, sickern Grenzen durch.

Ein praktischer nächster Schritt

Wähle ein unordentliches Modul und strukturierte zuerst den Kontrollfluss:

  1. Extrahiere kleine Funktionen mit klaren Namen.
  2. Ersetze verknotete Verzweigungen durch einfachere, explizite Fälle.
  3. Verschiebe Spezialfälle an die Ränder (Input-Validierung, frühe Rückgaben).

Füge dann ein paar fokussierte Tests um die neuen Grenzen hinzu. Wenn du mehr Muster wie dieses willst, stöbere in verwandten Beiträgen unter /blog.

FAQ

Warum ist Dijkstra noch relevant für moderne Softwareteams?

Weil mit wachenden Codebasen der eigentliche Engpass das Verstehen wird – nicht das Tippen. Dijkstras Fokus auf vorhersehbare Kontrollflüsse, klare Verträge und Korrektheit reduziert das Risiko, dass eine „kleine Änderung“ an unerwarteten Stellen Probleme auslöst. Genau diese Probleme verlangsamen Teams über die Zeit.

Was bedeutet „Skalierung“ in diesem Beitrag — Performance oder etwas anderes?

In diesem Beitrag bedeutet „Skalierung“ weniger Leistung und mehr das Wachsen der Komplexität:

  • mehr Features und Randfälle
  • mehr Mitwirkende und Übergaben
  • mehr Integrationen und Fehlerarten
  • mehr Zeit und historische Entscheidungen

Diese Faktoren machen Nachvollziehbarkeit und Vorhersehbarkeit wichtiger als Cleverness.

Was ist strukturierte Programmierung, in praktischen Worten?

Strukturierte Programmierung setzt auf eine kleine Menge klarer Kontrollstrukturen:

  • Sequenz (erst A dann B)
  • Auswahl (if/else, switch)
  • Wiederholung (for, while)

Das Ziel ist nicht Starrheit, sondern Ausführungswege so verständlich zu machen, dass man Verhalten erklären, Änderungen prüfen und ohne „Teleportieren“ debuggen kann.

Warum ist Spaghetti-Kontrollfluss (wie unbeschränktes `goto`) ein Wartungsproblem?

Das Problem ist das unbeschränkte Springen, das schwer vorhersagbare Pfade und unklare Zustände erzeugt. Wenn die Kontrollflüsse verknotet sind, verlieren Entwickler Zeit mit Fragen wie „Wie sind wir hierher gekommen?“ oder „Welcher Wert hat diese Variable jetzt?".

Moderne Äquivalente sind stark verschachtelte Verzweigungen, verstreute frühe Rückgaben und implizite Zustandsänderungen, die das Nachvollziehen erschweren.

Was ist eine praktische Definition von Korrektheit für Produktsoftware?

Korrektheit ist die leise Eigenschaft, auf die sich Nutzer verlassen: das System liefert konsistent das Versprochene und fällt im Fehlerfall vorhersehbar und erklärbar aus. Sie unterscheidet „funktioniert in einigen Beispielen“ von „funktioniert weiter nach Refactorings, Integrationen und Randfällen“.

Warum werden kleine Bugs in großen Systemen teuer?

Weil Abhängigkeiten Fehler verstärken. Ein kleiner falscher Zustand oder ein Grenzfehler wird kopiert, zwischengespeichert, erneut versucht, eingewickelt oder „umgangen“ – über Module und Services hinweg. Mit der Zeit fragt man weniger „Was ist wahr?“ und mehr „Was passiert normalerweise?“, was Vorfälle komplizierter und Änderungen riskanter macht.

Was bedeutet „Einfachheit“ hier (und was nicht)?

Einfachheit heißt hier: wenige Konzepte gleichzeitig im Kopf behalten — klare Zuständigkeiten, transparenter Datenfluss und minimale Sonderfälle. Es geht nicht um weniger Zeilen oder clevere Einzeiler.

Ein Pragmatismus-Test: Wenn jede neue Anforderung ein „außer…“ erzeugt, summierst du versehentliche Komplexität.

Wie helfen Invarianten im Alltag, ohne formale Beweise zu führen?

Eine Invariante ist eine Tatsache, die während einer Schleife oder Zustandsübergangs wahr bleibt. Leichtgewichtige Nutzung:

  • eine Ein-Zeilen-Bemerkung oberhalb der Schleife (z. B. „total entspricht der Summe der verarbeiteten Positionen“)
  • den Code so anpassen, dass diese Aussage in jeder Iteration eingehalten wird
  • ggf. eine Assertion hinzufügen, wenn sinnvoll

Das macht spätere Änderungen sicherer, weil die nächste Person weiß, was nicht gebrochen werden darf.

Wie sollen Teams Tests und Nachvollziehen ausbalancieren?

Tests finden Bugs durch Beispiele; Nachvollziehen verhindert ganze Klassen von Fehlern, indem Logik explizit gemacht wird. Tests können nicht die Abwesenheit von Fehlern beweisen (sie decken nicht alle Eingaben oder Timing-Fälle). Nachvollziehbarkeit lohnt sich besonders bei kostenintensiven Fehlerbereichen (Geld, Sicherheit, Nebenläufigkeit).

Ein pragmatischer Mix: breite Tests + gezielte Assertions + klare Vor-/Nachbedingungen an kritischen Stellen.

Wie wende ich Dijkstras Ideen inkrementell an, ohne dogmatisch zu werden?

Beginne mit kleinen, wiederholbaren Änderungen, die kognitive Last senken:

  • Extrahiere kleine Funktionen und dokumentiere Eingaben/Ausgaben
  • Ersetze „magische“ Bedingungen durch gut benannte Prädikate
  • Kapsle knifflige Zustandsänderungen hinter einer einzigen Schnittstelle
  • Füge kurze Kommentare hinzu, die warum der Code korrekt ist (Invarianten, Annahmen), nicht nur was er tut

Das sind inkrementelle Struktur-Upgrades, die zukünftige Änderungen günstiger machen, ohne einen Rewrite zu erzwingen.

Related posts