Martin Opdam
Auteur
Martin Opdam Software Developer
Gepubliceerd
09 september 2026
Leestijd
12 minuten

ToernooY: controle houden over AI-geschreven code

Hoe houd je nog controle over een project als een AI vrijwel alle code voor je schrijft? In dit artikel beschrijf ik de werkwijze waarmee ik het overzicht en eigenaarschap over ToernooY’s codebase van 100.000 regels productiecode heb weten te houden, en wat dat betekent voor de rol van de developer.

Over het project ToernooY

Een vriend van me organiseert ieder jaar in december een zaalvoetbaltoernooi en vroeg of ik een website kon maken waarop standen en uitslagen bijgehouden konden worden. Hier bestaan natuurlijk al oplossingen voor, zoals Tournify, maar het leek me leuk om hier samen met AI een eigen website voor te maken.

Ik ben al 28 jaar software developer en heb natuurlijk de laatste jaren ook regelmatig de verschillende AI chatbots gebruikt voor mijn werk, maar ik had nog geen ervaring met agentic coding. Mijn workflow bestond vooral uit vragen stellen in een chatvenster en code heen en weer kopiëren naar mijn IDE. Dit project was voor mezelf de aanleiding om eens te kijken hoe het is om samen met AI aan een softwareproject te werken.


Het logo van ToernooY.

Het logo van ToernooY.

Wat kan ToernooY?

Om een idee te geven van de omvang: ToernooY is een applicatie met een serieuze featureset. Je kunt er complete toernooien mee aanmaken en publiceren via een eigen URL. Teams, spelers, speeldagen, kleedkamers, scheidsrechters en wedstrijdschema's zijn eenvoudig te beheren. De knockout-fase is configureerbaar en de applicatie berekent automatisch de doorstroom van teams. Wedstrijdschema's kunnen als PDF worden geëxporteerd en uitslagen en standen worden tijdens het toernooi live bijgewerkt.

Op https://www.toernooy.nl/t/demo staat een demo-toernooi klaar dat een goed beeld geeft van hoe dit er in de praktijk uitziet.


Het dashboard van Toernooi.

Dit demotoernooi laat zien hoe de website werkt.

In vijf maanden van idee tot eindproduct

Het idee ontstond in januari, en na een korte spikeperiode waarbij ik alleen nog de chat-interface van Claude gebruikte, ben ik in februari overgestapt naar een Claude Code Pro abonnement. Toen ben ik echt begonnen om de applicatie te bouwen. In maart stond een eerste versie live en in april was de applicatie klaar voor het zaalvoetbaltoernooi dat pas in december wordt gespeeld. In mei breidde ik de applicatie uit met ondersteuning voor meerdere velden, met een ander wedstrijdschema-algoritme, voor een veldtoernooi in juni wat inmiddels succesvol is gespeeld.

De applicatie was nu klaar voor wat ik beloofd had te maken, maar omdat dit alles zo makkelijk en snel ging en ik het leuk vond om de applicatie te zien groeien, besloot ik om het verder te ontwikkelen. Je kon nu nog maar één toernooi tegelijk organiseren en alles had dezelfde kleurstijl. Ik wilde een platform bouwen waarop iedereen een account kon maken en eigen toernooien met een eigen huisstijl kon publiceren. In juli stond deze multi-tenant versie live.

Dus vijf maanden nadat ik was gestart met Claude Code had ik een multi-tenant toernooiplatform draaien. Alles in avonduren en weekenden, naast mijn normale baan. Ik schat dat ik er tussen de 400 en 500 uur aan heb besteed. Deze tijd ging nauwelijks naar het schrijven of debuggen van code, maar veel meer naar het bedenken en specificeren van functionaliteit, het beoordelen van de implementatie en testen van de gebouwde features.


Techniek en kwaliteit

ToernooY draait op .NET 10 Minimal APIs, PostgreSQL, Entity Framework, SignalR en React 19 met TypeScript. De applicatie wordt gedeployed in Docker containers op een Linux VPS met Traefik en Let's Encrypt.

Het project is volledig geautomatiseerd. Een Fallout build project bouwt en test zowel frontend als backend. Dit draait in GitHub Actions op elke pull request en bouwt bij een release de Docker images die automatisch worden uitgerold naar de VPS.

De codebase telt zo'n 106.000 regels productiecode (dus exclusief unit tests en comments), waarvan ongeveer 71.000 regels C# en 35.000 regels TypeScript (bepaald met tokei).

Daarnaast zijn er ruim 1.500 backend-tests en bijna 2.000 frontend-tests, met een test coverage van 96% op de backend en 90% op de frontend. Ook zijn er zo’n 30 Playwright tests om de belangrijkste functionaliteit end-to-end te testen.

Van dit alles heb ik vrijwel niets zelf geschreven. Met Claude Code kun je dus heel snel veel code genereren. De uitdaging zit hem dan ook niet in de snelheid waarmee je code schrijft, maar wel hoe je overzicht en controle houdt.


Werkwijze: hoe houd je overzicht en controle?

Controle houden betekent voor mij dat ik van elke wijziging kan uitleggen waarom die zo is gebouwd. Dat ik weet waar een feature leeft en zelf aanpassingen kan doen of bugs kan oplossen als dat nodig is. Kort gezegd: dat de applicatie van mij is en niet van de AI.

Wanneer AI je code schrijft bestaat het risico dat je die controle geleidelijk verliest: een implementatie die je niet goed genoeg beoordeelt, een refactor die je uitstelt, een feature die je te groot hebt gemaakt om nog te overzien. Het gevaar is dat de codebase langzaam een black box wordt die je alleen nog met behulp van Claude kunt aanpassen. Je kunt dan nog steeds functionaliteit toevoegen, maar je kunt niet meer beoordelen of het goed gebeurt. En dat is precies het moment waarop je de verantwoordelijkheid voor je eigen applicatie niet meer kunt waarmaken.

Dit is volgens mij te voorkomen met een manier van werken die voor 'traditionele' software development ook al z'n sporen heeft verdiend:


OpenSpect helpt om gestructureerd met Claude te coderen.

OpenSpec helpt met procesmatig te werk gaan.

Specifieer concreet wat je wil

Hoe beter ik vooraf specificeer wat ik wil, hoe minder ik achteraf hoef bij te sturen. Daarvoor gebruik ik OpenSpec, die o.a. de volgende skills toevoegt aan Claude Code: explore, propose, apply en archive.

Ik start meestal eerst de OpenSpec explore-skill; hiermee onderzoekt Claude wat de feature precies moet worden. De prompts hiervoor komen uit mijn GitHub project van Toernooy, waar de meeste stories al in de vorm van een prompt geschreven zijn. Zo’n explore sessie dwingt me om vooraf goed na te denken over wat ik eigenlijk wil. Claude stelt tijdens zo'n sessie regelmatig vragen waar ik zelf nog niet goed genoeg over had nagedacht. Zowel technisch als functioneel. Ik probeer hierbij zo compleet mogelijk te zijn, maar ik voeg niets toe dat er niet toe doet want irrelevante informatie kan Claude juist op het verkeerde spoor zetten. Ik gebruik mockup-screenshots en een duidelijke uitleg voor hoe ik de UX van een feature wil hebben. Dat werkt best goed en is ook echt nodig, want uit zichzelf zal Claude waarschijnlijk niet de aantrekkelijke en gebruiksvriendelijke UI bouwen die je nodig hebt.

Zodra ik met de OpenSpec explore skill alle details van de feature heb besproken, start ik in dezelfde Claude sessie de OpenSpec propose skill. Deze maakt op basis van ons gesprek een aantal documenten met de rationale van de feature, de gemaakte keuzes van de explore-sessie en een task-list voor de te implementeren wijziging.  Die proposal documenten staan gewoon in je project folder en worden dus ook opgeslagen in Git, zodat ze gewoon meegenomen kunnen worden in een normaal review proces.

Deze twee sessies doorlopen vraagt overigens wel wat van je. Een explore-sessie kan behoorlijk wat lees- en denkwerk zijn. Soms betrap ik mezelf erop dat ik er te snel doorheen ga en de tegenvragen meer scan in plaats van ze echt goed te beantwoorden. Meestal vertaalt zich dat in een feature implementatie die niet aan mijn verwachtingen voldoet.


Hanteer een kleine scope per implementatie

Een feature opbreken in kleine, behapbare brokken is altijd al een goede gewoonte. Met Claude Code is dat nog steeds belangrijk, want Claude werkt niet deterministisch. Je krijgt per keer dat je dezelfde vraag stelt dus een andere output en weet vooraf niet precies wat je terugkrijgt. Hoe groter de scope, hoe moeilijker het voor Claude is om binnen de context te blijven, en hoe moeilijker het voor mij wordt om te beoordelen of het resultaat aan mijn verwachtingen voldoet. Soms stelt Claude zelf voor om een proposal op te breken in kleinere stukken, maar je kunt hier ook zelf pro-actief om vragen.

Ik start de OpenSpec apply-skill in een nieuwe Claude sessie, nadat de proposal documenten zijn gemaakt. Deze voert de implementatie uit op basis van de proposal documenten. Door met een nieuwe Claude sessie te beginnen start je weer met een leeg context window en loop je niet snel tegen een context-limiet aan.

Ik start de OpenSpec archive skill wanneer de feature is geimplementeerd en ik tevreden ben met het resultaat Deze synchroniseert de nieuwe specificaties van de applicatie met alle bestaande specificaties, zodat er een compleet overzicht blijft van wat de applicatie kan en welke beslissingen er onderweg genomen zijn. Dit is enorm belangrijk omdat op die manier niet alleen beschreven is wat de applicatie doet, maar ook waarom bepaalde keuzes zijn gemaakt. Dit helpt Claude om bij latere implementaties de juiste keuzes te kunnen blijven maken.


Blijf refactoren

Claude zal uit zichzelf niet zo snel signaleren dat code te complex wordt. Het initiatief om je code op te schonen en daarmee de kwaliteit van je codebase hoog te houden, ligt dus bij jezelf. 

Van zichzelf heeft Claude de neiging om door te bouwen op bestaande code, wat na verloop van tijd lastig leesbare code oplevert. Het voegt bijvoorbeeld makkelijk een extra `if` of `foreach` toe aan een bestaande methode. In ToernooY zag ik dat terug in de code voor het verwerken van een wedstrijduitslag en het genereren van het wedstrijdschema. Daar waren methodes ontstaan met een cyclomatic complexity van meer dan 60, terwijl ik zelf meestal een maximum van 10 aanhoud. Daarnaast zag ik in de frontend dat bij het introduceren van nieuwe React componenten door Claude vaak prop drilling ontstaat, wat met een Context eenvoudig te verhelpen is.


Refactoren blijft belangrijk, ook bij AI-geschreven code.

Door Claude gegenereerde code moet je handmatig opschonen.

Een review doe ik ofwel zelf, ofwel ik geef Claude de opdracht hiervoor. De refactor laat ik vaak door Claude uitvoeren. Maar ik moet dat dus wel zelf initiëren. Precies daar ging het bij die te complexe methodes mis: in mijn enthousiasme om nieuwe features te bouwen had ik het refactoren te lang laten liggen.


Gebruik goede software development practices

Uiteindelijk verandert AI niets aan wat een applicatie onderhoudbaar maakt. Ik heb gezorgd dat de basisarchitectuur van de applicatie goed staat en gebruik duidelijke naamgeving. Het mooie is dat, naarmate de codebase groeit, Claude de stijl van de code meestal overneemt. Dus als je zorgt dat je in het begin het juiste voorbeeld geeft, heb je daar later profijt van.

Ik investeerde vanaf dag één in goede test coverage. Dat is cruciaal om Claude een manier te geven om te valideren dat zijn wijzigingen niks kapot hebben gemaakt, en het geeft een comfortabel gevoel bij het refactoren. Ook laat ik Claude documentatie schrijven en bijwerken, wat ik zelf uiteraard review. Deze documentatie is zowel voor mezelf als voor Claude handig als naslagwerk bij latere aanpassingen aan de applicatie.

Elke feature test ik zelf, zodat ik zeker weet dat iets werkt zoals ik het bedoeld had. Code review deed ik in het begin vrij grondig, tegenwoordig vooral scannend. Dat komt doordat de `propose`-documenten, die ik al heb gereviewd, alle details en een stappenplan bevatten. Mijn review is daardoor verschoven naar een hoger niveau: ik kijk of de wijziging past binnen de architectuur, of er geen constructies zijn geïntroduceerd die niet bij de rest van de codebase passen en of er niets is gebouwd dat afwijkt van het plan. De details van de implementatie laat ik aan Claude.

Deze werkwijze is vooral een kwestie van discipline. Het staat los van hoeveel Claude Code extensions je daarbij gebruikt.


Claude Code extensions en modelkeuze

Wat ik zelf in het begin wat overweldigend vond is de hoeveelheid skills, agents, commands, hooks en MCP's die je kunt gebruiken om Claude Code uit te breiden. Ik vond het lastig om te bepalen wat ik nodig had, daarom ben ik bewust begonnen met een zeer kleine set en heb dit aangevuld wanneer ik merkte dat ik steeds dezelfde dingen aan het uitleggen of herstellen was. Dit zijn de extensions die ik op dit moment gebruik:

Verder heb ik een claude.md file met daarin o.a. mijn 'Definition of Done' zodat Claude weet wanneer een implementatie klaar is. En dat is alles. Ik heb het idee dat ik nog lang niet alles uit de mogelijkheden haal, want er zijn nog zoveel meer tools en customizations toe te voegen aan Claude Code, en toch heb ik er een volwaardig platform mee gebouwd.


Kiezen tussen de modellen

Als ik niet op het tokengebruik zou hoeven letten, zou ik voor alles het Opus model gebruiken. Maar aangezien je dan wel heel snel door je token budget heen bent, kies ik in de meeste gevallen voor Sonnet. Voor de keuze tussen de verschillende modellen gebruik ik grofweg deze vuistregel:


Dit alles, van werkwijze tot tooling tot modelkeuze, is mijn manier om de controle te houden in dit project. Een logische vraag is nu wat dit alles betekent voor mijn rol als developer.


De Explore sessie in command line met Claude.

Een OpenSpec explore-sessie in Claude.

Hoe verandert de rol van software developer?

Wat ik tijdens dit project heb ervaren, is dat Claude op veel technische vlakken zowel bredere als diepere kennis heeft dan ik. Daardoor was ik in staat om zelf alle rollen in dit project te vervullen, met een snelheid waar normaal meerdere mensen voor nodig zijn. Mijn rol is daarbij verschoven van degene die gedetailleerd bezig is met een implementatie, naar degene die beoordeelt of een oplossing goed is en bijstuurt wanneer die niet voldoet.

Dat lukt me niet overal even goed. Bij frontend styling kan ik veel minder goed beoordelen of een oplossing echt deugt en vertrouw ik erop dat Claude een verstandige keuze maakt. Dat is geen tekortkoming van AI: van een ervaren frontender zou ik de styling ook niet regel voor regel kunnen valideren. Het zit hem erin dat ik als team van één nu eenmaal niet de kennis meebreng die een team met verschillende disciplines wel heeft.

Toch ben ik ervan overtuigd dat je zonder ervaring in software development niet tot hetzelfde resultaat zou komen. Ervaring gaat namelijk verder dan de technologie waarin je die hebt opgedaan. Veel van wat code goed maakt, staat er los van: duidelijke naamgeving, kleine componenten en classes, lage complexiteit, etc. Ik ben geen React-specialist, maar met mijn C#-achtergrond kan ik nog steeds zien of een component te veel doet of moeilijk te begrijpen is. Wat ik niet altijd goed kan beoordelen zijn de details, waar het echt om specialistische kennis gaat. Iemand zonder enige ervaring in software development mist beide.

Maar een team van verschillende disciplines is natuurlijk beter dan één developer met Claude, want zo'n team kan ook de onderdelen valideren waar jij zelf minder goed in bent. In je eentje krijg je een applicatie werkend, maar de controle is kleiner dan ik voor een groot project zou willen. De verantwoordelijkheid blijft: als er iets niet werkt, zoek ik het uit, met Claude of zonder. Maar voor de dingen waar ik minder verstand van heb, ben ik afhankelijk. Ofwel van een collega, ofwel van Claude. En als beide niet beschikbaar zijn zal ik het zelf moeten uitzoeken, maar dat kost dan aanzienlijk meer tijd.


Minder zelf schrijven, evenveel verantwoordelijkheid

Mijn belangrijkste lesson-learned: het 'ambachtelijke' schrijven van code wordt waarschijnlijk een steeds kleiner deel van ons werk. Misschien komt er een moment waarop developers nauwelijks nog handmatig programmeren. Dat roept de vraag op hoe iemand die vandaag begint nog de ervaring opdoet die ik hierboven beschrijf, want de code schrijven waar je dat van leert doet iemand anders inmiddels voor je.

Daarin schuilt een risico, want als we minder zelf gaan programmeren, verzwakken die vaardigheden en worden we afhankelijker van AI-tools. Dat is natuurlijk niet helemaal nieuw, want toen compilers hun intrede deden, verloren we ook de vaardigheid om zelf assembly te schrijven en werden we afhankelijk van de compiler. Maar er is een belangrijk verschil: een compiler is wél deterministisch. De compiler vertaalt alleen wat jij al volledig had gespecificeerd en levert bij dezelfde code altijd hetzelfde resultaat op. 

Bij AI ligt dat anders. AI is niet zomaar een extra abstractielaag zoals de compiler dat was: het is een abstractielaag die zelf meebeslist met ontwerp- en implementatiekeuzes, en die je daarom actief moet blijven begeleiden en controleren. 

Bij dit hele artikel past wel een kanttekening: het is een momentopname. Toen ChatGPT eind 2022 verscheen, had ik niet kunnen bedenken dat een tool als Claude Code nu al zo ver zou zijn. Als de ontwikkeling in dit tempo doorgaat, heb je misschien over een paar jaar helemaal geen ervaring in software development meer nodig om een grote applicatie te bouwen. Ik weet niet of dat zo is, en ik weet ook niet wat er dan voor in de plaats komt.

Hoe dan ook, één ding verandert niet: uiteindelijk blijf je als developer verantwoordelijk voor je applicatie en de kwaliteit ervan. Op dit moment kun je dat alleen waarmaken als je de controle over je codebase behoudt. Hopelijk helpt dit artikel je daarbij.


Een overzicht van verschillende groepen binnen een toernooi.

ToernooY is te bekijken op https://www.toernooy.nl, met een demo-toernooi op https://www.toernooy.nl/t/demo.






Interessant voor jou