Edsger Dijkstra’s ideeën over gestructureerd programmeren verklaren waarom gedisciplineerde, eenvoudige code correct en onderhoudbaar blijft naarmate teams, features en systemen groeien.

Software faalt zelden omdat het niet te schrijven is. Het faalt omdat, een jaar later, niemand het veilig kan veranderen.
Als codebases groeien, begint elke “kleine” aanpassing te golven: een bugfix breekt een verafgelegen feature, een nieuwe eis dwingt herschrijvingen af, en een simpele refactor wordt een week van zorgvuldige afstemming. Het moeilijke is niet het toevoegen van code—het is het voorspelbaar houden van gedrag terwijl alles eromheen verandert.
Edsger Dijkstra betoogde dat correctheid en eenvoud eersteklas doelen moeten zijn, geen leuke extra’s. De opbrengst is niet academisch. Als een systeem makkelijker te doorgronden is, besteden teams minder tijd aan brandjes blussen en meer tijd aan bouwen.
Als mensen zeggen dat software moet “scha len”, bedoelen ze vaak performance. Dijkstra’s punt is anders: complexiteit schaalt ook.
Schaal blijkt als:
Gestructureerd programmeren draait niet om streng zijn om het streng zijn. Het gaat om het kiezen van controlestroom en decompositie die het makkelijk maken twee vragen te beantwoorden:
Als gedrag voorspelbaar is, wordt veranderen routine in plaats van riskant. Daarom blijft Dijkstra relevant: zijn discipline richt zich op het echte knelpunt van groeiende software—het voldoende goed begrijpen om die te verbeteren.
Edsger W. Dijkstra (1930–2002) was een Nederlandse informaticus die heeft bijgedragen aan hoe programmeurs denken over het bouwen van betrouwbare software. Hij werkte aan vroege besturingssystemen, droeg bij aan algoritmen (waaronder het naar hem vernoemde kortste-pad-algoritme) en—voor dagelijkse ontwikkelaars het belangrijkst—promootte het idee dat programmeren iets moet zijn waar je over kunt redeneren, niet iets dat je probeert tot het lijkt te werken.
Dijkstra gaf minder om of een programma juist output gaf voor een paar voorbeelden, en meer om of we kunnen uitleggen waarom het correct is voor de relevante gevallen.
Als je kunt zeggen wat een stukje code zou moeten doen, moet je in staat zijn stap voor stap te betogen dat het dat ook doet. Die mindset leidt vanzelf tot code die makkelijker te volgen is, makkelijker te reviewen en minder afhankelijk van heldhaftig debuggen.
Sommige van Dijkstra’s teksten lezen compromisloos. Hij bekritiseerde “slimme” trucs, slordige controlestroom en programmeergewoonten die redeneren moeilijk maken. De strengheid gaat niet om stijlpolitie; het gaat om het verminderen van ambiguïteit. Als de betekenis van code duidelijk is, besteed je minder tijd aan het bespreken van intenties en meer aan het valideren van gedrag.
Gestructureerd programmeren is de praktijk van het bouwen van programma’s uit een kleine set heldere controlestructuren—volgorde, selectie (if/else) en iteratie (lussen)—in plaats van verwarde sprongen. Het doel is simpel: maak het pad door het programma begrijpelijk zodat je het met vertrouwen kunt uitleggen, onderhouden en veranderen.
Mensen beschrijven softwarekwaliteit vaak als “snel”, “mooi” of “feature-rich”. Gebruikers ervaren correctheid anders: als de stille zekerheid dat de app hen niet zal verrassen. Als correctheid aanwezig is, merkt niemand het. Als het ontbreekt, wordt alles anderen onbelangrijk.
“Het werkt nu” betekent meestal dat je een paar paden probeerde en het verwachte resultaat kreeg. “Het blijft werken” betekent dat het zich gedraagt zoals bedoeld over tijd, randgevallen en veranderingen—na refactors, nieuwe integraties, hogere load en nieuwe teamleden die aan de code zitten.
Een feature kan “nu werken” terwijl hij fragiel is:
Correctheid gaat over het weghalen van deze verborgen aannames—of ze expliciet maken.
Een klein probleem blijft zelden klein als software groeit. Één onjuiste toestand, één off-by-one grens of één onduidelijke foutafhandelingsregel wordt gekopieerd naar nieuwe modules, omringd door diensten, gecached, opnieuw geprobeerd of “omzeild”. Na verloop van tijd stoppen teams met vragen “wat is waar?” en beginnen ze te vragen “wat gebeurt er meestal?” Dat is het moment waarop incidentrespons archeologie wordt.
De vermenigvuldiger is afhankelijkheid: een klein misgedrag wordt veel downstream-misgedragingen, elk met een eigen gedeeltelijke fix.
Duidelijke code verbetert correctheid omdat het communicatie verbetert:
Correctheid betekent: voor de inputs en situaties die we claimen te ondersteunen, produceert het systeem consistent de uitkomsten die we beloven—en faalt het op voorspelbare, verklaarbare manieren wanneer het dat niet kan.
Eenvoud gaat niet om code “schattig” maken, minimaal of slim. Het gaat om gedrag makkelijk voorspelbaar, uitlegbaar en wijzigbaar maken zonder angst. Dijkstra waardeerde eenvoud omdat het ons vermogen om over programma’s te redeneren vergroot—vooral als de codebase en het team groeien.
Eenvoudige code houdt een klein aantal ideeën tegelijk in beweging: duidelijke datastroom, duidelijke controlestroom en duidelijke verantwoordelijkheden. Het dwingt de lezer niet om veel alternatieve paden in zijn hoofd te simuleren.
Eenvoud is geen:
Veel systemen worden moeilijk te veranderen, niet omdat het domein inherent complex is, maar omdat we accidentele complexiteit introduceren: flags die op onverwachte manieren met elkaar interacteren, patches voor speciale gevallen die nooit worden verwijderd, en lagen die vooral bestaan om eerdere beslissingen te omzeilen.
Elke extra uitzondering is een belasting op begrip. De kosten verschijnen later, wanneer iemand een bug probeert te repareren en ontdekt dat een wijziging in één gebied subtiel meerdere andere breekt.
Als een ontwerp eenvoudig is, komt vooruitgang door steady werk: reviewbare wijzigingen, kleinere diffs en minder noodreparaties. Teams hebben geen “helden” nodig die alle historische randgevallen onthouden of onder druk kunnen debuggen om 2 uur ’s nachts. In plaats daarvan ondersteunt het systeem normale menselijke aandachtsspanne.
Een praktische test: als je steeds uitzonderingen toevoegt (“tenzij…”, “behalve wanneer…”, “alleen voor deze klant…”), dan stapel je waarschijnlijk accidentele complexiteit op. Geef de voorkeur aan oplossingen die vertakkingen in gedrag verminderen—één consistente regel verslaat vijf special cases, zelfs als die consistente regel iets algemener is dan je in eerste instantie bedacht.
Gestructureerd programmeren is een eenvoudig idee met grote gevolgen: schrijf code zodat het uitvoeringspad makkelijk te volgen is. Simpel gezegd kunnen de meeste programma’s worden gebouwd uit drie bouwstenen—sequence, selection en repetition—zonder te vertrouwen op verwarde sprongen.
if/else, switch).for, while).Als controlestroom uit deze structuren is opgebouwd, kun je meestal uitleggen wat het programma doet door van boven naar beneden te lezen, zonder te hoeven "teleporteren" door het bestand.
Voordat gestructureerd programmeren de norm werd, leunden veel codebases zwaar op willekeurige sprongen (klassieke goto-stijl). Het probleem was niet dat sprongen altijd slecht zijn; het is dat onbeperkt springen uitvoeringspaden creëert die moeilijk te voorspellen zijn. Je stelt dan vragen als “Hoe kwamen we hier?” en “Welke staat heeft deze variabele?”—en de code geeft geen helder antwoord.
Duidelijke controlestroom helpt mensen een correct mentaal model te bouwen. Dat model is waar je op vertrouwt bij debuggen, het reviewen van een pull request of het veranderen van gedrag onder tijdsdruk.
Als structuur consistent is, wordt aanpassen veiliger: je kunt één tak veranderen zonder per ongeluk een andere te beïnvloeden, of een lus refactoren zonder een verborgen exitpad te missen. Leesbaarheid is niet alleen esthetiek—het is de basis om gedrag zelfverzekerd te veranderen zonder te breken wat al werkt.
Dijkstra propageerde een eenvoudig idee: als je kunt uitleggen waarom je code correct is, kun je het met minder angst veranderen. Drie kleine redeneringshulpmiddelen maken dat praktisch—zonder je team in wiskundigen te veranderen.
Een invariant is een feit dat waar blijft terwijl een stukje code draait, vooral binnen een lus.
Voorbeeld: je telt prijzen in een winkelwagen op. Een nuttige invariant is: “total is gelijk aan de som van alle verwerkte items.” Als dat bij elke stap waar blijft, dan is het resultaat betrouwbaar als de lus eindigt.
Invarianten zijn krachtig omdat ze je aandacht richten op wat nooit mag breken, niet alleen op wat er daarna moet gebeuren.
Een preconditie is wat waar moet zijn voordat een functie draait. Een postconditie is wat de functie garandeert nadat hij klaar is.
Alledaagse voorbeelden:
In code kan een preconditie zijn “invoerlijst is gesorteerd”, en de postconditie “uitvoerlijst is gesorteerd en bevat dezelfde elementen plus de ingevoegde.”
Als je deze uitspraken opschrijft (zelfs informeel), wordt het ontwerp scherper: je bepaalt wat een functie verwacht en belooft, en je maakt hem vanzelf kleiner en meer gefocust.
In reviews verschuift het debat van stijl (“Ik zou het anders schrijven”) naar correctheid (“Handhaaft deze code de invariant?” “Handhaven we de preconditie of documenteren we hem?”).
Je hebt geen formele bewijzen nodig om te profiteren. Kies de buggyste lus of de lastigste statusupdate en voeg er een één-regel invariant-commentaar boven. Als iemand later de code wijzigt, fungeert die comment als vangrail: als een wijziging dit feit breekt, is de code niet langer veilig.
Testen en redeneren streven naar hetzelfde resultaat—software die zich gedraagt zoals bedoeld—maar ze werken heel verschillend. Tests ontdekken problemen door voorbeelden te draaien. Redeneren voorkomt hele categorieën problemen door logica expliciet en controleerbaar te maken.
Tests zijn een praktisch vangnet. Ze vangen regressies, verifiëren realistische scenario’s en documenteren verwacht gedrag op een manier die het hele team kan draaien.
Maar tests kunnen alleen de aanwezigheid van bugs aantonen, niet de afwezigheid ervan. Geen testset dekt elke input, elke timingvariatie of elke interactie tussen features. Veel “werkt op mijn machine”-fouten komen door ongeteste combinaties: een zeldzame invoer, een specifieke volgorde van operaties of een subtiele status die pas na meerdere stappen verschijnt.
Redeneren gaat over het bewijzen van eigenschappen van de code: “deze lus stopt altijd”, “deze variabele wordt nooit negatief”, “deze functie retourneert nooit een ongeldig object.” Goed gedaan sluit het hele klassen van defecten uit—vooral rond grenzen en randgevallen.
De beperking is moeite en scope. Volledige formele bewijzen voor een heel product zijn zelden economisch. Redeneren werkt het best wanneer het selectief wordt toegepast: kernalgoritmen, security-gevoelige flows, geld- of facturatie-logica en concurrency.
Gebruik tests breed, en pas diepgaander redeneren toe waar falen duur is.
Een praktische brug tussen beide is intent uitvoerbaar maken:
Deze technieken vervangen tests niet—ze maken het net strakker. Ze veranderen vage verwachtingen in controleerbare regels, waardoor bugs moeilijker te schrijven en makkelijker te diagnosticeren zijn.
“Clever” code voelt vaak als winst op het moment: minder regels, een nette truc, een one-liner die je slim doet voelen. Het probleem is dat cleverheid niet schaalt over tijd of over mensen. Zes maanden later vergeet de auteur de truc. Een nieuwe collega leest het letterlijk, mist de verborgen aanname en verandert het op een manier die gedrag breekt. Dat is “cleverness debt”: kortetermijnsnelheid gekocht met langetermijnverwarring.
Dijkstra’s punt was niet “schrijf saaie code” als stijlvoorkeur—het was dat gedisciplineerde constraints programma’s makkelijker maakten om over te redeneren. In een team verminderen constraints ook beslissingsvermoeidheid. Als iedereen de defaults al kent (hoe te benoemen, hoe functies op te bouwen, wat “klaar” betekent), hoef je niet steeds de basics in elke pull request te heropenen. Die tijd gaat terug in productwerk.
Discipline verschijnt in routinepraktijken:
Een paar concrete gewoonten voorkomen dat cleverness debt zich ophoopt:
do_it(); gebruik calculate_total()).Discipline gaat niet over perfectie—het gaat over de volgende wijziging voorspelbaar maken.
Modulariteit is niet alleen “code in bestanden splitsen.” Het gaat om beslissingen te isoleren achter duidelijke grenzen, zodat de rest van het systeem de interne details niet hoeft te weten (of zich erom bekommert). Een module verbergt de rommelige delen—datastructuren, randgevallen, performance-trucks—en biedt een klein, stabiel oppervlak.
Als een wijzigingsverzoek binnenkomt, is de ideale uitkomst: één module verandert en alles blijft verder onaangeroerd. Dat is de praktische betekenis van “verandering lokaal houden.” Grenzen voorkomen accidentele koppeling—waarbij bijwerken van één feature stilletjes drie anderen breekt omdat ze aannames deelden.
Een goede grens maakt redeneren ook eenvoudiger. Als je kunt zeggen wat een module garandeert, kun je over het grotere programma redeneren zonder elke implementatie keer op keer te herlezen.
Een interface is een belofte: “Geef deze inputs, ik produceer deze outputs en houd me aan deze regels.” Als die belofte helder is, kunnen teams parallel werken:
Het gaat niet om bureaucratie—het gaat om veilige coördinatiepunten in een groeiende codebase.
Je hebt geen groots architectuuroverleg nodig om modulariteit te verbeteren. Probeer deze lichte checks:
Goed getekende grenzen veranderen “verandering” van een systeemwijd evenement in een gelokaliseerde bewerking.
Als software klein is, kun je alles “in je hoofd houden.” Op schaal stopt dat en de faalmodi worden herkenbaar.
Veelvoorkomende symptomen zien er zo uit:
Dijkstra’s kernzet was dat mensen de bottleneck zijn. Duidelijke controlestroom, kleine goed gedefinieerde eenheden en code waar je over kunt redeneren zijn geen esthetische keuzes—het zijn capaciteitsvermenigvuldigers.
In een grote codebase werkt structuur als compressie voor begrip. Als functies expliciete inputs/outputs hebben, modules grenzen die je kunt benoemen en het “happy path” niet verward is met elk randgeval, besteden ontwikkelaars minder tijd aan het reconstructeren van intentie en meer tijd aan doelgerichte wijzigingen.
Als teams groeien, stijgen communicatiekosten sneller dan regelaantallen. Gedisciplineerde, leesbare code vermindert de hoeveelheid tribale kennis die nodig is om veilig bij te dragen.
Dat zie je direct in onboarding: nieuwe engineers kunnen voorspelbare patronen volgen, een kleine set conventies leren en wijzigingen doorvoeren zonder lange rondleiding langs de “valkuilen.” De code leert het systeem.
Tijdens een incident is tijd schaars en vertrouwen zwak. Code geschreven met expliciete aannames (precondities), zinvolle checks en eenvoudige controlestroom is makkelijker te traceren onder druk.
Even belangrijk: gedisciplineerde wijzigingen zijn makkelijker terug te draaien. Kleinere, gelokaliseerde edits met duidelijke grenzen verminderen de kans dat een rollback nieuwe fouten veroorzaakt. Het resultaat is geen volmaakte situatie—maar minder verrassingen, snellere herstelacties en een systeem dat onderhoudbaar blijft naarmate jaren en bijdragers zich opstapelen.
Dijkstra’s punt was niet “schrijf code op de oude manier.” Het was “schrijf code die je kunt uitleggen.” Je kunt die mindset overnemen zonder van elke feature een formeel bewijsproject te maken.
Begin met keuzes die redeneren goedkoop maken:
Een goede heuristiek: als je niet kunt samenvatten wat een functie garandeert in één zin, doet hij waarschijnlijk te veel.
Je hebt geen grote refactorsprint nodig. Voeg structuur toe op de naden:
isEligibleForRefund).Deze upgrades zijn incrementeel: ze verlagen de cognitieve last voor de volgende wijziging.
Bij het reviewen (of schrijven) van een wijziging, vraag:
Als reviewers dit niet snel kunnen beantwoorden, signaleert de code verborgen afhankelijkheden.
Commentaren die de code letterlijk herhalen, raken verouderd. Schrijf in plaats daarvan waarom de code correct is: de sleutelassumpties, randgevallen die je bewaakt en wat er kapot zou gaan als die aannames veranderen. Een korte opmerking als “Invariant: total is altijd de som van verwerkte items” kan waardevoller zijn dan een paragraaf narratief.
Als je een lichte plek zoekt om deze gewoonten vast te leggen, verzamel ze dan in een gedeelde checklist voor gedisciplineerde code.
Moderne teams gebruiken steeds vaker AI om levering te versnellen. Het risico is bekend: snelheid vandaag kan morgen tot verwarring leiden als de gegenereerde code moeilijk uit te leggen is.
Een Dijkstra-vriendelijke manier om AI te gebruiken is het te behandelen als versneller voor gestructureerd denken, niet als vervanging daarvan. Bijvoorbeeld bij bouwen in Koder.ai kun je je “redenering eerst”-gewoonten behouden door je prompts en reviewstappen expliciet te maken:
Zelfs als je uiteindelijk de broncode exporteert en elders draait, blijft hetzelfde principe gelden: gegenereerde code moet code zijn die je kunt uitleggen.
Dit is een lichte “Dijkstra-vriendelijke” checklist die je kunt gebruiken tijdens reviews, refactors of voor het mergen. Het gaat niet om dag en nacht bewijzen—het maakt correctheid en duidelijkheid de standaard.
total is altijd de som van verwerkte items” voorkomt subtiele fouten.Kies één rommelige module en structureer eerst de controlestroom:
Voeg daarna een paar gerichte tests toe rond de nieuwe grenzen. Voor meer patronen als deze, bekijk gerelateerde berichten op het blog.
Omdat naarmate codebases groeien het belangrijkste knelpunt begrip is — niet typen. Dijkstra’s nadruk op voorspelbare controlestroom, duidelijke contracten en correctheid vermindert het risico dat een “kleine wijziging” ergens anders verrassend gedrag veroorzaakt, en juist dát vertraagt teams in de loop van de tijd.
In deze context gaat “schaal” minder over performance en meer over complexiteit die zich vermenigvuldigt:
Deze krachten maken redeneren en voorspelbaarheid waardevoller dan cleverheid.
Gestructureerd programmeren geeft de voorkeur aan een kleine set duidelijke controlestucturen:
if/else, switch)for, while)Het doel is geen starheid, maar dat uitvoeringspaden makkelijk te volgen zijn zodat je gedrag kunt uitleggen, veranderingen kunt reviewen en debuggen zonder door het bestand te "teleporteren".
Het probleem is onbeperkt springen dat uitvoeringspaden moeilijk voorspelbaar maakt en de staat onduidelijk laat. Als de controlestroom verward raakt, verspillen ontwikkelaars tijd met basisvragen als “Hoe kwamen we hier?” en “Welke staat is geldig nu?”.
Moderne evenknieën zijn diep geneste vertakkingen, verspreide vroege returns en impliciete statuswijzigingen die gedrag lastig te traceren maken.
Correctheid is het “stille kenmerk” waarop gebruikers vertrouwen: het systeem doet consequent wat het belooft en faalt op voorspelbare, verklaarbare manieren wanneer het niet kan. Het onderscheidt “werkt in een paar voorbeelden” van “blijft werken na refactors, integraties en randgevallen”.
Omdat afhankelijkheden fouten versterken. Een kleine onjuiste toestand of grensfout wordt gekopieerd, gecached, opnieuw geprobeerd, ingepakt en ‘gewerkt rondom’ in modules en services. Na verloop van tijd stoppen teams met vragen “wat is waar?” en vertrouwen ze op “wat meestal gebeurt”, wat incidenten moeilijker en wijzigingen risicovoller maakt.
Eenvoud betekent weinig ideeën tegelijk in beweging: duidelijke verantwoordelijkheden, duidelijke datastroom en zo min mogelijk uitzonderingen. Het gaat niet over minder regels code of slimme one-liners.
Een goede test is of gedrag makkelijk voorspelbaar blijft als eisen veranderen. Als elke nieuwe situatie een “tenzij…” toevoegt, stapel je accidentele complexiteit op.
Een invariant is een feit dat waar moet blijven tijdens een lus of statusovergang. Een lichte manier om het te gebruiken:
total is de som van verwerkte items”)Dit maakt latere bewerkingen veiliger omdat de volgende persoon weet wat niet mag breken.
Tests vinden bugs door voorbeelden te draaien; redeneren voorkomt categorieën bugs door logica expliciet en verifieerbaar te maken. Tests kunnen geen afwezigheid van fouten bewijzen omdat ze niet elke input of timing kunnen dekken. Redeneren is vooral zinvol voor dure faalgebieden (geld, beveiliging, concurrency).
Een praktische mix is: brede tests + gerichte asserties + duidelijke precondities/postcondities rond kritieke logica.
Begin met kleine, herhaalbare stappen die cognitieve last verminderen:
Dit zijn incrementele “structure upgrades” die de volgende wijziging goedkoper maken zonder een rewrite.