Kwaliteit en snelheid in softwareontwikkeling: een paradox?

In een recent project vroeg een directeur 🎩 me in de context van een project met tijdsdruk de bevestiging dat ik “hopelijk niet uit een industrie kom waar alles aan strenge kwaliteitsnormen moet voldoen”.

Een goede vraag! 🎯 Het roept namelijk iets fundamenteels op over softwareontwikkeling: is er ruimte voor snelheid zonder in te boeten op kwaliteit? 🤔

🔎 Een nadere blik op de realiteit in softwareontwikkeling:

1️⃣ “Een stevige basis bouwen kost tijd” 🎓: In sectoren zoals de luchtvaart, medische technologie of de financiële wereld is het vanzelfsprekend dat je niet kunt inleveren op kwaliteit – en terecht. Maar die kwaliteit zit op een heel ander abstractieniveau dan waar we het in softwareontwikkeling doorgaans over hebben. Een fundamenteel doordacht ontwerp staat los van de toepassing van ducktape als constructiemateriaal tijdens het bouwen.

1️⃣ In wat door moet gaan voor “agile” omgevingen, streven we vaak slechts naar snelle releases tegen een krappe deadline. Maar wat we vaak vergeten is dat snelheid zonder kwaliteitscontrole door de complexiteit van moderne software met zekerheid leidt tot een enorme hoeveelheid fouten. 💩 En die herstellen kost uiteindelijk méér tijd, waardoor de bereikte snelheid niet te handhaven blijkt en de agile transitie “dus” niets op heeft geleverd. 😢

3️⃣ In mijn persoonlijke ervaring heb ik meerdere malen mogen ervaren dat de fundamentele keuze voor “kwaliteit boven meer features” 🌈 een wonderbaarlijk solide basis oplevert voor de toekomst: Het betekende veel minder tijd kwijt zijn aan het doorlopend herstellen van problemen, en dus volledige focus op features die werkelijk waarde aan het product toevoegen.

Dit klopt met de stelling over “technical debt” door Ward Cunningham: Snelle, suboptimale codeoplossingen lijken op korte termijn handig zijn, maar leiden uiteindelijk tot extra werk en onderhoudskosten die je later moet “aflossen” om de code onderhoudbaar te houden.

Investeren in kwaliteit levert daarmee juist de gehoopte verhoging van snelheid op. 🚀 Misschien niet in het absoluut aantal gesloten tickets, maar wel in het succes van het product. 💰💰💰

Waarom “Clean Architecture” zo controversieel is

(En de “Hexagonal” architectuur dus ook.)

Nadat Uncle Bob (Robert Martin) jaren geleden zijn “Clean Architecture” in een vaag artikel op zijn blog beschreef, heb ik deze architectuur in verschillende Java backend projecten met wisselend succes toegepast. In eerste instantie door mijn onbegrip over zijn suggesties 🤷‍♂️, maar later ook door onbegrip over wat het werkelijk brengt. 🤦‍♂️

In de wereldwijde Flutter gemeenschap blijkt Clean Architecture inmiddels ook een enorme opmars the hebben gemaakt. Maar ook daar lijkt het onbegrip hoogtij te vieren, waardoor zelfs simpele apps trots worden gebouwd als enorme gelaagde gedrochten. 🦄

Fundamenteel is Clean Architecture een poging om vanwege “Separation of Concerns” het domein van de applicatie los te weken van de opslag en de toepassing in een IT omgeving (=de UX of een API). Hiervoor wordt de code in lagen verdeeld, met een strikte oriëntatie van alle afhankelijkheden richting de domein code.

En die strikte opdeling is waar het vaak mis gaat:

➡️ Is die volledige Separation of Concerns wel altijd zo zinvol als de applicatie niet zo groot is, of maar door een enkele ontwikkelaar wordt beheerd?

➡️ Wat nou als het “domein” in de UX of juist in de database is gestopt, en er dus nauwelijks domein over blijft?

Door in die gevallen toch vast te houden aan de voorgeschreven rigide opdeling, is het resultaat een wirwar van interface, classes, en conversies die alleen nog aan de onderhoudskosten en hoeveelheid bugs bijdragen.💩

Clean Architecture is volgens mij nog steeds een goed idee 🌈, maar het is zeker niet de oplossing voor elke situatie.

Waarom je AI juist NIET moet inzetten voor het afleiden van unit testen

De meeste programmeurs blijven een voorkeur hebben voor het achteraf schrijven van automatische testen. Ik vind dat sub-optimaal, maar het is beter dan geen testen. Met de opkomst van code assistenten, is de nieuwste trend om deze testen door AI te laten schrijven. ✨

Maar is dat wel zo’n goed idee? 🤔

Voor eenvoudige code lijkt er geen vuiltje aan de lucht. Echter, zodra code complexer is (en de testen dus belangrijker), ontstaan er significante risico’s:

1️⃣ Testen zijn (ook) bedoeld om de intentie van de programmeur weer te geven. Bij een complexe implementatie raken die intenties verborgen in de slimme code, zodat de gegenereerde testen nog slechts de implementatie in plaats van de intentie beschrijven.

2️⃣ Als er bugs in de implementatie zitten, dan zullen de testen slechts bevestigen dat die bugs er in zitten. Naast dat dit false-positives over de correcte werking oplevert, zullen de toekomstige oplossers van deze problemen hierdoor zelf moeten gaan bedenken of het ongewenste gedrag wellicht toch met opzet is gebouwd.

3️⃣ Realiteit van automatische testen is dat (lang) niet alle paden te testen zijn, waardoor een menselijke programmeur zal kiezen voor testen die het bedoelde bedrag zo ver mogelijk afdekken. Omdat een AI die context niet heeft, zullen er ook onnodige testen worden geproduceerd die niets toevoegen maar wel onderhouden moeten worden.

Als we door AI gegenereerde testen dus niet (minstens) zorgvuldig handmatig controleren, zal hun waarde niet veel groter zijn dan de klassieke pogingen om de “coverage” op te krikken met testen die de code aanroepen zonder te controleren of het gedrag wel klopt. 🙈

Laten we AI vooral gebruiken waar het echt waarde toevoegt, maar de regie over kwaliteit voorlopig nog even bij mensen laten. 🤝

De allersnelste database is er geen

En de goedkoopste ook! 💰

Voor de recente ontwikkeling van mijn “Cannect” app had ik een backend nodig die kandidaten ter beoordeling voorstelt, en beschikbare kandidaten aan elkaar koppelt. Dit is een relatief eenvoudig algoritme, maar wel met potentieel veel data die bovendien doorlopend wijzigt. Omdat mijn doel was om de backend zo goedkoop mogelijk te houden, besloot ik om de actuele data niet in een database te bewaren en met SQL te queryen, maar als datastructuren in het geheugen van de server.

Het gevolg is een oplossing die met minimale processing grote hoeveelheden transacties kan verwerken, omdat de data rechtstreeks in het geheugen beschikbaar is, en de interactie met een externe database niet meer nodig is. Dit maakt dat een eenvoudige server meer capaciteit levert, en de operationele kosten van een SQL database zowel in performance als geld zijn voorkomen.

Omdat servers soms herstart moeten worden, heb ik in plaats van de database een mechanisme toegevoegd dat de evolutie per evenement in een bestand schrijft. Normaal worden deze bestanden nooit meer gelezen, maar na een reset kan de server hiermee toch terug worden gebracht in de laatste stabiele toestand. Deze oplossing is net zo simpel als hij doeltreffend is.

Na het aflopen van een evenement kan bovendien het bijbehorende bestand zonder impact op het systeem worden gearchiveerd tot het definitief wordt vernietigd. Dat is laatste wel zo goed voor de privacy van de deelnemers. 🕵️

We kunnen stoppen met programmeren!

💡 Wat als ik alleen nog testen schrijf, en rest over laat aan AI?

Ik probeer al jaren netjes volgens TDD te werken: Ik schrijf eerst een (falende) test, schrijf daarna slechts voldoende code om de test te laten slagen, en optimaliseer regelmatig de resulterende code. Zo codeer ik richting het langzaam evoluerende ontwerp dat ik voor ogen heb. Mechanisch, saai, maar wel erg effectief. 🎯

Het (voor mij) fascinerende van deze manier van werken is dat de wijzigingen in resulterende code doorlopend geïmpliceerd worden door mijn testen. En telkens als ik iets “onhandig” optimaliseer, geven mijn testen aan dat ik iets over het hoofd heb gezien. 😇

Maar eigenlijk is het waanzin dat ik dit nog steeds met de hand aan het doen ben, terwijl er in mijn IDE een “slimme” code assistent zit. 🤔

Want stel je deze alternatieve workflow eens voor:

  1. Ik schrijf (met hulp van AI) een falende test om het verwachte gedrag en randgevallen te beschrijven, en geef daarna de beurt expliciet aan de AI.
  2. De AI implementeert de benodigde minimale code om mijn test te laten slagen.
  3. Zodra ik het idee heb dat het daar tijd voor is, laat ik de AI de code helemaal zelfstandig refactoren. (Dit zorgt dat ik als mens kan blijven volgen hoe de implementatie evolueert.)
  4. De AI draait doorlopend de bestaande testsuite om te bevestigen dat er ondertussen door de wijzigingen niets stuk is gegaan.

Dit zou wel eens een hoop mentale ruimte kunnen vrijmaken. Want minder schakelen betekent dat ik me meer kan richten op het grotere strategische plaatje en de samenhang van het project. 🌴☀️

Bestaat er al een code assistent die die dit kan? En zou dit de productiviteit merkbaar vergroten?