De klant vraagt: "Als het toevoegen van deze functie in een nieuwe applicatie een week zou kosten, waarom is daar hier drie weken voor nodig?"
Dat is een heel goede vraag.
En vaak luidt het antwoord niet: "omdat programmeurs langzamer werken".
Het probleem kan veel dieper liggen - in de architectuur van het systeem, de afhankelijkheden, de manier waarop gegevens worden opgeslagen, het gebrek aan tests, historische beslissingen en opeenvolgende wijzigingen die in de loop der jaren zijn toegevoegd.
Juist daarom is de kost van softwareontwikkeling niet constant.
Dezelfde functie kan in twee verschillende systemen een totaal ander bedrag kosten.
Code wordt niet alleen op basis van het aantal functies geprijsd
Op het eerste gezicht kan een taak banaal lijken.
"Laten we de mogelijkheid toevoegen om gegevens naar Excel te exporteren."
Of: "Laten we een nieuwe gebruikersrol toevoegen."
Of: "Laten we het systeem koppelen aan ons CRM."
Het probleem is dat een functie nooit volledig losstaat van de rest van het systeem.
Nieuwe functionaliteit kan wijzigingen vereisen in:
- de database,
- de API,
- de backend,
- de frontend,
- het rechtenbeheer,
- de logging,
- rapportage,
- integraties,
- tests,
- cachemechanismen,
- documentatie,
- het uitrolproces.
Hoe sterker een systeem verweven is, hoe meer elementen er vóór een wijziging geanalyseerd moeten worden.
De grootste kost kan ontstaan vóórdat de eerste regel code is geschreven
In een volwassen systeem zou een programmeur niet zomaar moeten beginnen met schrijven.
Eerst moet worden beantwoord:
- Waar moet deze functie worden toegevoegd?
- Met welke modules zal ze communiceren?
- Welke gegevens gebruikt ze?
- Dekken de bestaande autorisatiemechanismen dit af?
- Heeft de wijziging invloed op andere processen?
- Welke tests moeten worden bijgewerkt?
- Laat de huidige architectuur dit überhaupt correct toe?
Dit alles maakt deel uit van de kost van het ontwikkelen van de functie.
Daarom kan in een oud systeem een groot deel van het werk niet het programmeren zelf zijn, maar het in kaart brengen van de afhankelijkheden en beperkingen van de bestaande oplossing.
Technical debt werkt als rente
Een goede manier om over technical debt na te denken is precies de kost van volgende wijzigingen.
Als een bepaalde oplossing ooit snel is gebouwd, kan dat volledig gerechtvaardigd zijn.
Het probleem ontstaat wanneer een tijdelijke oplossing een vast onderdeel van het systeem wordt.
Er komt een volgende functie.
Dan nog een.
Er verschijnt een uitzondering.
Dan nog een uitzondering.
Daarbovenop komen integratie, workarounds, handmatige processen en extra regels.
Na enkele jaren herinnert niemand zich nog waarom het systeem precies zo werkt.
Maar elke volgende wijziging moet al die historische beslissingen meenemen.
Martin Fowler beschrijft technical debt als de extra inspanning die bij systeemwijzigingen ontstaat door problemen met de interne kwaliteit ervan.
Je kunt dus zeggen: technical debt hoeft ontwikkeling niet meteen stil te leggen. Eerst zorgt het ervoor dat elke volgende wijziging duurder wordt.
Signaal één: "toevallig moeten er nog vijf dingen worden aangepast"
Dit is een van de meest karakteristieke symptomen.
De klant bestelt één functie.
Tijdens de analyse blijkt dat om die te implementeren, het volgende nodig is:
- de tabelstructuur aanpassen,
- de manier van autoriseren wijzigen,
- de bibliotheek bijwerken,
- de oude API repareren,
- een deel van de frontend herschrijven.
Plots is een kleine functie geen kleine functie meer. Niet omdat de vereiste ingewikkeld is. Maar omdat het systeem geen passende architecturale grenzen meer heeft.
Signaal twee: één wijziging vereist testen van het hele systeem
Als een kleine wijziging een volledige handmatige regressietest vereist, betaalt de organisatie voor een gebrek aan automatisering.
Naarmate het systeem groeit, neemt het aantal mogelijke combinaties toe.
Zonder een geschikte set tests wordt het steeds moeilijker om zeker te weten dat de nieuwe functie de oude niet heeft beschadigd.
Dat leidt op zijn beurt tot voorzichtigheid.
Uitrolmomenten zijn zeldzamer.
Wijzigingen worden groter.
Het risico neemt toe.
En grotere uitrollen zijn moeilijker te diagnosticeren wanneer er problemen zijn.
Er ontstaat een vicieuze cirkel.
Signaal drie: "van deze module kunnen we beter afblijven"
Die uitspraak zou een waarschuwingslampje moeten doen branden.
Als een specifieke module een gebied is geworden dat het team vermijdt omdat het gedrag ervan onvoorspelbaar is, heeft het systeem een belangrijk onderhoudsprobleem.
Nog erger is het als alleen één persoon weet hoe het werkt. Dan heeft het bedrijf niet alleen technical debt. Het heeft ook knowledge risk.
Het vertrek van één werknemer kan betekenen dat kennis verloren gaat die nodig is om het systeem veilig verder te ontwikkelen.
Signaal vier: elke functie vereist uitzonderingen
Een goed ontworpen systeem zou voorspelbare regels moeten hebben.
Als elke volgende functie vraagt om het toevoegen van een speciale uitzondering, een extra voorwaarde of een individuele route, begint de architectuur waarschijnlijk de groei te beperken.
Dat leidt vaak tot code waarvan het gedrag niet langer eenvoudig te voorspellen is.
En een gebrek aan voorspelbaarheid betekent een hogere kost voor analyse, tests en onderhoud.
Moet alles herschreven worden?
Nee.
En hier komen we bij een heel belangrijk onderscheid.
Technical debt betekent niet automatisch dat een rewrite nodig is.
Mogelijke oplossingen zijn onder meer:
Refactoring
Dus de structuur van bestaande code verbeteren zonder het zakelijke gedrag te veranderen.
Dat is een goede richting wanneer het systeem nog steeds een logische architectuur heeft, maar specifieke onderdelen moeilijk te onderhouden zijn.
Gerichte modernisering van componenten
Je hoeft niet de hele applicatie te vervangen.
Je kunt beginnen met de meest problematische module, integratie of laag.
Stapsgewijze migratie
Nieuwe onderdelen kunnen naast het oude systeem draaien, en volgende gebieden worden geleidelijk overgezet.
Deze aanpak helpt het risico van een eenmalige migratie te beperken. In de literatuur over legacy modernisation wordt vaak juist gebruikgemaakt van het geleidelijk uitlichten van functionaliteit en het vervangen van opeenvolgende delen van het systeem.
Rewrite
De bouw van een nieuw systeem is zinvol wanneer de huidige architectuur zo beperkend is dat verdere modernisering geen verantwoorde opbrengst meer oplevert.
Maar een rewrite moet het resultaat zijn van analyse, niet van de frustratie van het team.
Wanneer is het nog niet de moeite waard om te investeren in modernisering?
Technische schuld op zichzelf is geen reden om de ontwikkeling stop te zetten. Elk systeem heeft een bepaald niveau van technische schuld. Soms is het economisch niet zinvol om die af te lossen.
Als de applicatie:
- stabiel werkt,
- veilig is,
- een klein aantal wijzigingen heeft,
- een proces ondersteunt dat niet wezenlijk zal groeien,
- geen operationele problemen veroorzaakt,
kan het rationeel zijn om haar in de huidige staat te laten.
Het gaat er niet om dat elk systeem technologisch perfect is.
Het gaat erom dat het niveau van schuld een bewuste beslissing is.
Wanneer wordt de kost van schuld een bedrijfsprobleem?
Wanneer die begint te beïnvloeden hoe goed het bedrijf presteert.
Bijvoorbeeld:
Een nieuwe functie zou binnen een maand op de markt komen, maar heeft er drie nodig.
De integratie met een nieuwe partner sleept aan, omdat de API van het oude systeem nieuwe gegevens niet gemakkelijk kan verwerken.
De sleutelpersoon in het team moet telkens aan het werk deelnemen, omdat alleen die persoon de oude module kent.
Elke grotere uitrol vereist urenlange regressietests.
De concurrent brengt sneller nieuwe functies uit, omdat zijn platform sneller experimenteren mogelijk maakt.
Op dat moment is technical debt niet langer een probleem van de IT-afdeling.
Het wordt een bedrijfsprobleem.
Hoe meten we of de situatie verslechtert?
Je hoeft geen ingewikkeld KPI-systeem op te zetten.
Het is de moeite waard om enkele eenvoudige indicatoren te volgen:
Lead time - hoeveel tijd verstrijkt er van de start van het werk aan een wijziging tot de implementatie ervan.
Frequentie van uitrol - hoe vaak het team veilig wijzigingen kan opleveren.
Change failure rate - hoe vaak uitrollen problemen veroorzaken.
Hersteltijd - hoe snel men na een storing kan terugkeren naar stabiele werking.
Doorlooptijd van een functie - of vergelijkbare taken steeds meer inspanning vereisen.
Daarnaast is het de moeite waard om het aantal handmatige handelingen, testdekking, de actualiteit van afhankelijkheden en de tijd te analyseren die nodig is om een nieuwe programmeur in het project in te werken.
Dergelijke gegevens maken het mogelijk te zien of het probleem werkelijk technisch is, of misschien voortkomt uit het proces, de vereisten of de manier waarop het werk is georganiseerd.
De slechtste oplossing is "nog één snelle fix"
Als het team weet dat de architectuur veranderingen vereist, maar het onderwerp telkens uitstelt, kan het systeem in een spiraal terechtkomen.
"Laten we nu een workaround maken."
"De refactoring doen we later."
"Voor nu is het voldoende."
"Bij de volgende release."
Het probleem is dat de volgende release opnieuw eisen met zich meebrengt.
En elke volgende workaround verhoogt de kost van de volgende wijziging.
Daarom moet de beslissing om technical debt af te lossen deel uitmaken van de strategie voor productontwikkeling, en niet een toevallige reactie op een crisis.
Een goede applicatie is niet die welke nooit veroudert
Elk systeem zal veranderen.
Technologieën zullen veranderen.
Klanten zullen nieuwe behoeften hebben.
Er zullen nieuwe integraties verschijnen.
De manier waarop het bedrijf werkt, zal veranderen.
Daarom zou het doel niet moeten zijn om een applicatie te maken die nooit gemoderniseerd hoeft te worden. Het doel zou moeten zijn om een architectuur te creëren, waarin modernisering mogelijk is zonder het bedrijf stil te leggen.Dat is een enorm verschil.
Want het beste systeem is niet datgene wat er op de dag van lancering het meest modern uitziet. Het is het systeem dat ook na enkele jaren het bedrijf in staat stelt snel te reageren op veranderingen.
En als elke nieuwe functie steeds meer kost, betekent dat niet altijd dat de functie moeilijk is.
Misschien is het systeem zelf al moeilijk geworden.



