Code quality: hoe wij zorgen dat jouw applicatie stabiel is

In den beginne…

Wist ik niet waarom je unit tests zou schrijven. Het voelt als zo’n grote tijdsverspilling wanneer het niet in je systeem zit. De hoeveelheid regels code die nodig is voor een functionaliteit schiet dramatisch omhoog. Zo kun je als voorbeeld nemen “als admin wil ik een account kunnen aanmaken”. In Laravel is dat zo gepiept met een paar regels code (niet schrikken, na dit voorbeeld blijft de post bij Nederlands i.p.v. PHP):

account aanmaken als admin

Simpel, toch? 😉

Wil je dit echter gaan unit testen, heb je een test nodig voor alle subonderdelen van de eerder genoemde functionaliteit:

  • Admin mag een account aanmaken
  • Non-admin mag geen account aanmaken
  • User bestaat en de naam en email zijn gevuld na aanmaken
  • De front-end geeft een nette foutmelding als de email niet klopt
  • De front-end geeft een nette foutmelding als de email al bezet is

Zo zie je al snel hoe de bovenstaande 7 regels code nog makkelijk 50 regels erbij krijgen om alle flows, “happy-paths” en “unhappy-paths” af te vangen.

Tijdverspilling? Nope, lees verder!

Onderhoudbaarheid van software optimaliseren

Software ontwikkeling via de Scrum methodiek betekent dat je flexibel (Agile) te werk gaat. In de praktijk betekent dat dat je in sprints – werk ‘batches’ van een week of 2 – features gaat ontwikkelen. Bij de oplevering van een sprint worden deze besproken met de product owner(s) en worden er nieuwe features gepland. Vervolgens blijkt er een aanpassing aan een feature van de laatste sprint nodig te zijn, past de developer de code aan, test de feature een keer handmatig, en mooi: het werkt nog.

Dat werkt wanneer je pas één sprint heb gehad, maar stel nu een situatie voor waar een platform al 2 jaar live staat, een enorme codebase heeft, duizenden echte users, en dat de feature die gewijzigd wordt in de core van de applicatie zit. In dat geval moet de developer van voor tot achter alles wat in connectie staat met die feature handmatig doortesten, alle mogelijkheden onderzoeken die fout kunnen gaan, alle dingen die een user mogelijk verkeerd doet, enz. Je kunt je voorstellen dat deze tijdsinvestering in testen exponentieel groeit naarmate de applicatie (en wellicht het aantal developers die eraan meewerken) groeit.

En in de praktijk gebeurt dit vaak, een core functionaliteit kan meerdere keren per jaar uitgebreid of aangepast worden naarmate de wensen wijzigen. Heb je dan bij de initiële ontwikkeling wél de tijd genomen om die tests te schrijven, moet de ontwikkelaar natuurlijk nog steeds wel een keer real-life testen, maar neemt de code 95% hiervan uit handen. Je drukt als het ware op een knop en een paar seconden later weet je of alles nog functioneert (de admin kan nog steeds een user maken, non-admins niet, enz. enz.).

Automated tests en code quality

Die knop waar ik het net over had, kan ook nog worden geautomatiseerd. Daarmee komen we bij CI/CD pipelines. Bij Scrumble zijn wij groot fan van GitLab en Jenkins, en deze vervullen ieder hun eigen rol.

Hoe wij GitLab gebruiken om problemen op te speuren

Wanneer een developer klaar is met zijn/haar code, alles is lokaal getest, en de unit tests slagen, wordt er een merge request aangemaakt. Hier wordt de code regel voor regel nagekeken door een andere developer. Zijn er slordigheidsfoutjes, spaties teveel, of kan de logica geoptimaliseerd worden voor performance, door bijv. minder database calls te doen, zal hij/zij een comment plaatsen.

De vorige developer zal al deze punten dan moeten fixen voordat de code door mag naar de acceptatie branch of zelfs de productie branch. Hiermee wordt al een groot deel van de issues er uitgeplukt en garanderen we dat er geen “quick and dirty” fixes ooit live komen te staan; dat wordt niet goedgekeurd.

Terwijl de controleur aan het controleren is, begint er een ander proces te lopen: de GitLab pipeline. Wij runnen op onze acceptatie omgeving een zogeheten “GitLab runner”. Deze start dynamisch Docker containers wanneer er een pipeline moet starten, en handelt dan stapsgewijs alle onderdelen van de pipeline af.

Bij ons is dat:

  • Een Laravel 8 container opzetten + overige setup (packages, database)
  • Static code analysis runnen
  • Unit (en feature) tests runnen
  • Front-end tests runnen

scrumble gitlab pipeline
Een stukje uit onze GitLab CI pipeline

Mislukt er één stap hierin, bijvoorbeeld een test (van misschien wel 500 tests totaal), komt er een groot rood kruis te staan bij de merge request en kan hij niet meer doorgevoerd worden naar de hoofdbranches. Dit is een seintje voor de developer om eerst die test te gaan fixen. Alle features zouden namelijk altijd moeten blijven werken.

Verder houden wij bij Scrumble 100% code coverage aan. Dat betekent dat iedere regel code in het project (die wij hebben geschreven), ergens in een test wel wordt aangeroepen en gevalideerd of hij werkt zoals wij verwachten.

Tevens wordt er altijd een zogeheten static code analysis uitgevoerd. Dit is een soort test die eigenlijk alle code en alle plaatsen waar code wordt gebruikt, controleert of hij geen vreemde dingen ziet die problemen op zouden kunnen leveren.

Het komt wel eens voor dat een ontwikkelaar aannames maakt, bijvoorbeeld bij een feature als inloggen. Hierbij zou je dan de bijbehorende gebruiker voor een emailadres gaan ophalen en kijken of die gebruiker actief is, bijvoorbeeld “[email protected]”. Het is je misschien niet opgevallen, maar hier zit geen extra check in of er wel een gebruiker bestaat voor dat adres. De static code analysis zal dan een foutmelding geven die zegt “misschien bestaat de user niet, en dan gaat je code kapot”. Ook dit mag niet in het project voorkomen, anders krijg je weer het dikke rode kruis en mag je niet mergen.

gitlab test resultaten
Rood kruis links bovenin betekent dat je code niet door mag

Op deze manier hoeven wij nooit meer bang te zijn dat zaken “omvallen” op hele andere plaatsen in de applicatie dan waar we aan hebben gewerkt.

Hoe Jenkins de laatste fouten detecteert voor ze ooit live staan

Dit is de laatste stap. Een tweede developer heeft de code goedgekeurd, alle tests slagen, en de code heeft nog steeds 100% coverage. De code mag nu gemerged worden naar de acceptatie- of productie branch, en daarna wordt een deploy gestart. Nu wordt Jenkins aan het werk gezet: ook hierin hebben wij een pipeline ontwikkeld, die iets complexer is dan die van GitLab. Deze belicht ik nog een keer in een andere post, dat wordt namelijk nog een stuk technischer.

Één onderdeel van deze pipeline wat nog wel interessant is, is de front-end build stap. Wij werken namelijk met React, wat ontwikkeld is op JavaScript. JavaScript is van zichzelf weakly-typed (dus niet strictly-typed), wat betekent dat je als developer eigenlijk vrij bent om datatypes door elkaar heen te gebruiken en funky dingen mee doen zoveel als je maar wilt. Dit levert soms grappige dingen op (“2” + 2 komt overeen met “22”, maar “2” – 2 komt overeen met 0), maar in de praktijk betekent dit dat je jezelf erg makkelijk in de vingers snijdt. Dit is op te lossen met strict typing, waar wij TypeScript voor gebruiken. Op deze manier forceren we de developer om aan te geven welke datatypes hij/zij verwacht. Wil je dat je functie twee cijfers ontvangt? Zet erbij nummerA: number, en de compiler gaat vanzelf klagen als je wat anders doet.

Hier komt Jenkins into play: we laten de build stap uitvoeren in Jenkins, en deze voert dus die compile uit. Loopt TypeScript hierbij tegen een error aan, krijgt de developer wederom een groot rood kruis te zien bij die build, en de nieuwe release houdt per direct op (en de werkende versie van de applicatie blijft dus live staan).

jenkins typescript errors
Niet goed

Code waar we achter staan

Nadat de programmeur zijn laatste TypeScript errors ook nog heeft gefixt, een tweede developer deze weer heeft gecheckt, de automated tests nog steeds slagen, en de build nu dus ook wordt afgerond, staat de applicatie eindelijk live. Het kost wat extra CPU en RAM kracht van de server, en wat extra denkkracht van een tweede developer, maar op deze manier weten we (bijna 100%) zeker dat hetgeen wat live komt te staan niet zomaar omvalt wanneer je er verkeerd naar kijkt. We ontwikkelen op deze manier code waar we trots op zijn, en over twee (of vijf, of zes) jaar tijd nog steeds aanpassingen in kunnen aanbrengen zonder steeds elke feature met de hand door te testen.

succesvolle pipeline mergen maar
Goed gewerkt, jongens

Meer weten? Vincent helpt je graag verder.

Meer lezen over digitalisering en software ontwikkeling?

Hier vind je de meest recente artikelen.

12345