Building Quality Software Part 1
Goed genoeg is beter dan perfect
Have you read the previous blog yet?: Writing Quality Code, Part 2
“Ik hoor wat jullie zeggen,” zegt de projectmanager tegen het team. “Maar laten we eens horen wat Alex ervan vindt.”
Het is een paar dagen geleden dat Alex zich bij het Public API-team heeft aangesloten. Wisselen tussen het werk van twee verschillende teams is lastig, maar het is duidelijk dat beide teams hulp nodig hebben. Het Public API-team doet het niet goed. Naast een tekort aan teamleden zijn de ontwikkelaars en de projectmanager voortdurend met elkaar in onenigheid. De ontwikkelaars vinden dat het team te veel moet focussen op nieuwe functies en gebruikersverzoeken, terwijl de projectmanager vindt dat het team te traag werkt.
Alex denkt even na voordat hij reageert op de vraag van de projectmanager.
“Als ik de argumenten goed heb begrepen, is er een goed gevulde backlog met potentiële nieuwe gebruikersfunctionaliteiten en klantverzoeken. Maar jij, als projectmanager, vindt dat het team niet snel genoeg werkt en dat we geld verliezen door de lange doorlooptijd. En jullie, als ontwikkelaars, zeggen dat een hogere snelheid de kwaliteit van de software die we bouwen zal verminderen. De gezondheid van de applicatie zal hieronder lijden als we gehaast nieuwe functies bouwen.”
“Klopt,” zegt een van de ontwikkelaars, “maar ik wil benadrukken dat we de gezondheid van de applicatie al behoorlijk hebben aangetast door snelle en slordige klussen. Het is verre van perfect. We moeten flink refactoren voordat we überhaupt zouden moeten nadenken over het toevoegen van nieuwe functies.”
De projectmanager zucht. “Dat zeg je altijd. Maar het management wil dat onze onderwijspartner zich aansluit op onze e-cursussen. Dat zou een enorme bron van inkomsten zijn. Zo moeilijk kan dat toch niet zijn?”
Alex kucht beleefd. “Wat ik hier hoor, is dat mensen in twee verschillende talen proberen te communiceren. Er is geen manier om overeenstemming te vinden als we niet allemaal zeker weten waar we het eigenlijk over hebben.”
“Ik denk dat we elkaar prima begrijpen,” zegt de projectmanager.
“Ja, we zijn het gewoon niet eens,” voegt een van de ontwikkelaars toe.
Alex haalt zijn schouders op. “Doe me een plezier… Hm, hebben we hier ergens een flip-over?”
Waarom ik waardevolle software belangrijker vind dan waardevolle code
In de afgelopen twee delen van deze serie heb ik betoogd dat codeerstijlen, codestandaarden en zelfs codekwaliteit niet zo belangrijk zijn als sommigen beweren. Misschien heb ik ook de indruk gewekt dat ik alleen waarde hecht aan processen, documentatie en geautomatiseerde tests. Ik kreeg zelfs een paar reacties van mensen die zeiden dat ik meer klink als een businessanalist of testengineer dan als een software engineer. En eerlijk gezegd is dat geen onterechte karakterisering, noch was het bedoeld als belediging.
Maar hier is het punt: als software engineer geef ik om kwaliteit in software. Ik geef alleen om kwaliteit in code voor zover die bijdraagt aan het creëren van kwalitatieve software. En uit mijn ervaring blijkt dat codekwaliteit geen harde voorwaarde is voor softwarekwaliteit. Soms helpt het, soms maakt het geen enkel verschil, en soms kan een blind streven naar codekwaliteit zelfs schadelijk zijn voor de algehele kwaliteit van de software.
In dit deel zal ik proberen uit te leggen waarom software engineers zich druk moeten maken over veel belangrijkere zaken dan regels als “één punt per regel” of eindeloze discussies over “naked ifs”. Bij codekwaliteit gaat het gesprek alleen tussen degenen die de code ontwikkelen of testen. Bij softwarekwaliteit is het doel een gesprek te voeren waarin iedereen die betrokken is bij het softwareontwikkelingsproces elkaar begrijpt. En om dat te bereiken, hebben we twee belangrijke dingen nodig: een gedeelde taal en gedeelde doelen. En die doelen moeten meetbaar zijn, en ook daadwerkelijk worden gemeten. We hebben softwarekwaliteitsmetingen nodig.
Correctheid in software-engineering
De eerste maatstaf voor softwarekwaliteit is correctheid. Als ik even mag generaliseren: software engineers zijn vaak perfectionisten. We houden ervan wanneer dingen precies goed zijn. Als men ons de kans geeft, besteden we uren, dagen of zelfs weken aan het bijschaven van onze code totdat die zo dicht mogelijk bij perfectie komt. Dat geldt zeker voor mijn persoonlijke softwareprojecten, waarin het plezier juist ligt in het perfectioneren.
Die drang tot verfijning helpt software engineers om software te creëren met een hoge mate van correctheid. Correctheid verwijst naar de mate waarin software zich gedraagt zoals bedoeld. Simpel gezegd: een systeem functioneert correct als het zijn functies goed uitvoert, zonder bugs of fouten. Correctheid is een van de eerste kwaliteitsmaten die ontwikkelaars leren kennen, of dat nu formeel is of intuïtief.
De eenheid waarmee we correctheid meten, is de defect density (DD).
DD = (number of defects / number of lines of code) * 1000.
Een lagere DD betekent een hogere mate van correctheid. Het is mogelijk om de DD van nog niet ontdekte defecten in een stuk software te schatten door het aantal gevonden fouten in de loop van de tijd te volgen. Houd er echter rekening mee dat refactoring, vooral wanneer een engineer de hoeveelheid code vermindert, de DD juist kan verhogen. Dit betekent ook dat je de DD kunt verlagen door nutteloze regels code toe te voegen of door code zo te herschrijven dat die uit meer regels bestaat.
Ten eerste: doe dat niet. Ten tweede: tenzij je systeem heel klein is of het aantal defecten extreem hoog, zal deze aanpak nauwelijks helpen.
Het klinkt aantrekkelijk om te zeggen dat de correctheid absoluut moet zijn en dat de defectdichtheid nul moet zijn. Maar dat is niet alleen bijna onmogelijk, het is ook pure tijdverspilling. Die inspanning weegt simpelweg niet op tegen de opbrengst. Waarom? Omdat lagere kwaliteit soms juist meer waarde oplevert. Dat klinkt tegenstrijdig, maar het is eigenlijk heel logisch. Stel dat we een app bouwen. Op dit moment heeft de app een DD van 5 (oftewel: 5 fouten per 1000 regels code). We kunnen ervoor kiezen om veel tijd te besteden aan het refactoren en repareren van de app totdat we een DD van 0 bereiken, of we kunnen veel minder tijd besteden, de DD terugbrengen tot een acceptabel niveau (bijvoorbeeld 2 of 4) en live gaan, zelfs met enkele resterende fouten. Welke optie denk je dat waardevoller is? In de meeste gevallen zal de tweede keuze de juiste zijn. Een eerdere release voor gebruikers levert meestal meer waarde op, tenzij de defectdichtheid extreem hoog is en gebruikers de software afwijzen uit frustratie of wantrouwen.
Ten eerste wordt het verlagen van de DD exponentieel moeilijker naarmate je dichter bij nul komt. Ten tweede is er nog een andere belangrijke maatstaf: time to market (TTM). Hoe eerder je app of een nieuwe feature beschikbaar is, hoe meer waarde deze genereert.
Dus, als nul niet het doel is, wat zou dan een goed DD-doel zijn voor een applicatie? Je zult veel bronnen vinden die beweren dat één defect per 1000 regels code een “goede” DD is, maar ik wil dat nuanceren met een oude wijsheid uit de software-engineering: het hangt ervan af. Er bestaat geen universeel geaccepteerd doelgetal voor DD. Soms is hoge kwaliteit essentieel. Bijvoorbeeld, als je software ontwikkelt voor de medische sector, kun je je geen fouten of storingen veroorloven. In andere gevallen, bijvoorbeeld bij het bouwen van een game, kan streven naar een extreem lage defectdichtheid fataal zijn, omdat de langere ontwikkeltijd betekent dat nieuwe engines op de markt verschijnen en je game verouderd is nog voordat hij uitkomt.
Uiteindelijk moet je het doel voor correctheid met je team bespreken. Gelukkig is correctheid iets wat iedereen begrijpt - projectmanagers, product owners en zelfs gebruikers. Maar in veel sectoren is een functioneel, werkend product alles wat nodig is om winst te maken.
Oud en betrouwbaar
De volgende maatstaf voor softwarekwaliteit is betrouwbaarheid. Betrouwbaarheid is een cruciale factor in software-engineering. Het kan worden gedefinieerd als het vermogen van een systeem om zijn beoogde functie uit te voeren onder specifieke omstandigheden en gedurende een bepaalde periode. Met andere woorden, betrouwbaarheid meet het vermogen van een systeem om consistent te presteren zonder te falen, terwijl correctheid betrekking heeft op het relatieve aantal fouten in het systeem.
Betrouwbaarheid is vooral belangrijk in toepassingen die gevoelige of bedrijfskritische gegevens verwerken, zoals financiële transacties, medische dossiers of militaire operaties. Net als bij correctheid moet je begrijpen welk niveau van betrouwbaarheid vereist is voor een bepaalde applicatie. Dit kan variëren afhankelijk van de sector, het doel van de applicatie en de gevolgen van een storing. In sommige gevallen heeft het bedrijf een zeer hoge betrouwbaarheid nodig, zoals bij medische apparatuur of luchtvaartsystemen. In andere gevallen is het bedrijf bereid om wat in te leveren op betrouwbaarheid om kosten te besparen, bijvoorbeeld bij een consumentgerichte mobiele app. Hoe dan ook, het is de verantwoordelijkheid van de software engineer om ervoor te zorgen dat de applicatie voldoet aan het vereiste betrouwbaarheidsniveau.
Een manier om de betrouwbaarheid te vergroten is door een DevOps-mentaliteit aan te nemen. DevOps is een reeks praktijken die softwareontwikkeling (Dev) en IT-operaties (Ops) combineert om de kwaliteit en snelheid van softwarelevering te verbeteren. Door DevOps-praktijken toe te passen kunnen software engineers het deploymentproces automatiseren, waardoor de kans op menselijke fouten afneemt en de software consistent kan worden uitgerold.
Daarnaast kunnen software engineers monitoringtools gebruiken om storingen te detecteren en te helpen bij het oplossen ervan. Monitoring houdt in dat verschillende prestatie-indicatoren worden gevolgd, zoals responstijd, CPU-gebruik en geheugengebruik, om afwijkingen te signaleren die kunnen wijzen op een storing. Door storingen vroegtijdig te detecteren, kunnen engineers actie ondernemen voordat het probleem ernstig wordt.
Bij het meten van betrouwbaarheid zijn er twee belangrijke indicatoren: Mean Time Between Failures (MTBF) en Mean Time To Repair (MTTR). MTBF is de gemiddelde tijd tussen storingen van een systeem, terwijl MTTR de gemiddelde tijd is die nodig is om een systeem te herstellen nadat een storing heeft plaatsgevonden. Een hoge MTBF en een lage MTTR zijn ideaal. Naar mijn mening is het een kerntaak van een software engineer om de betrouwbaarheidsstatistieken te bespreken met het team en met het bedrijf. Door dit gezamenlijk te doen, begrijpt iedereen welk niveau van betrouwbaarheid wordt verwacht en kan er effectief worden samengewerkt om dat doel te bereiken.
Inzicht in investeringen
Hopelijk heb ik inmiddels uitgelegd waarom het belangrijk is om doelen te stellen voor correctheid en betrouwbaarheid. Een van de belangrijkste redenen waarom die doelen niet op perfectie kunnen worden gezet - naast de redenen die ik eerder heb genoemd - is kosten. Alles kost geld, en de meeste van ons schrijven software met als doel om geld te verdienen. Zelfs als je werkt voor een non-profitorganisatie of een applicatie bouwt voor je plezier, blijft het een doel om de bijbehorende kosten te minimaliseren.
We verwachten dat het bedrijf begrijpt wat correctheid en betrouwbaarheid betekenen, toch? Dan is het, vind ik, niet meer dan redelijk dat wij als software engineers de financiële kant begrijpen. Dat betekent dat een software engineer niet afhaakt wanneer er wordt gesproken over budgetten, kosten en Return on Investment (ROI).
ROI - winst op een investering / kosten van de investering
Let wel: de ROI van een feature is tijdens de ontwikkelfase natuurlijk altijd een schatting.
Software-engineering is uiteindelijk een bedrijfstak, en het is essentieel om rekening te houden met ROI bij het ontwikkelen van software. Die verantwoordelijkheid ligt meestal bij andere mensen in de organisatie, maar als software engineer moet je in staat zijn met hen te communiceren, hun perspectief te begrijpen en softwarematige overwegingen te vertalen naar termen van kosten en winstgevendheid.
Door ROI mee te nemen, kun je bepalen welk kwaliteitsniveau je software moet bereiken. Lagere kwaliteit kan soms juist meer winst opleveren dan hogere kwaliteit, afhankelijk van de markt en de verwachtingen van klanten.
Als een bedrijf bijvoorbeeld software ontwikkelt voor een nichemarkt met weinig concurrentie, kan het zich lagere kwaliteit veroorloven, omdat klanten weinig alternatieven hebben. Aan de andere kant, als een bedrijf software maakt voor een sterk concurrerende markt, moet er waarschijnlijk meer worden geïnvesteerd in kwaliteit om zich te onderscheiden. Maar zelfs dan is er een grens aan hoeveel investering nog de moeite waard is.
Het is dus cruciaal om de balans tussen kwaliteit en kosten te vinden die de winst maximaliseert. Mijn advies om dat perfecte evenwicht te bereiken: richt je op de laagst mogelijke kosten, terwijl je toch voldoet aan de minimale eisen voor correctheid en betrouwbaarheid. “Minimaal” klinkt misschien negatief, maar anders gezegd: we optimaliseren de ROI terwijl we de vereiste kwaliteit behouden.
Schulden aangaan
Soms betekent een focus op ROI dat we besluiten technische schuld aan te gaan. Technische schuld is niet per se iets dat het bedrijf ons oplegt. Zoals bij elke vorm van schuld, gaat het erom dat je nu iets uitgeeft wat je eigenlijk nog niet hebt. In dit geval ruilen we softwarekwaliteit in voor een verwachte hogere ROI. Wanneer het goed wordt gedaan, helpt technische schuld ons om de ideale balans te vinden. Wanneer het verkeerd wordt aangepakt, zorgen de “rente” en bijkomende kosten er alleen maar voor dat de totale kosten stijgen. Met andere woorden: technische schuld aangaan is een strategische keuze – een complexe strategie, zeker, maar wel één die jij als software engineer moet begrijpen. Te vaak hoor je ontwikkelaars zeggen dat “alle technische schuld slecht is”, en daarop zeg ik: technische schuld is een noodzaak in de echte wereld. Ja, je zult waarschijnlijk een afbetalingsplan nodig hebben. Maar als je bewust schuld aangaat, kun je de investering vergroten en – mits goed uitgevoerd – ook het rendement verhogen.
Er bestaan verschillende metrieken die indirect technische schuld meten, zoals Cycle Time (de tijd tussen een commit en de daadwerkelijke deployment) en Bug Ratio (het aantal openstaande bugs gedeeld door het totaal aantal bugs, dus open plus gesloten bugs). Voor beide geldt: hoe lager, hoe beter. De meest waardevolle maatstaf voor technische schuld is echter de Technical Debt Ratio (TDR). Deze kan worden uitgedrukt in tijd of in geld, waardoor de vergelijking met andere metrieken eenvoudig is.
TDR = (Kosten van herstel ÷ Ontwikkelkosten) × 100
Hierbij verwijst “Kosten van herstel” naar de kosten die nodig zijn om de technische schuld aan te pakken, zoals het identificeren en prioriteren van die schuld, het refactoren van code of het updaten van afhankelijkheden.
“Ontwikkelkosten” zijn de kosten om de software te ontwikkelen of te onderhouden zonder rekening te houden met technische schuld – dus bijvoorbeeld voor het ontwikkelen van nieuwe features, het oplossen van bugs of het uitvoeren van standaard onderhoud.
De TDR-formule geeft het percentage van de herstelkosten ten opzichte van de ontwikkelkosten weer.
Een hogere ratio betekent dat een groter deel van de ontwikkelkosten nodig is om technische schuld op te lossen, wat duidt op een hogere schuldgraad binnen het systeem. Handmatige schattingen zijn vaak inconsistent en subjectief en daardoor minder betrouwbaar vanuit zakelijk perspectief. Gelukkig kan dit tegenwoordig grotendeels geautomatiseerd worden met tools zoals SonarQube.
Zoals ik eerder oversloeg maar hier wil toevoegen: technische schuld kan ook ontstaan zonder strategische bedoeling, puur door fouten in technisch leiderschap. Problemen zoals een slechte architectuur, zwakke reviewprocessen, gebrekkige tooling, documentatie of testsets kunnen ontstaan zonder enige druk van het bedrijf. Dit noemen we onbedoelde schuld, en software engineers zijn verantwoordelijk om die koste wat het kost te voorkomen.
Naast ROI en technische schuld is er nog een andere manier waarop een software engineer zich bewust moet zijn van kosten: ontwikkelefficiëntie. Het is de taak van de software engineer om ervoor te zorgen dat ontwikkeltijd efficiënt wordt besteed, aan de juiste zaken, om onnodige kosten te vermijden. Simpel gezegd: jij bent de expert, dus gedraag je er ook naar.
Afhankelijk van de duur van een project kan die expertise verschillende vormen aannemen. Meestal valt er echter veel geld te besparen door te zorgen dat de volgende processen onderdeel zijn van je softwareontwikkelstrategie: ticketing en/of user stories, versiebeheer, code reviews, een Definition of Done, continue integratie, geautomatiseerde tests, geautomatiseerde (eventueel continue) deployments via infrastructure-as-code, monitoring, alerts en regelmatige gebruikersinteractie tijdens de ontwikkeling.
Kort gezegd: door repetitieve taken te automatiseren, werk te documenteren en de interactie tussen team, systeem en gebruikers te vergroten, kunnen engineers de totale ontwikkelkosten aanzienlijk verlagen. Kosten minimaliseren vraagt om een pragmatische aanpak die rekening houdt met de markt, klantverwachtingen en de financiële doelstellingen van het bedrijf.
Met andere woorden: het is jouw taak om te streven naar “goed genoeg” wanneer “perfect” of zelfs “zeer goed” duurder en onnodig is. En dat blijft waar, ongeacht dat warme, tevreden gevoel dat we allemaal krijgen bij het bedenken van de talloze manieren waarop we een systeem nóg beter zouden kunnen maken.
Dezelfde taal spreken
Alex pakt een flip-over en een stift en begint de verschillende metrieken in kaart te brengen waar het team rekening mee moet houden. “Laten we beginnen met de Return on Investment,” zegt Alex. “Wat is de potentiële waarde van het koppelen van onze onderwijspartner aan onze e-cursussen, en hoeveel gaat het ons kosten om dat te bouwen?” Het team begint cijfers en ideeën te roepen, en Alex noteert ze op de flip-over. Er volgen nog meer metrieken en veel meer discussie, en al snel staat het bord vol met getallen en concrete gesprekspunten.
Na een uitgebreide bespreking vat Alex de metrieken en hun implicaties voor het Public API-team samen. “Op basis van ons gesprek lijkt het erop dat het koppelen van de onderwijspartner aan onze e-cursussen een voldoende hoge ROI zou opleveren om wat extra technische schuld aan te gaan. Maar we moeten wel rekening houden met de betrouwbaarheid van de applicatie, die bevindt zich gevaarlijk dicht bij het onaanvaardbare niveau. De volgende keer moeten we een plan maken om een deel van die technische schuld af te lossen zodra we klaar zijn met de education API. Dat lijkt me de beste manier om aan al onze gestelde doelen te blijven voldoen.”
Het team knikt instemmend. Een van de ontwikkelaars stelt zelfs voor om deze doelen toe te voegen aan de Definition of Done van het team, waarop opnieuw instemmend wordt geknikt. De projectmanager bedankt Alex voor het helpen creëren van duidelijkheid in het gesprek. De weken erna zijn de meningsverschillen binnen het team er nog steeds, maar ze kunnen in elk geval duidelijk verwoorden waar ze het over oneens zijn.
Dan wordt Alex gebeld door de product owner van het Etrain Public Website-team. “Wij hebben gehoord wat je hebt gedaan voor het Public API-team,” zegt de product owner, “en we zouden graag jouw hulp inschakelen.”
“Hebben jullie problemen?” vraagt Alex.
“Nee, integendeel!” antwoordt de product owner. “We zijn genomineerd voor E-learning Website van het Jaar, maar we denken dat jij onze kansen om te winnen echt kunt vergroten.”
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.
ContenttypeBlog
-
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.
ContenttypeBlog
-
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.ContenttypeBlog
Altijd op de hoogte met onze tech-updates!
Schrijf je in en ontvang om de week een update met de nieuwste kennis en ontwikkelingen.