Writing Quality Code, Part 2

Contenttype
Blog

Het schrijven van goede documentatie is belangrijker dan het schrijven van goede code.

Kennis Blog Writing Quality Code Part 2
Auteur
Jelle Fremery

Have you read 'Writing Quality Code, part 1' yet?: Writing Quality Code, Part 1

Na een paar maanden begint Alex de boekingsapplicatie van Etrain goed onder de knie te krijgen. Nieuwe functies kunnen snel worden toegevoegd, bugs worden met zekerheid opgelost en het team heeft een algemeen gevoel van vertrouwen bij het doorvoeren van wijzigingen. “We weten wat we doen,” is de algemene consensus.

Maar op een dag komt er een verzoek binnen om iets aan te passen in een van de oudere delen van de code. Dat gevoel van zekerheid verdwijnt meteen. De codekwaliteit is nog lager dan in de nieuwere delen, maar dat is niet eens het ergste. Alex weet gewoon niet hoe het verzoek moet worden geïmplementeerd. “Ik heb geen idee waarom de code is zoals hij is,” mompelt Alex. De gemaakte keuzes lijken onorthodox en bijna contraproductief. Maar wel bewust zo. Geen enkele ontwikkelaar zou deze functionaliteit op deze manier bouwen, tenzij daar een specifieke reden voor was. Alleen kan Alex die reden niet achterhalen door simpelweg naar de code te kijken.

In de geschiedenis blijkt dat de wijziging onderdeel was van een grote commit met de naam “Several bug fixes (incl. refactoring)”. Dat helpt niet echt. Bovendien is dit een deel van de code waar (nog steeds) geen tests of documentatie voor bestaan. Alex moet de code licht aanpassen voor de nieuwe feature, maar een volledige herschrijving lijkt onvermijdelijk. Als Alex de code herschrijft en vereenvoudigt, waarbij alle vreemde keuzes worden verwijderd, zou er dan iets kapot gaan? Misschien is het veiliger om om al die vreemde constructies heen te werken, om te voorkomen dat er onbedoeld iets verandert. Maar Alex wil eigenlijk kunnen documenteren waarom die constructies er zijn.

Een van Alex’ collega’s weet het ook niet. “Als de codekwaliteit hoger was, hadden we geen documentatie nodig. Goede code documenteert zichzelf, toch?” Alex trekt een wenkbrauw op. “Dat is… iets wat mensen zeggen. Maar wat hebben we daar nu aan? We hebben noch goede code, noch documentatie. Jullie zijn de enige ‘documentatie’ die we hebben, en zelfs jullie zijn de meeste beslissingen uit het verleden al vergeten. Eerlijk is eerlijk, hoewel ik denk dat we goed bezig zijn sinds ik bij het team zit, voelt het ook alsof we telkens het wiel opnieuw uitvinden.”
Die avond denkt Alex na over die uitspraak: ‘Goede code documenteert zichzelf.’ Het is eigenlijk een kip-en-ei-situatie. Zou het verbeteren van de code leiden tot minder behoefte aan documentatie, of zou juist meer documentatie helpen om betere code te schrijven? Alex raakt het gevoel niet kwijt dat het team een cruciale stap mist in hun ontwikkelproces.

Goede code lezen versus goede code schrijven

Laten we Alex even laten nadenken over die vraag. Eerst een mooie vraag voor een Sprint Retrospective: wat is voor een software engineer de belangrijkste vaardigheid? De vaardigheid om goede code te schrijven, of de vaardigheid om slechte code te lezen? Denk daar eens over na.

Klaar? Mooi. Ik zou stellen dat het vermogen om slechte code te lezen nét iets waardevoller is dan het vermogen om goede code te schrijven. Simpelweg omdat kwaliteitscode vanzelf zal verouderen en langzaam in slechte code verandert.

Dat betekent niet dat het kunnen schrijven van goede code onbelangrijk is. Schone en efficiënte code schrijven is een essentiële vaardigheid voor elke ontwikkelaar: het vermindert de kans op bugs, verbetert het onderhoudsgemak en verhoogt de algehele codekwaliteit. Bovendien is het vaak een vereiste om überhaupt code goed te kunnen lezen.

Maar het kunnen lezen van slechte code is belangrijker, omdat het grootste deel van softwareontwikkeling bestaat uit het begrijpen en onderhouden van bestaande code, niet uit het schrijven van volledig nieuwe code. Wie goed slechte code kan lezen, begrijpt sneller de bedoeling van een stuk code, hoe het werkt en hoe het past binnen de grotere architectuur van het systeem.

Daarnaast helpt het leren begrijpen van code (ongeacht de kwaliteit) ontwikkelaars ook om hun eigen codeervaardigheden te verbeteren. Door goed geschreven code te bestuderen, leren ze best practices, ontwerpprincipes en technieken voor het schrijven van schone, efficiënte code.

Gooi het Agile Manifesto niet naar mijn hoofd

Dus als ik pleit voor het leren lezen van slechte code, betekent dat dan dat we geen documentatie nodig hebben? Zou Alex gewoon beter moeten worden in het lezen van waardeloze code en zal de bedoeling vanzelf duidelijk worden? Hier zou ik een </sarcasm> grap kunnen maken, maar dat is hopelijk overbodig. Slechte code lezen is belangrijk, maar dat beantwoordt Alex’ vraag niet volledig. Iets anders geformuleerd: moeten we kennisgaten opvullen door betere code te schrijven of door documentatie toe te voegen? En daarmee komen we bij de gewaagde stelling van deze blog: documentatie is de sleutel tot kwaliteit.

“Goede code documenteert zichzelf” klinkt prachtig, maar het is complete drogredenering. Goede code betekent dat je veel tijd hebt gestoken in het maken van doordachte beslissingen op basis van bekende en onbekende factoren. Waarom zou je dan denken dat die beslissingen, en het proces dat daartoe leidde, mogen verdwijnen in de tijd? Goede code moet, als iets, juist beter gedocumenteerd worden dan slechte code. Slechte code kan volstaan met één regel: “Niet zeker of dit de juiste aanpak is”, “Deze code is rommel, sorry” of simpelweg “TODO”. Goede code heeft goede documentatie nodig.

Veel ontwikkelaars hechten te veel waarde aan codekwaliteit, in de veronderstelling dat schone, compacte en onderhoudbare code voldoende is voor een succesvol project. In werkelijkheid is goede documentatie cruciaal - waarschijnlijk het belangrijkste onderdeel - voor het langetermijnsucces van een softwareapplicatie.

Je zou mijn punt kunnen weerleggen met het Agile Manifesto: “Working software over comprehensive documentation.” En daar ben ik het ook mee eens. Wat ik hier eigenlijk zeg is: werkende software boven uitgebreide documentatie boven codekwaliteit.

Permanente perfectie bestaat niet

Stel voor de discussie dat je niet alleen goede code hebt geschreven, maar perfecte code. Maar dan verstrijkt de tijd. De context van een softwareapplicatie verandert voortdurend: nieuwe functies worden toegevoegd, oude verwijderd. Daardoor kan zelfs de best geschreven code moeilijk te begrijpen of te onderhouden worden zonder goede documentatie. Dat wordt nog duidelijker wanneer nieuwe ontwikkelaars bij een project komen of wanneer bestaande ontwikkelaars terugkeren naar code die ze al lang niet hebben aangeraakt. Goede en actuele documentatie kan dan het verschil maken tussen een soepel ontwikkelproces en een proces vol verwarring en inefficiëntie.

Efficiënte documentatie zorgt er ook voor dat software op de lange termijn onderhoudbaar blijft. Dat betekent het documenteren van ontwerpbeslissingen, vereisten en technische specificaties, evenals het bieden van duidelijke instructies voor ontwikkelaars. Met de juiste documentatie kunnen ontwikkelaars snel begrijpen hoe de software hoort te werken en hoe verschillende onderdelen met elkaar samenhangen. Dat maakt het makkelijker om weloverwogen beslissingen te nemen bij het wijzigen of uitbreiden van de software.

Duidelijkheid

De reden dat ik documentatie zo belangrijk vind, is dat het essentieel is voor duidelijkheid. En met duidelijkheid bedoel ik doel, intentie en context. Ik geloof sterk dat die drie zaken cruciaal zijn voor efficiënte ontwikkeling. Goede code schrijven is belangrijk, maar alleen vertrouwen op code om je bedoelingen over te brengen, is als praten in het donker. Je hoort elkaar wel, maar mist alle non-verbale informatie. Code kan nooit de zakelijke behoeften documenteren die eraan voorafgingen, noch de argumenten of obstakels die overwogen zijn.

Hoe creëren we dan duidelijkheid in een softwareproject? Het begint bij het bespreken van die duidelijkheid tijdens het refinementproces. Iedereen die bij het project betrokken is, moet begrijpen wat we willen bereiken en hoe we dat gaan doen. Dat betekent ook het bespreken van bedrijfsvereisten, architectuurkeuzes en codebeslissingen. Door deze beslissingen te documenteren, zorgen we dat iedereen op één lijn zit en erop kan terugvallen wanneer dat nodig is.

Ik heb het hier over meerlagige documentatie: van bedrijfscontext tot high-level architectuurkeuzes, en van daaruit naar de technische details over hoe componenten met elkaar communiceren. Zo ontstaat een gedeeld begrip van hoe het systeem werkt, waardoor ontwikkelaars op lange termijn makkelijker aan het project kunnen werken.

Een andere manier om duidelijkheid te creëren: zorg dat je code gemakkelijk te debuggen is. Code die moeilijk te debuggen is, is als praten met iemand in een vreemde taal: je begrijpt misschien de strekking, maar nooit echt de diepte van wat bedoeld wordt. Door code te schrijven die goed te debuggen is, kunnen ontwikkelaars problemen sneller vinden en oplossen, wat leidt tot een efficiënter ontwikkelproces.

Tot slot, code die getest wordt, is al deels gedocumenteerd. Tests vormen niet alleen een veiligheidsnet, maar leggen ook vast hoe de code hoort te werken. Daardoor begrijpen ontwikkelaars sneller hoe onderdelen samenwerken en wat de verwachte resultaten zijn. Testbare code schrijven en die uitgebreid testen zorgt dus voor een meer zelf-documenterende codebasis. Niet volledig, maar wel een uitstekende start.

Hoe schrijf je documentatie

Goede code schrijven is moeilijk. Goede documentatie schrijven is - eerlijk gezegd - minstens zo moeilijk. Documentatie moet vindbaar, leesbaar en eenvoudig bij te werken zijn. Ze moet zo weinig mogelijk overbodige informatie bevatten en het schrijven ervan moet zo weinig mogelijk moeite kosten.

De locatie van je documentatie is cruciaal. Ze moet worden opgeslagen op een plek die voor iedereen in het project gemakkelijk toegankelijk en doorzoekbaar is. Goede plekken voor documentatie die niet in je repository thuishoren zijn bijvoorbeeld een gedeelde schijf, een wiki of tools zoals Confluence of Google Docs.

Daarnaast moet documentatie zich vooral richten op de waaroms, niet de wat’s. De ‘wat’ kun je meestal wel uit de code halen, maar de ‘waarom’ gaat voor altijd verloren zonder documentatie. De redenen voor keuzes kunnen in de loop der tijd veranderen, en het is waardevol te weten waarom iemand destijds iets heeft besloten, zodat je kunt bepalen of dat besluit nog steeds geldig is.

Een belangrijke vorm van documentatie zijn git commit-berichten. Goede commit-berichten zijn beschrijvend, beknopt en bieden context bij de aangebrachte wijzigingen. Een goed voorbeeld: “Refactor user authentication logic to improve security.” Een slecht voorbeeld: “Updated login stuff.” Goede commit-berichten helpen ontwikkelaars te begrijpen wat er is veranderd en waarom, wat het onderhoud en debuggen vergemakkelijkt.

Een andere belangrijke vorm van documentatie zijn stories en features, die kunnen worden vastgelegd in tools zoals Azure DevOps Boards of GitHub Projects. Een goede user story bevat informatie over de zakelijke behoefte, relevante discussies en beslissingen uit de planningsfase, en een duidelijke beschrijving van de feature zelf. Een slechte story bestaat alleen uit een lijst stappen zonder context of achtergrond.

En zelfs als je al het bovenstaande perfect doet, is het nog steeds schrikbarend eenvoudig om verouderde documentatie te krijgen. Zoals eerder gezegd: de context waarin je applicatie waarde toevoegt verandert voortdurend, en dus moet je documentatie mee veranderen. Voeg daarom het schrijven en bijwerken van documentatie toe aan je Definition of Done.

En zorg er ook voor dat je een Definition of Done hébt.

Hoe schrijf je tests

Eerder noemde ik al wat ik zie als een van de belangrijkste en meest efficiënte vormen van documentatie: unittests. Als het gaat om documentatie op codeniveau, is er niets dat in de buurt komt. Goed benoemde en gestructureerde tests tonen de bedoeling van de code en maken die begrijpelijker en onderhoudbaarder. Een test genaamd TestCalculateTotal zegt niet veel: we weten niet welke invoer wordt gebruikt of wat de verwachte uitvoer is. De test GivenMultipleItems_WhenCalculatingTotal_ThenReturnCorrectValue is veel duidelijker. We weten precies wat er wordt getest, onder welke omstandigheden en wat het verwachte resultaat is. Deze naam volgt bovendien de Given-When-Then-structuur, waardoor de test makkelijk te lezen en te begrijpen is.

Test-Driven Development

Logischerwijs zou je denken dat ik een groot voorstander ben van Test-Driven Development (TDD). En hoewel sommige engineers erbij zweren, denk ik dat TDD niet altijd de beste aanpak is voor elk project. Natuurlijk zie ik de voordelen, maar in de praktijk schrijf ik mijn tests liever ná de code, niet om de reden die je misschien denkt.

TDD is handig om te garanderen dat code doet wat hij moet doen, maar het garandeert niet dat de code ook echt aansluit op de behoeften van het bedrijf of de eindgebruiker. De focus van TDD ligt op het schrijven van tests en vervolgens code die die tests laat slagen, maar dat leidt niet per se tot een diep begrip van het probleem of de randgevallen die overwogen moeten worden.

In plaats van alleen op TDD te vertrouwen, zouden software engineers en businessvertegenwoordigers samen moeten nadenken over het wat en waarom van een project, nog vóór er één regel code geschreven wordt. Door vooraf na te denken over mogelijke randgevallen en toekomstige veranderingen, kunnen teams software bouwen die beter aansluit op de behoeften van gebruikers en organisatie. Zo blijven de voordelen van TDD (nadenken over vereisten en vroegtijdig problemen ontdekken) behouden, terwijl de nadelen (meer ontwikkeltijd en veel kleine, weinig waardevolle tests) grotendeels verdwijnen.

Daarnaast helpt het betrekken van zakelijke stakeholders in een vroeg stadium ervoor te zorgen dat de code aansluit bij de bredere doelen van de organisatie. Zo voorkom je dat er software wordt gebouwd die technisch werkt, maar geen echte waarde toevoegt.

Om deze sectie af te sluiten herhaal ik graag iets wat ik in mijn vorige blog ook al noemde: niemand zou moeten streven naar volledige code-coverage met tests, omdat dat onnodig rigide is. Richt je op het testen van echte use-cases en reële risico’s, zodat de code in de praktijk doet wat hij moet doen. Weten wanneer iets een theoretisch risico is en wanneer een echt probleem, vraagt om ervaring en inzicht. In de volgende delen van deze blogreeks geef ik meer richtlijnen over hoe je dat onderscheid kunt maken, niet alleen voor codekwaliteit, maar ook voor softwarekwaliteit en zelfs ethische overwegingen.

Het kip-en-ei-probleem oplossen

Alex weet dat zelfs kwalitatief goede code binnen een paar jaar verouderd, onbegrijpelijk of nutteloos kan worden. Zonder documentatie is het onmogelijk te achterhalen wat daarvan de oorzaak is. In plaats van de hele codebase te herschrijven, vraagt Alex het team om te zoeken naar documentatie in oude e-mails, featurebeschrijvingen, documenten en gesprekken met collega’s. De gevonden documentatie wordt besproken, herschreven, bijgewerkt en opgeslagen in een documentatietool. Als er geen documentatie bestaat, wordt nieuwe gemaakt. Als er onzekerheid blijft, wordt de huidige code actief onderzocht via debugging, handmatig testen, feature toggles tussen oude en nieuwe implementaties en zelfs A/B-tests.

Alex neemt de tijd om git commit-geschiedenis te bekijken vóórdat er wijzigingen worden aangebracht. Als er geen tests zijn, worden die eerst toegevoegd. Als ze wel bestaan, zorgt Alex ervoor dat hun doel en functie duidelijk zijn. In eerste instantie ligt de focus niet op de codekwaliteit, maar op de duidelijkheid van de tests.

Alex begrijpt dat de redenen achter het oorspronkelijke ontwerp van de code inmiddels kunnen zijn veranderd. Daarom zorgt Alex ervoor dat alle nieuwe documentatie wordt geschreven met oog op het heden, met begrip van hoe de code zich sindsdien heeft ontwikkeld. Deze aanpak stelt het team in staat om efficiënter en effectiever te werken, met aandacht voor zowel de korte- als langetermijnbehoeften van het project.

Na nog een paar maanden van vooruitgang krijgt Alex een telefoontje. Een van de software engineers gaat met ouderschapsverlof voor minstens twee maanden. Dat betekent dat hun team iemand zoekt om tijdelijk bij te springen. En aangezien Alex het zo goed doet met de boekingsapplicatie…

Een overzicht van deze hele blog serie door Jelle.

Meer blogposts

  • Exploring the essentials of professional software engineering

    Jelle verkende in deze serie wat een software engineer professioneel maakt en deelt inzichten uit eigen ervaring. Hieronder staat een korte terugblik op de besproken onderwerpen.
    Contenttype
    Blog
    Kennis Blog Exploring The Essentials Of Professional Software Engineering
  • The Software Engineer Oath

    In dit laatste deel blikken we terug op de hele reeks, van codekwaliteit tot ethiek, teamwork, professionaliteit en de introductie van Dijkstra’s Eed voor verantwoord software-engineeringschap.
    Contenttype
    Blog
    Kennis Blog The Software Engineer Oath
  • The development process Part 2

    Deze blog laat zien hoe succesvolle softwareontwikkeling draait om mensen: samenwerking, teamdynamiek, psychologische veiligheid en ontwikkelaars die actief bijdragen aan productvisie, groei en verandering.
    Contenttype
    Blog
    Kennis Blog The Development Process 2

Altijd op de hoogte met onze tech-updates!

Schrijf je in en ontvang om de week een update met de nieuwste kennis en ontwikkelingen.