De softwarewaarheden van Joel Spolsky helpen nog steeds nu AI snel code kan schrijven. Leer hoe je tests, werving en eenvoud richt op correctheid.

AI kan er in enkele minuten uitzien alsof het werkende code produceert. Dat verandert het tempo van een project, maar het verandert niet wat software succes laat hebben. De lessen in Joel Spolsky’s “softwarewaarheden” gingen nooit echt over typesnelheid. Ze gingen over oordeelsvermogen, feedbackloops en het vermijden van zelfopgelegde complexiteit.
Wat wél veranderd is, is de kost om code te creëren. Je kunt om drie benaderingen, vijf variaties of een volledige herschrijving vragen en meteen iets terugkrijgen. Wat niet veranderd is, is de kost van het kiezen van de juiste aanpak, het controleren ervan en er maanden mee leven. Tijd die je bespaart met schrijven verschuift vaak naar besluiten over wat je bedoelde, het valideren van randgevallen en het voorkomen dat een snelle overwinning van vandaag de onderhoudslast van morgen wordt.
Correctheid, beveiliging en onderhoudbaarheid kosten nog steeds echte tijd omdat ze vertrouwen op bewijs, niet op vertrouwen. Een loginflow is niet af als het compileert. Het is af wanneer het betrouwbaar slechte inputs weigert, vreemde toestanden afhandelt en geen data lekt. AI kan zeker klinken terwijl het één cruciaal detail mist, zoals een permissiecontrole op een endpoint of een raceconditie bij een betalingsupdate.
AI is het sterkst wanneer je het behandelt als een snelle conceptgenerator. Het blinkt uit in boilerplate, repetitieve patronen, snelle refactors en het verkennen van opties die je naast elkaar kunt vergelijken. Goed gebruikt comprimeert het de “lege pagina”-fase.
AI doet het meeste kwaad wanneer je het vaag doelen geeft en de output voor waar aanneemt. Dezelfde faalpatronen duiken steeds weer op: verborgen aannames (onuitgesproken bedrijfsregels), ongeprobeerde paden (foutafhandeling, retries, lege staten), zelfverzekerde fouten (plausibele code die subtiel verkeerd is) en “slimme” oplossingen die later moeilijk uit te leggen zijn.
Als code goedkoop is, wordt vertrouwen de nieuwe schaarse hulpbron. Deze waarheden zijn belangrijk omdat ze dat vertrouwen beschermen: met gebruikers, met teamgenoten en met je toekomstige zelf.
Wanneer AI een feature in enkele minuten kan genereren, is het verleidelijk om testen als het trage deel te zien dat je moet elimineren. Spolsky’s punt blijft geldig: het trage deel is waar de waarheid zit. Code is makkelijk te produceren. Correct gedrag niet.
Een nuttige verschuiving is om tests te behandelen als uitvoerbare requirements. Als je het verwachte gedrag niet beschrijft op een controleerbare manier, ben je nog niet klaar met nadenken. In AI-ondersteund werk wordt dit belangrijker, niet minder, omdat het model vol vertrouwen iets kan produceren dat net iets fout is.
Begin met testen voor de dingen die het meeste schade zouden doen als ze stukgaan. Voor de meeste producten zijn dat kernflows (aanmelden, afrekenen, opslaan, export), permissies (wie kan bekijken, bewerken, verwijderen) en data-integriteit (geen duplicaten, juiste totalen, veilige migraties). Dek daarna de randen die vaak nachtelijke incidenten veroorzaken: lege inputs, lange tekst, tijdzones, retries en wankele externe grenzen zoals betalingen, e-mails en bestandsuploads.
AI is goed in het voorstellen van testgevallen, maar het kan niet weten wat je daadwerkelijk aan gebruikers hebt beloofd. Gebruik het als een brainstormpartner: vraag om ontbrekende randgevallen, misbruikscenario’s en permissiecombinaties. Doe daarna het menselijke werk: match dekking aan je echte regels en verwijder tests die alleen “de implementatie testen” in plaats van het gedrag.
Maak fouten actiegericht. Een falende test moet je vertellen wat er stuk is, niet je op een speurtocht sturen. Houd tests klein, noem ze alsof het zinnen zijn en maak foutmeldingen specifiek.
Stel dat je met AI-hulp een simpele "team notes"-app bouwt. CRUD-schermen verschijnen snel. Het correctheidsrisico is niet de UI. Het gaat om toegangscontrole en dataregels: een gebruiker mag de notities van een ander team niet zien, bewerkingen mogen geen nieuwere wijzigingen overschrijven en het verwijderen van een notitie mag geen verweesde bijlagen achterlaten. Tests die deze regels vastleggen zullen als het knelpunt voelen, maar ze zijn ook je vangnet.
Wanneer testen het knelpunt is, dwingt het duidelijkheid af. Die duidelijkheid zorgt ervoor dat snelle code niet in snelle bugs verandert.
Een van de meest duurzame waarheden is dat simpele code wint van slimme code. AI maakt het verleidelijk om mooie abstracties te accepteren omdat ze gepolijst en snel verschijnen. De kosten tonen zich later: meer plekken waar bugs zich kunnen verstoppen, meer bestanden om door te zoeken en meer “wat doet dit eigenlijk?”-momenten.
Als code goedkoop is, is complexiteit wat je betaalt. Een klein, saai ontwerp is eenvoudiger te testen, makkelijker te veranderen en makkelijker uit te leggen. Dat telt extra wanneer het eerste concept van een model kwam dat vol vertrouwen kan klinken terwijl het subtiel verkeerd is.
Een praktische regel is om functies, componenten en modules klein genoeg te houden zodat een teamgenoot ze in enkele minuten kan reviewen, niet in uren. Als een React-component meerdere custom hooks, een lokale statusmachine en een generieke “smart renderer”-laag nodig heeft, pauzeer dan en vraag of je een echt probleem oplost of gewoon architectuur accepteert omdat AI het aangereikt heeft.
Een paar “eenvoud-tests” helpen je terug te duwen:
Prompts doen er hier toe. Als je vraagt om “de beste architectuur” krijg je vaak een overgebouwd ontwerp. Vraag om beperkingen die naar minder bewegende delen sturen. Bijvoorbeeld: gebruik de eenvoudigste aanpak met de minste bestanden; vermijd nieuwe abstracties tenzij ze duplicatie op drie of meer plekken wegnemen; geef de voorkeur aan expliciete code boven generieke helpers.
Een concreet voorbeeld: je vraagt AI om rolgebaseerde toegang toe te voegen aan een admin-pagina. De slimme versie introduceert een permissiekader, decorators en een config-DSL. De simpele versie checkt de gebruikersrol op één plaats, blokkeert routes op één plaats en logt geweigerde toegang. De simpele versie is makkelijker te reviewen, makkelijker te testen en moeilijker verkeerd te interpreteren.
Als je bouwt in een chat-gebaseerde tool zoals Koder.ai, maken eenvoud en kleine wijzigingen snapshots en rollback waardevoller. Kleine, voor de hand liggende veranderingen zijn makkelijker te vergelijken, bewaren of terugdraaien.
Wanneer code makkelijk te produceren is, is de zeldzame vaardigheid kiezen wat er überhaupt zou moeten bestaan en zorgen dat het klopt. Het oude advies “huur geweldige programmeurs” blijft gelden, maar de rol verschuift. Je huurt geen iemand om sneller te typen. Je huurt iemand om te oordelen, verfijnen en het product te verdedigen.
De meest waardevolle mensen in AI-ondersteunde ontwikkeling delen vaak vier eigenschappen: oordeel (wat belangrijk is), smaak (wat goed eruitziet), debugging-vaardigheid (de echte oorzaak vinden) en communicatie (trade-offs duidelijk maken). Zij kunnen een door AI geschreven feature die “grotendeels werkt” omzetten in iets dat je kunt vertrouwen.
In plaats van te vragen om vanaf nul een perfecte oplossing, geef kandidaten een AI-gegenereerde pull request (of een geplakte diff) met een paar realistische problemen: onduidelijke naamgeving, een verborgen randgeval, missende tests en een kleine beveiligingsfout.
Vraag hen uit te leggen wat de code probeert te doen in gewone taal, de grootste risico’s te vinden, fixes voor te stellen en (of tests toe te voegen of te schetsen) tests die regressies zouden vangen. Als je een sterk signaal wilt, vraag ook hoe ze de instructies zouden aanpassen zodat de volgende AI-poging beter wordt.
Dit laat zien hoe ze denken onder reële omstandigheden: onvolmaakte code, beperkte tijd en de noodzaak prioriteiten te kiezen.
AI klinkt vaak vol vertrouwen. Goede hires kunnen terugduwen. Ze kunnen nee zeggen tegen een feature die complexiteit toevoegt, nee tegen een verandering die beveiliging verzwakt en nee tegen uitrollen zonder bewijs.
Een concreet signaal is hoe ze reageren op “Zou je dit mergen?” Sterke kandidaten geven geen gevoel; ze geven een besluit en een korte lijst van vereiste wijzigingen.
Voorbeeld: je vraagt om een “snelle” update van toegangscontrole en AI stelt voor checks door handlers te verspreiden. Een sterke kandidaat verwerpt die aanpak en stelt één duidelijke autorisatielaag voor, plus tests voor admin- en non-admin-paden.
Tot slot: bouw gedeelde standaarden zodat het team AI-output op dezelfde manier bewerkt. Houd het simpel: één definitie van gedaan, consistente reviewverwachtingen en een testbaseline.
Wanneer AI veel code in enkele minuten kan genereren, is het verleidelijk om het nadenken over te slaan en gewoon te itereren. Dat werkt voor demo’s. Het faalt wanneer je correctheid, voorspelbaar gedrag en minder verrassingen nodig hebt.
Een goede prompt is vaak een korte specificatie in vermomming. Voordat je om code vraagt, zet het vage doel om in een paar acceptatiecriteria en expliciete non-goals. Dit voorkomt dat AI (en je team) ongemerkt scope uitbreidt.
Houd de spec klein maar specifiek. Je schrijft geen roman. Je stelt grenzen vast rond:
Definieer “done” vóór generatie, niet erna. “Done” moet meer zijn dan “het compileert” of “de UI ziet er goed uit.” Voeg testverwachtingen, backward compatibility en wat er na release gemonitord wordt toe.
Voorbeeld: je wilt “wachtwoord herstellen” toevoegen. Een helderdere specificatie zou kunnen zeggen: gebruikers vragen een reset per e-mail aan; links verlopen na 15 minuten; hetzelfde bericht verschijnt ongeacht of het e-mailadres bestaat; rate limit per IP; log resetpogingen zonder tokens in platte tekst op te slaan. Non-goal: geen redesign van de loginpagina. Nu heeft je prompt begrenzingen en worden reviews eenvoudiger.
Houd een lichtgewicht changelog bij van beslissingen. Eén alinea per beslissing is genoeg. Noteer waarom je een aanpak koos en waarom je alternatieven verwierp. Als iemand over twee weken vraagt “waarom is het zo?”, heb je een antwoord.
De grootste verschuiving met AI is dat code produceren makkelijk is. Het moeilijke deel is beslissen wat de code moet doen en bewijzen dat het dat doet.
Begin met het doel en de beperkingen in gewone taal. Voeg toe wat nooit mag gebeuren, wat langzaam mag zijn en wat buiten scope valt. Een goede beperking is testbaar: “Geen gebruiker mag de data van een andere gebruiker zien” of “Totalen moeten overeenkomen met de financiële export tot op de cent.”
Vraag voordat je om code vraagt om een simpel ontwerp en de trade-offs. Je wilt dat AI zijn redenering toont in een vorm die jij kunt beoordelen: wat het opslaat, wat het valideert en wat het logt. Als het iets slims voorstelt, duw terug en vraag om de eenvoudigste versie die nog steeds aan de beperkingen voldoet.
Een herhaalbare lus ziet er zo uit:
Hier is een klein scenario: je voegt “refund status” toe aan een bestelscherm. AI kan de UI snel genereren, maar correctheid zit in randgevallen. Wat als een terugbetaling gedeeltelijk is? Wat als de betalingsprovider een webhook opnieuw probeert? Schrijf die gevallen eerst, implementeer daarna één slice (databasekolom plus validatie) en verifieer met tests voordat je verdergaat.
Als je Koder.ai gebruikt, passen functies zoals planning mode, snapshots en rollback natuurlijk in deze lus: eerst plannen, in slices genereren en voor elke betekenisvolle wijziging een veilig herstelpunt vastleggen.
Als codegeneratie snel is, is het verleidelijk code als het werkproduct te zien. Dat is het niet. Het werkproduct is gedrag: de app doet het juiste, zelfs als dingen misgaan.
AI klinkt vaak zeker, ook als het raadt. De fout is het overslaan van het saaie deel: tests draaien, randgevallen checken en echte inputs valideren.
Een eenvoudige gewoonte helpt: voordat je een wijziging accepteert, vraag “Hoe weten we dat dit klopt?” Als het antwoord “het ziet er goed uit” is, gok je.
AI voegt graag extras toe: caching, retries, meer instellingen, meer endpoints, een mooiere UI. Sommige ideeën zijn goed, maar ze verhogen risico. Veel bugs ontstaan uit “nice to have”-features die niemand vroeg.
Houd een harde grens: los het probleem op dat je begon, en stop. Als een suggestie waardevol is, leg het vast als een apart taakje met eigen tests.
Een grote AI-gegenereerde commit kan een dozijn ongerelateerde beslissingen verbergen. Review wordt rubber-stamping omdat niemand het geheel kan bevatten.
Behandel chat-output als een concept. Breek het in kleine wijzigingen die je kunt lezen, draaien en terugdraaien. Snapshots en rollback helpen alleen als je ze op verstandige momenten maakt.
Een paar eenvoudige limieten voorkomen de meeste problemen: één feature per change set, één database-migratie per change set, één hoog-risicogebied tegelijk (auth, betalingen, dataverwijdering), tests bijgewerkt in dezelfde wijziging en een duidelijke “hoe te verifiëren”-notitie.
AI kan patronen uit trainingsdata reproduceren of dependencies voorstellen die je niet begrijpt. Zelfs als licenties oké zijn, is het grotere risico security: hard-coded secrets, zwakke tokenafhandeling of onveilige bestands- en query-operaties.
Als je niet kunt uitleggen wat een snippet doet, ship het niet. Vraag om een simpelere versie of schrijf het zelf opnieuw.
Veel “het werkte op mijn machine”-bugs zijn eigenlijk data- en schaalproblemen. AI kan schemawijzigingen maken zonder rekening te houden met bestaande rijen, grote tabellen of downtime.
Een realistisch voorbeeld: het model voegt een nieuwe NOT NULL-kolom toe aan een PostgreSQL-tabel en backfillt die in een trage lus. In productie kan dat de tabel vergrendelen en de app breken. Overweeg altijd wat er gebeurt met een miljoen rijen, een traag netwerk of een mislukte deploy halverwege.
Stel je een klein intern verzoekentrackingsysteem voor: mensen dienen verzoeken in, managers keuren goed of wijzen af en finance markeert items als betaald. Het klinkt simpel en met AI-hulp kun je snel schermen en endpoints genereren. Het deel dat je vertraagt is dezelfde oude waarheid: de regels, niet het typen.
Begin met op te schrijven wat minimaal correct moet zijn. Als je het niet in gewone woorden kunt uitleggen, kun je het niet testen.
Een strakke eersteversie-definitie ziet er vaak zo uit: velden (titel, aanvrager, afdeling, bedrag, reden, status, timestamps); rollen (aanvrager, goedkeurder, finance, admin); statussen (concept, ingediend, goedgekeurd, afgewezen, betaald). Formuleer daarna de overgangen die ertoe doen: alleen een goedkeurder kan van ingediend naar goedgekeurd of afgewezen gaan; alleen finance kan van goedgekeurd naar betaald gaan.
Gebruik AI in een gecontroleerde volgorde zodat je fouten vroeg vangt:
De meest waardevolle tests zijn niet “laadt de pagina.” Het zijn permissiechecks en staatstransities. Bewijs bijvoorbeeld dat een aanvrager niet zijn eigen verzoek kan goedkeuren, een goedkeurder iets niet als betaald kan markeren, afgewezen verzoeken niet betaald kunnen worden en (als dat jouw regel is) bedragen niet bewerkbaar zijn na indiening.
Wat het langst duurt is het verduidelijken van randgevallen. Kan een goedkeurder van gedachten veranderen na afwijzing? Wat als twee goedkeerders tegelijk op approve klikken? Wat als finance gedeeltelijk betaalt? AI kan code genereren voor welk antwoord je ook kiest, maar het kan het antwoord niet voor je kiezen. Correctheid komt van het nemen van die besluiten en het afdwingen ervan in de code.
AI kan veel code snel produceren, maar de laatste mijl is nog steeds menselijk werk: bewijzen dat het doet wat je bedoelde en veilig falen wanneer het dat niet doet.
Voordat je vakjes aankruist, kies je de kleinste “done”-definitie die ertoe doet. Voor een kleine feature kan dat één happy path, twee faalpaths en een snelle leesbaarheidscheck zijn. Voor betalingen of auth, verhoog je de lat.
Stel dat AI "bulk invite users" toevoegt aan een adminscherm. Het happy path werkt, maar het echte risico zijn randgevallen: dubbele e-mails, partiële failures en rate limits. Een solide ship-besluit kan één geautomatiseerde test voor duplicaten zijn, één manuele check voor partiële-failure-messaging en een rollback-plan.
Als code goedkoop is, verschuift het risico naar besliskwaliteit: wat je vroeg, wat je accepteerde en wat je uitrolde. De snelste manier om deze waarheden te laten renderen in AI-ondersteund werk is het toevoegen van vangrails die voorkomen dat “bijna goed” wijzigingen doorheen glippen.
Begin met een één-pagina specificatie voor de volgende feature. Houd het eenvoudig: voor wie is het, wat het moet doen, wat het niet moet doen en een handvol acceptatietests in alledaagse taal. Die acceptatietests worden je anker wanneer AI een verleidelijke snelkoppeling suggereert.
Een set vangrails die schaalt zonder veel procesoverhead:
Prompts zijn nu onderdeel van je proces. Spreek een huisstijl af: welke libraries zijn toegestaan, hoe fouten worden afgehandeld, wat “done” betekent en welke tests moeten slagen. Als een prompt niet door een teamgenoot herbruikbaar is, is hij waarschijnlijk te vaag.
Als je de voorkeur geeft aan een chat-eerst manier om web-, backend- en mobiele apps te bouwen, is Koder.ai (koder.ai) één voorbeeld van een vibe-coding platform waar planning mode, snapshots en source code export deze vangrails kunnen ondersteunen. De tool kan drafts versnellen, maar de discipline houdt mensen verantwoordelijk voor correctheid.
Behandel AI-output als een snel concept, niet als een afgewerkt feature. Begin met 3–5 pass/fail-acceptatiechecks, genereer vervolgens één klein onderdeel (één endpoint, één scherm, één migratie) en verifieer het met tests en foutgevallen voordat je verdergaat.
Omdat tests laten zien wat de code daadwerkelijk doet. AI kan plausibele logica produceren die één belangrijke regel mist (permissions, retries, randgevallen). Tests zetten je verwachtingen om in iets wat je kunt uitvoeren, herhalen en vertrouwen.
Begin met wat het meeste pijn doet:
Breid de dekking uit nadat de “high damage”-gedragingen vastliggen.
Vraag om de eenvoudigste aanpak met expliciete beperkingen en verwijder extra lagen tenzij ze de kost waard zijn. Een goede regel: introduceer geen nieuwe abstractie tenzij deze duplicatie op 3+ plekken verwijdert of het aantonen van correctheid duidelijker maakt.
Schrijf een korte specificatie: inputs, outputs, fouten, constraints en non-goals. Voeg concrete voorbeelden toe (voorbeeld verzoek/antwoord, randgevallen). Definieer daarna “done”: vereiste tests, backward-compatibility en een korte verificatiebeschrijving.
Splits het op. Houd elke change set reviewbaar in enkele minuten:
Zo wordt review echt in plaats van rubber-stampen.
Vertrouw niet op zekerheid—vertrouw op bewijs. Voer tests uit, probeer onjuiste inputs en verifieer permissiegrenzen. Let ook op veelvoorkomende AI-valkuilen: missende auth-checks, onveilige query-building, zwakke tokenafhandeling en stilzwijgend weggooien van fouten.
Geef de voorkeur aan expliciete overgangsendpoints boven een generieke “update anything”. Bijvoorbeeld: submit, approve, reject, pay in plaats van een generieke update-route. Schrijf vervolgens tests die afdwingen wie welke transitie mag doen en welke transities verboden zijn.
Geef kandidaten een AI-gegenereerde diff met reële problemen: onduidelijke namen, een missende test, een randgeval en een kleine beveiligingsfout. Vraag hen de intentie uit te leggen, de grootste risico’s te vinden, fixes voor te stellen en de tests te schetsen die ze zouden toevoegen.
Gebruik toolfeatures om een gedisciplineerde lus te ondersteunen: eerst plannen, in kleine stukken genereren, snapshotten vóór riskante wijzigingen en terugrollen als validatie faalt. In een chatgebaseerd platform zoals Koder.ai passen planning mode, snapshots en rollback hier goed bij—vooral bij auth, betalingen of migraties.