Breed bouwen, smal verzenden
Goede engineers plannen voordat ze gaan bouwen. De workflow waarmee ik ben opgegroeid was als volgt: schrijf een RFC (Request for Comments) waarin de feature wordt beschreven, splits deze op in kleinere issues en bouw deze vervolgens, waarbij elk issue vaak het volgende blokkeert. De structuur van het werk lag vast voordat er één regel code was geschreven.
Dit is redelijk. Het houdt code-reviews beheersbaar en voorkomt enorme, complexe merges. Tegelijkertijd dwingt het je echter om je meest kritische structurele beslissingen te nemen op het moment dat je het minst weet over het probleem.
Voordat je iets hebt gebouwd, ben je aan het gissen: welke onderdelen zijn scheidbaar, hoe complex is elk onderdeel en zal stap 3 je dwingen om stap 1 te heroverwegen? Soms heb je gelijk. Vaak niet, waardoor stap 1 wordt weggegooid. Je hebt iets geleerd tijdens het bouwen, maar je had dit sneller geleerd door eerst het geheel te bouwen.
We accepteerden deze kosten om een goede reden: het alternatief was alles in één keer bouwen en dit daarna handmatig ontwarren, en een week aan werk ontwarren is moeilijker dan vooraf plannen. Het vooraf bepalen van grenzen was nooit bedoeld om het bouwen makkelijker te maken; het was de enige betaalbare manier om reviews mogelijk te maken. Dat is precies het onderdeel dat is veranderd.
Wat er is veranderd
Drie zaken zijn drastisch goedkoper geworden:
- Bouwen: een AI-assistent zet een duidelijk probleem in uren, soms minuten, om in werkende code.
- Design: je kunt een plan bevragen en hervormen op conversationsnelheid.
- Het ontleden van een voltooide branch: het splitsen van een week aan verstrengeld werk in een reeks kleine PR's (Pull Requests) was vroeger het meest tedious deel van de baan, precies waarom we het vermeden. Nu is dit een kwestie van één prompt.
Twee zaken zijn niet goedkoper geworden: Ten eerste het oordeelsvermogen bij code-reviews. Agents hebben het mechanische deel (consistentie, kleine details, overduidelijke bugs) bijna gratis gemaakt, maar ze lossen subtiele correctheid of strategische vragen niet op: hoort deze wijziging hier wel, of zal de vorm van dit endpoint over zes maanden voor problemen zorgen? Een bot die je PR goedkeurt is niet hetzelfde als het begrijpen van de code. Als je de code niet zelf hebt getypt, is lezen de enige manier om het echt te doorgronden. Smalle PR's maken dat lezen mogelijk.
Ten tweede de productvalidatie. Het draaien van de software en beslissen of het het juiste is om te bouwen, is nog steeds een traag proces. Wat nieuw is, is dat je de volledige feature vroeg genoeg werkend hebt om deze aan iemand te laten zien voordat er ook maar één regel code wordt gelezen.
Stop daarom met het vooraf bepalen van grenzen om een kost aan te spreken die niet langer bestaat.
Mijn huidige workflow ziet er als volgt uit:
- Bevraag het plan grondig totdat er echte beslissingen in staan.
- Commit de specificatie voordat er code wordt geschreven, wanneer het design vernieuwend is.
- Bouw breed en commit 'save points' terwijl je bezig bent.
- Demo en itereer voordat iemand de code leest.
- Splits de code in PR's langs de grenzen die de code zelf heeft onthuld.
- Merge en voer als laatste een opschoning uit: pure deletie in een eigen, finale PR.
Design gaat nog steeds eerst
Om duidelijk te zijn: dit is niet "sla de planning over en begin met coderen". Voordat ik de editor aanraak, heb ik een plan, en elke feature begint met een ondervraging. Ik gebruik grill-me, een skill die je idee in adversarial rondes interviewt totdat er echte beslissingen in staan. Wat is de fallback als de API-call faalt? Ik pas dit op alles toe, inclusief kleine wijzigingen, en het blijft hiaten aan het licht brengen waarvan ik niet wist dat ze er waren.
Wanneer het design vernieuwend is, wordt het plan een specificatie die wordt gecommit voordat er code is. In een ander project was de eerste PR een document: wat de feature was, hoe deze zou werken en waar de trust boundaries lagen. Dit werd dagen voordat er implementaties waren gemerged, zodat het team eerst feedback kon geven.
Wat nooit vooraf wordt vastgelegd, is de opsplitsing in PR's. Beslis wát je gaat bouwen voordat je begint; beslis hóé je het in stukjes snijdt nadat het af is.
Breed bouwen
Zodra het design vaststaat, begin ik met bouwen. Ik werk vaak in stappen, maar ik stop niet bij elke stap om een PR te openen en op review te wachten. Alles blijft op één branch totdat het end-to-end werkt, ongeacht welke bestanden hiervoor aangepast moeten worden.
Er worden commits gemaakt, maar deze zijn geen mijlpalen voor anderen. Het zijn save points: een concept is bewezen, of ik ga iets riskants proberen en wil een touw hebben om terug te trekken. Een refactor die ik onlangs heb gedaan, bestond uit meer dan een dozijn commits in één dag over tientallen bestanden, met operationele berichten. Wegpunten zodat ik kan zien waar ik ben geweest, niet een verhaal voor een reviewer.
Dit maakt sommige engineers ongemakkelijk omdat de git-geschiedenis het verslag zou moeten zijn. Maar deze geschiedenis wordt nooit het definitieve verslag. De PR's aan het einde worden vers afgesplitst van main, en de build-branch is kladpapier dat je weggooit. Door mezelf en de reviewers in tijd te scheiden, kun je voor beide optimaliseren.
Demo voordat iemand de code leest
Zodra het werk in een goede staat is, stop ik en laat ik het zien. Niet als een PR of code-review, maar als een korte video in Slack, of een preview-deployment als de wijziging interactie vereist. Feedback op werkende software van de mensen die het gaan gebruiken, voordat er één code-review begint.
Reviews zijn nu duur omdat het goedkope deel is geautomatiseerd. Tijdens een review ontdekken dat je het verkeerde hebt gebouwd (verwarrend UI-patroon, een endpoint-vorm die niet past bij hoe de frontend de data gebruikt) verspilt de tijd van de reviewer én die van jou. Een demo vangt dit op terwijl wijzigingen nog goedkoop zijn. Als daaruit blijkt dat er iets heroverwogen moet worden, pas ik eerst grill-me toe op de feedback, zodat de iteratie niet alleen gebaseerd is op een gevoel ("vibes").
Smal verzenden
De volgorde van bouwen is slechts een ruwe schets voor de splitsing: de stappen worden gegroepeerd en opnieuw gesneden. De deletie-PR is het duidelijkste voorbeeld; dit is een unit die pas aan het einde bestaat, zodra het nieuwe pad operationeel is.
Ik gebruik hiervoor één prompt:
"Split the current work into the smallest set of independently reviewable PRs, each safe to merge on its own, and each delivering value to the user whenever the work allows. Create a git worktree and branch off main for each. Stack only where a dependency is real; otherwise branch from main. Any removal of the code being replaced goes in its own final PR. Show me the proposed split before creating anything."
De tijd die dit kost hangt af van hoe verstrengeld het werk is, van enkele minuten tot een paar rondes heen en weer. In ieder geval is er geen handmatig cherry-picking nodig.
Die refactor resulteerde in vijf PR's: twee backend endpoints als siblings vanaf main, twee frontend views die elk rusten op de backend-PR waarvan ze data nodig hebben, en een finale PR die puur bestond uit het verwijderen van het oude pad. De deletie verwijderde honderden regels meer dan de hele feature had toegevoegd. Dat is de vorm van een refactor die in deze volgorde wordt uitgevoerd.
Ik ben tot twee regels gekomen door dit herhaaldelijk te doen:
- Stack alleen wanneer de afhankelijkheid echt is. Een frontend view PR is afhankelijk van zijn backend endpoint PR, en de branch reflecteert dat. Alles andere komt van
main. Stacken uit gemakzucht creëert een rebase-keten waar je spijt van krijgt zodra de onderste PR feedback krijgt. - Opschoning wordt als laatste verzonden. De oude code sterft in zijn eigen PR, nadat het nieuwe pad live is. Het mengen van deletie met creatie verwart reviewers, maakt rollbacks ambigu en begraaft de opschoning in de ruis van de feature.
De splitsing is ook het moment waarop ik mijn eigen werk lees. Door de diff PR per PR door te lopen, op een grootte die ik in mijn hoofd kan houden, is het verschil tussen het verzenden van AI-geschreven code en het daadwerkelijk begrijpen ervan. Ik vind mijn eigen problemen liever op dat moment.
Praktische opmerking: het beheren van een stack kost veel context, dus besteed het splitsingswerk uit aan subagents die rapporteren aan een hoofdagent.
Wat het oplevert en wat het kost
De zware PR's blijven zwaar, en dat moeten ze ook zijn. De backend-onderdelen bevatten het echte architecturale risico (nieuwe datamodellen, nieuw API-oppervlak, trust boundaries), en dat is waar reviewers hun aandacht aan moeten besteden. De frontend-PR's die deze consumeren, kunnen in minuten worden gelezen. Kleine, gefocuste PR's stromen door; grote blijven liggen.
De agent-review-loop is op deze schaal ook sneller. Bij Adapt gebruiken we Adapt zelf als reviewer; een agent met businesscontext uit previous werk. Deze reageert binnen minuten na het openen van een PR, en de uitwisseling (vragen, verduidelijkingen, een kleine fix) is in minder dan tien minuten opgelost. Dat werkt alleen wanneer een PR klein genoeg is om snel te lezen; een PR van 2.000 regels die verschillende zaken mengt, krijgt deze behandeling niet eens van een mens.
Incrementeel mergen maakt ook deployments makkelijker. Als er iets breekt, revert je één gefocuste wijziging en wijst je error-tracking naar die specifieke wijziging in plaats van naar een hele gemergde stack.
Er zijn echter twee kosten verbonden aan deze methode:
- Rebasing. Wanneer een reviewer vraagt om een wijziging in een PR waar een andere op rust, moet elke branch daarboven worden gerebased. Dit gebeurt niet vaak, omdat reviewers meestal de 'leaf' PR's aanpassen, maar als het gebeurt, voel je dat.
- Splitsen is niet hetzelfde als verzenden. Een goede splitsing maakt elke PR gemakkelijk te reviewen en waardevol om onafhankelijk te mergen. Maar zelfs als alle vijf de PR's op dezelfde dag mergen, behoud ik het review-voordeel en een schoon revert-doel per wijziging; wat ik verlies is incrementele levering aan de gebruiker, aangezien de deploys als één batch landen.
Wanneer deze methode te gebruiken
Geschikt voor: multi-surface features die backend en frontend kruisen, refactors waarbij je de uiteindelijke vorm pas kent als je het gedaan hebt, en elk werk waarbij je anders zou moeten gissen naar issue-grenzen.
Minder geschikt voor: migraties en schemawijzigingen die in productie strikt opeenvolgend moeten gebeuren, waarbij de volgorde reëel is en vooraf gepland moet worden. Werk met één overduidelijk snijpunt. Features waarbij stap 1 nooit alleen kan live gaan; als alles tegelijk landt, levert late decompositie enkel een beter reviewproces op en niets meer.
De heuristiek: als je een RFC schrijft en gissend probeert te bepalen hoe je het in issues moet opbreken voordat je iets hebt gebouwd, is die tijd waarschijnlijk beter besteed aan bouwen. Je zult aan het einde betere antwoorden hebben. De structurele beslissing verdwijnt niet; ze wordt alleen veel goedkoper wanneer je haar neemt met de code al voor je neus.
Groetjes,