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

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.
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.
Wenn Leute sagen, Software müsse „skalieren“, meinen sie oft Leistung. Dijkstras Punkt ist anders: Komplexität skaliert ebenfalls.
Skalierung zeigt sich als:
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:
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.
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.
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.
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.
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.
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“ 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:
Korrektheit bedeutet, diese versteckten Annahmen zu entfernen — oder sie explizit zu machen.
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.
Klarer Code verbessert Korrektheit, weil er Kommunikation verbessert:
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 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.
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:
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.
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.
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 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.
if/else, switch).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“.
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.
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.
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.
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.
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:
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."
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?“).
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 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.
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.
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.
Verwende Tests breit und wende tieferes Nachvollziehen dort an, wo Fehler teuer sind.
Eine praktische Brücke ist, die Absicht ausführbar zu machen:
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.
„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.
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:
Einige konkrete Gewohnheiten verhindern das Ansammeln von Cleverness Debt:
calculate_total() über do_it()).Disziplin ist nicht Perfektion — es geht darum, die nächste Änderung vorhersehbar zu machen.
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.
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.
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:
Das ist keine Bürokratie — es schafft sichere Koordinationspunkte in einer wachsenden Codebasis.
Du brauchst keine große Architektur-Review, um Modularität zu verbessern. Probiere diese Leichtgewichtschecks:
Gut gezogene Grenzen verwandeln „Änderung“ von einem System-Ereignis in eine lokale Bearbeitung.
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:
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.
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.
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.
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.
Fange mit Entscheidungen an, die das Nachvollziehen billig machen:
Eine gute Heuristik: Wenn du nicht in einem Satz zusammenfassen kannst, was eine Funktion garantiert, macht sie wahrscheinlich zu viel.
Du brauchst keinen großen Refactor-Sprint. Füge Struktur an den Nahtstellen hinzu:
isEligibleForRefund).Diese Upgrades sind inkrementell: Sie senken die kognitive Last für die nächste Änderung.
Beim Review (oder Schreiben) einer Änderung frage:
Wenn Reviewer das nicht schnell beantworten können, signalisiert der Code versteckte Abhängigkeiten.
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).
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:
Auch wenn du den Code schließlich exportierst und anderswo ausführst, gilt dasselbe Prinzip: Generierter Code sollte erklärbar sein.
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.
total entspricht der Summe der verarbeiteten Items“ verhindert subtile Fehler.Wähle ein unordentliches Modul und strukturierte zuerst den Kontrollfluss:
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.
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.
In diesem Beitrag bedeutet „Skalierung“ weniger Leistung und mehr das Wachsen der Komplexität:
Diese Faktoren machen Nachvollziehbarkeit und Vorhersehbarkeit wichtiger als Cleverness.
Strukturierte Programmierung setzt auf eine kleine Menge klarer Kontrollstrukturen:
if/else, switch)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.
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.
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“.
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.
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.
Eine Invariante ist eine Tatsache, die während einer Schleife oder Zustandsübergangs wahr bleibt. Leichtgewichtige Nutzung:
total entspricht der Summe der verarbeiteten Positionen“)Das macht spätere Änderungen sicherer, weil die nächste Person weiß, was nicht gebrochen werden darf.
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.
Beginne mit kleinen, wiederholbaren Änderungen, die kognitive Last senken:
Das sind inkrementelle Struktur-Upgrades, die zukünftige Änderungen günstiger machen, ohne einen Rewrite zu erzwingen.