Leer hoe je gegenereerde code onderhoudbaar houdt met de regel van saaie architectuur: duidelijke mapgrenzen, consistente naamgeving en eenvoudige defaults die toekomstig werk verminderen.

Gegenereerde code verandert het dagelijkse werk. Je bouwt niet alleen features, je stuurt een systeem dat snel veel bestanden kan aanmaken. Die snelheid is echt, maar kleine inconsistenties vermenigvuldigen zich snel.
Gegenereerde output ziet er vaak op zichzelf prima uit. De kosten verschijnen pas bij de tweede of derde wijziging: je weet niet waar iets thuishoort, je herstelt hetzelfde gedrag op twee plekken, of je durft een bestand niet aan te raken omdat je niet weet wat het nog meer beïnvloedt.
"Clever" structuren worden duur omdat ze moeilijk te voorspellen zijn. Custom patronen, verborgen magie en zware abstracties maken op dag één soms zin. Na zes weken vertraagt de volgende wijziging omdat je eerst de truc opnieuw moet leren voordat je veilig kunt bijwerken. Met AI‑gestuurde generatie kan die slimheid toekomstige generaties ook verwarren en leiden tot gedupliceerde logica of extra lagen bovenop.
Saaie architectuur is het tegenovergestelde: duidelijke grenzen, eenvoudige namen en voor de hand liggende defaults. Het gaat niet om perfectie. Het gaat om een indeling die een vermoeide collega (of de toekomstige jij) binnen 30 seconden begrijpt.
Een simpel doel: maak de volgende wijziging makkelijk, niet indrukwekkend. Dat betekent meestal één duidelijke plek voor elk soort code (UI, API, data, gedeelde utilities), voorspelbare namen die passen bij wat een bestand doet, en minimale “magie” zoals auto‑wiring, verborgen globals of metaprogrammering.
Voorbeeld: als je Koder.ai vraagt om “team invites” toe te voegen, wil je dat de UI in het UI‑gebied terechtkomt, één API‑route in het API‑gebied komt en invite‑data in de datalaag wordt opgeslagen, zonder dat er een nieuwe map of patroon voor die feature wordt uitgevonden. Die saaie consistentie houdt toekomstige bewerkingen goedkoop.
Gegenereerde code wordt duur als het je veel manieren geeft om hetzelfde te doen. De regel van saaie architectuur is simpel: maak de volgende wijziging voorspelbaar, zelfs als de eerste build minder slim lijkt.
Je moet deze vragen snel kunnen beantwoorden:
Kies één simpele structuur en houd je er overal aan. Wanneer een tool (of collega) een fancy patroon voorstelt, is het standaardantwoord “nee” tenzij het echt pijn wegneemt.
Praktische defaults die lang standhouden:
Stel je voor dat een nieuwe ontwikkelaar je repo opent en een knop “Abonnement annuleren” moet toevoegen. Die persoon zou geen custom architectuur moeten leren. Ze moeten een duidelijk feature‑gebied vinden, een duidelijke UI‑component, één API‑client locatie en één datapad.
Deze regel werkt bijzonder goed met vibe‑coding tools zoals Koder.ai: je kunt snel genereren, maar je stuurt de output telkens in dezelfde saaie grenzen.
Gegenereerde code groeit vaak snel. De veiligste manier om het onderhoudbaar te houden is een saaie mappenkaart waar iedereen kan raden waar een wijziging hoort.
Een kleine top‑level indeling die bij veel webapps past:
app/ schermen, routing en page‑level statecomponents/ herbruikbare UI‑stukkenfeatures/ één map per feature (billing, projects, settings)api/ API‑clientcode en request‑helpersserver/ backend handlers, services en business rulesDit maakt grenzen duidelijk: UI zit in app/ en components/, API‑calls in api/, en backendlogica in server/.
Data‑toegang moet ook saai zijn. Houd SQL‑queries en repository‑code dicht bij de backend, niet verspreid over UI‑bestanden. In een Go + PostgreSQL setup is een eenvoudige regel: HTTP‑handlers roepen services aan, services roepen repositories aan, repositories praten met de database.
Gedeelde types en utilities verdienen een duidelijke plek, maar houd het klein. Zet cross‑cutting types in types/ (DTOs, enums, gedeelde interfaces) en kleine helpers in utils/ (datumformattering, simpele validators). Als utils/ aanvoelt als een tweede app, hoort de code waarschijnlijk in een featuremap.
Behandel gegenereerde mappen als vervangbaar.
generated/ (of gen/) en bewerk die bestanden bij voorkeur niet direct.features/ of server/ zodat regeneratie het niet overschrijft.Voorbeeld: als Koder.ai een API‑client genereert, bewaar die onder generated/api/, en schrijf dunne wrappers in api/ waar je retries, logging of duidelijkere foutmeldingen kunt toevoegen zonder gegenereerde bestanden aan te passen.
Gegenereerde code is makkelijk te maken en makkelijk op te stapelen. Naamgeving is wat het leesbaar houdt na een maand.
Kies één naamgevingsstijl en mix die niet:
kebab-case (user-profile-card.tsx, billing-settings)PascalCase (UserProfileCard)camelCase (getUserProfile)SCREAMING_SNAKE_CASE (MAX_RETRY_COUNT)Noem naar rol, niet naar hoe het vandaag werkt. user-repository.ts is een rol. postgres-user-repository.ts is een implementatiedetail dat kan veranderen. Gebruik implementatiesuffixen alleen als je echt meerdere implementaties hebt.
Vermijd rommelmappen als misc, helpers of een gigantisch utils. Als een functie alleen door één feature wordt gebruikt, houd het dicht bij die feature. Als het gedeeld is, laat de naam de capaciteit beschrijven (date-format.ts, money-format.ts, id-generator.ts) en houd de module klein.
Als routes, handlers en componenten een patroon volgen, vind je dingen zonder te zoeken:
routes/users.ts met paden zoals /users/:userIdhandlers/users.get.ts, handlers/users.update.tsservices/user-profile-service.tsrepositories/user-repository.tscomponents/user/UserProfileCard.tsxAls je Koder.ai (of een willekeurige generator) gebruikt, zet deze regels in de prompt en houd ze consistent tijdens edits. Het draait om voorspelbaarheid: als je de bestandsnaam kunt raden, blijven toekomstige wijzigingen goedkoop.
Gegenereerde code kan op dag één indrukwekkend lijken en op dag dertig pijnlijk. Kies defaults die de code duidelijk maken, ook als het een beetje repetitief is.
Begin met het verminderen van magie. Sla dynamische loading, reflection‑stijl trucs en auto‑wiring over tenzij er een gemeten noodzaak is. Deze eigenschappen verbergen waar dingen vandaan komen, wat debuggen en refactoren vertraagt.
Geef de voorkeur aan expliciete imports en duidelijke dependencies. Als een bestand iets nodig heeft, importeer het direct. Als modules wiring nodig hebben, doe het op één zichtbare plek (bijvoorbeeld een enkele composition file). Een lezer moet niet hoeven raden wat eerst draait.
Houd configuratie saai en gecentraliseerd. Zet environment‑variabelen, feature flags en app‑brede instellingen in één module met één naamgevingsschema. Verspreid geen config over willekeurige bestanden omdat het even handig voelde.
Vuistregels die teams consistent houden:
Foutafhandeling is waar slimheid het meest schaadt. Kies één patroon en gebruik het overal: retourneer gestructureerde fouten vanuit de datalaag, map ze naar HTTP‑responses op één plek, en vertaal ze naar gebruikersvriendelijke berichten aan de UI‑grens. Gooi geen drie verschillende fouttypes afhankelijk van het bestand.
Als je een app genereert met Koder.ai, vraag dan vooraf om deze defaults: expliciete module‑wiring, gecentraliseerde config en één foutpatroon.
Duidelijke lijnen tussen UI, API en data houden wijzigingen beperkt. De meeste mysteriebugs ontstaan wanneer één laag het werk van een andere laag doet.
Behandel de UI (vaak React) als een plek om schermen weer te geven en UI‑enkelstaat te beheren: welke tab open is, formulierfouten, loading spinners en basisinputafhandeling.
Houd serverstate gescheiden: opgehaalde lijsten, gecachte profielen en alles wat met de backend overeen moet komen. Wanneer UI‑componenten totals berekenen, complexe regels valideren of permissies beslissen, verspreidt logica zich en worden wijzigingen duur.
Houd de API‑laag voorspelbaar. Het moet HTTP‑verzoeken vertalen naar aanroepen naar businesscode, en resultaten terug vertalen naar stabiele request/response vormen. Vermijd het direct over de lijn sturen van databasemodellen. Stabiele responses laten je intern refactoren zonder de UI te breken.
Een simpel pad dat goed werkt:
Zet SQL (of ORM‑logica) achter een repository‑grens zodat de rest van de app niet weet hoe data is opgeslagen. In Go + PostgreSQL betekent dat meestal repositories zoals UserRepo of InvoiceRepo met kleine, duidelijke methoden (GetByID, ListByAccount, Save).
Concreet voorbeeld: kortingscodes toevoegen. De UI toont een veld en laat de bijgewerkte prijs zien. De API accepteert code en retourneert {total, discount}. De service bepaalt of de code geldig is en hoe kortingen zich opstapelen. De repository haalt de benodigde rijen op en slaat ze op.
Gegenereerde apps kunnen snel “klaar” lijken, maar structuur is wat wijzigingen goedkoop houdt. Bepaal saaie regels eerst, en genereer vervolgens alleen genoeg code om ze te bewijzen.
Begin met een korte planningsronde. Als je Koder.ai gebruikt, is Planning Mode een goede plek om een mappenkaart en een paar naamgevingsregels op te schrijven voordat je iets genereert.
Volg dan deze volgorde:
ui/, api/, data/, features/) en een handvol naamgevingsregels.CONVENTIONS.md toe en behandel het als een contract. Zodra de codebase groeit, wordt het duur om namen en mappenpatronen te veranderen.Reality check: als een nieuwe persoon niet kan raden waar “edit contact” naartoe moet zonder te vragen, is de architectuur nog niet saai genoeg.
Stel je een simpele CRM voor: een contactenlijstpagina en een contact‑editformulier. Je bouwt de eerste versie snel, en een week later moet je “tags” toevoegen aan contacten.
Behandel de app als drie saaie dozen: UI, API en data. Elke doos krijgt duidelijke grenzen en letterlijke namen zodat de “tags” wijziging klein blijft.
Een schone indeling kan er als volgt uitzien:
web/src/pages/ContactsPage.tsx en web/src/components/ContactForm.tsxserver/internal/http/contacts_handlers.goserver/internal/service/contacts_service.goserver/internal/repo/contacts_repo.goserver/migrations/Nu wordt “tags” voorspelbaar. Werk eerst het schema bij (nieuwe contact_tags tabel of een tags kolom), en raak dan laag voor laag aan: repo leest/schrijft tags, service valideert, handler draait het veld naar buiten, UI renderen en bewerken. Stop geen SQL in handlers en geen businessregels in React‑componenten.
Als product later vraagt om “filteren op tag”, werk je vooral in ContactsPage.tsx (UI‑state en queryparams) en de HTTP‑handler (request parsing), terwijl de repo de query afhandelt.
Voor tests en fixtures, houd het klein en dicht bij de code:
server/internal/service/contacts_service_test.go voor regels zoals “tagnamen moeten uniek zijn per contact”server/internal/repo/testdata/ voor minimale fixturesweb/src/components/__tests__/ContactForm.test.tsx voor formuliergedragAls je dit met Koder.ai genereert, geldt dezelfde regel na export: houd mappen saai, houd namen letterlijk, en bewerken voelt niet als archeologie.
Gegenereerde code kan er op dag één schoon uitzien en later toch kostbaar zijn. De gebruikelijke schuldige is niet “slechte code”, maar inconsistentie.
Een dure gewoonte is de generator elke keer structuur laten verzinnen. Een feature landt met eigen mappen, naamgevingsstijl en helperfuncties, en je eindigt met drie manieren om hetzelfde te doen. Kies één patroon, schrijf het op en behandel elk nieuw patroon als een bewuste verandering, niet als standaard.
Een andere valkuil is lagen mengen. Wanneer een UI‑component met de database praat, of een API‑handler SQL bouwt, worden kleine wijzigingen riskante edits door de hele app. Houd de grens: UI roept een API aan, de API roept een service aan, de service roept data‑toegang aan.
Te vroege overmatige abstracties voegen ook kosten toe. Een universele “BaseService” of “Repository” framework voelt slim, maar vroege abstracties zijn gissingen. Als de realiteit verandert, vecht je tegen je eigen framework in plaats van te leveren.
Constant hernoemen en reorganiseren is een stillere vorm van schuld. Als bestanden elke week verplaatsen, stoppen mensen met vertrouwen op de indeling en belanden snelle fixes op willekeurige plekken. Stabiliseer eerst de mappenkaart, refactor daarna in geplande stappen.
Wees tenslotte voorzichtig met “platformcode” zonder echte gebruikerswaarde. Gedeelde libraries en huisgemaakte tooling leveren pas op als je herhaalde, bewezen behoeften hebt. Tot die tijd: houd defaults direct.
Als iemand nieuw de repo opent, moet die snel één vraag kunnen beantwoorden: “Waar voeg ik dit toe?”
Geef het project aan een collega (of de toekomstige jij) en laat diegene een kleine feature toevoegen, zoals “voeg een veld toe aan het signup‑formulier.” Als ze de juiste plek niet snel kunnen vinden, werkt de structuur niet.
Controleer op drie duidelijke thuisplekken:
Als je platform het ondersteunt, houd een rollback‑pad. Snapshots en rollback zijn vooral nuttig wanneer je met structuur experimenteert en een veilige terugweg wilt.
Onderhoudbaarheid verbetert het snelst als je stopt met stijl discussiëren en een paar beslissingen neemt die blijven staan.
Schrijf een korte set conventies die dagelijkse twijfel wegnemen: waar bestanden heen gaan, hoe ze genoemd worden, en hoe fouten en config worden behandeld. Houd het kort genoeg om in één minuut te lezen.
Doe daarna één opruimronde om die regels door te voeren en stop met wekelijks herschikken. Frequent reorganiseren maakt de volgende wijziging langzamer, ook al ziet de code er mooier uit.
Als je met Koder.ai bouwt (koder.ai), helpt het om deze conventies als startprompt op te slaan zodat elke nieuwe generatie in dezelfde structuur terechtkomt. De tool kan snel bewegen, maar de saaie grenzen zijn wat de code makkelijk veranderbaar houdt.