Variables Considered Harmful

 

Dit artikel gaat in op methoden om de kwaliteit van programmatuur te verhogen. Er wordt beschreven dat huidige kwaliteitsverbeterende methodes “gestructureerd programmeren”, “correctheidsbewijzen” en “strakke datatypering” de kwaliteit van het programmeren weliswaar hebben verhoogd, maar dat we er hiermee niet zijn.

Aangetoond wordt dat een volgende stap zou moeten zijn: het uiterst restrictief gebruik van variabelen.

Bijvoorkeur zelfs het volledig vermijden van variabelen.

 

Nu 35 jaar geleden schreef Edsger Dijkstra (voor mij: professor Dijkstra) het legendarische artikel “Go To statement considered harmful”. Hij legt in dit artikel uit waarom het goto-statement leidt tot spagetti-programmatuur: onoverzichtelijke, slecht gedefinieerde, onduidelijke programma’s. Deze wijze van programmeren was kennelijk dé bron van fouten.

Ik begreep er weinig van, in die tijd. Pas na 10 jaar ongericht knutselen in allerlei programmeertalen, begon ik mij de kunst van het programmeren wat eigen te maken. En pas na veel lezen over het vak programmeren kwam daar gelukkig ook wat “kunde” bij.

De gangbare programmeertalen zijn inmiddels verrijkt met alle denkbare structureringsmechanieken. Wat je ook nodig hebt, er is altijd wel een goed gedefinieerde constructie, die dit kan uitdrukken. Verder werd in de literatuur de semantiek van alle structureringsconstructies zorgvuldig beschreven, methodes als stepwise-refinement en correctheidsbewijzen werden geïntroduceerd. De wereld lijkt werkelijk sterk verbeterd op dit vlak.

Inmiddels zou ik niets eens meer weten hoe ik precies een Goto-statement zou moeten gebruiken en zeker niet waarvoor.

 

Waarom maken we nog steeds fouten ?

Maar als we aan alle regels van de “kunde” houden, programmeren we dan foutloos? Nee, nog steeds niet. Er zitten nog veel steeds veel fouten in programma’s. Kennelijk zijn er nog meer redenen waarom wij fouten maken. En om fouten te vinden gebruiken we twee methoden: (a) desk-checking en (b) debuggers”.

De eerste methode, desk-checking, is niet verkeerd. Immers je leest rustig je werk na en probeert te controleren of je elke constructie en elke stap goed begrijpt. Of het een goede afbeelding is van de bedoelde oplossingmethode.

De tweede methode is beslist twijfelachtiger. Met een debugger lopen we statement voor statement door het programma en kijken of de programma-flow juist is.  Wijkt de daadwerkelijke programma-flow af van wat we verwachten, dan kijken we naar de waardes van bepaalde variabelen om te zien waarom het verkeerd gaat. We zien in zo’n geval vaak dat een variabele een onverwachte (dus verkeerde?) waarde heeft. Dat noemen we dan, geheel ten onrechte, “de fout”.

 

Al vijftien jaar terug gaf een collega mij aan, dat debuggers eigenlijk verboden moesten zijn. Dit, omdat je er niet de oorzaak van de fout mee ziet, maar het gevolg van de fout. Daarom leidt het gebruik van debuggers bijna als vanzelf tot het aanpakken van symptomen en niet tot het oplossen van de oorzaak. Dit was volgens hem de oorzaak van onbetrouwbare programmatuur.

 

Een mooi voorbeeld van een fout die werd veroorzaakt door onverstandig debuggen was zichtbaar in een programmadeel, bedoeld voor het berekenen van een datum. De programmatekst, met bij behorend commentaar, was:

 

dag := dag + 1;   { dan werkt het tenminste }

 

Met name het commentaar sprak hier boekdelen. Dit gedeelte van het programma was door de programmeur kennelijk niet volledig begrepen. Als er al goede uitkomsten waren geweest, garandeerden deze geenszins dat de uitkomsten onder alle omstandigheden goed zullen zijn..

De oorzaak van aangetroffen programmatekst lijkt direct terug te voeren op het gebruik van een debugger. Er werd een afwijkende waarde geconstateerd, deze werd gecorrigeerd en “klaar is kees”. Gelukkig werd de ingreep gedocumenteerd, zodat de fout via desk-checking door een collega tenminste nog gevonden kon worden.

 

Fouten worden mogelijk gemaakt door het gebruik van variabelen

Maar is de debugger wel de oorzaak? Mijns inziens ligt de oorzaak dieper. De echte oorzaak zou wel eens kunnen zijn: het gebruik van variabelen zelf.

Om dit te begrijpen moeten we kijken naar het wezen van een variabele: de op een bepaalde plek P0 berekende waarde op verderop in het programma, bijv op plaatsen P1, P2, … te kunnen gebruiken voor verdere berekeningen of programmakeuzes.

Maar tussen P0 en P1 en tussen P1 en P2 gebeuren er in het programma allerlei bewerkingen. Het programma doorloopt allerlei paden en procedures en zelfs lopen onverwacht event-handlers van spontaan optredende triggers, zoals in moderne GUI-omgevingen. In een dergelijke warrige wereld wordt de waarde van een variabelen maar al te makkelijk gewijzigd, zonder dat dit direct opvalt.

 

Maar ook als de variabele niet per abuis gewijzigd wordt, gaat het nog makkelijk mis. Als de variabele een nieuwe waarde moet krijgen moet dat dan voor P1 en P2 wel de zelfde waarde zijn? Hopelijk wel, maar soms toch juist niet. In dat laatste geval kan je beter twee variabelen gebruiken: voor elk doel één.

 

Nog moeilijker wordt de semantiek van een if-then-else-constructie, met een variabele in de boolean-expressie.

 

if B then <statement>

     else Vlaggetje := <boolean expressie>;    {P0}

:

:

if Vlaggetje then <statement 1>

             else <statement 2>;              {P1}

 

De waarde die aan “Vlaggetje” gegeven wordt op positie P0 bepaalt uiteindelijk welke statement P1 bij uitgevoerd zal gaan worden. Of de waarde die “Vlaggetje” daarvoor had, dan kan natuurlijk ook nog (als B = True). Het lijkt wel een goto-statement waarmee binnen in een programmablok wordt gesprongen. Ook op déze wijze wordt maar al te gemakkelijk spagetti geconstrueerd.

 

Als je goed nagaat welke statussen een variabele doorloopt, waar die statussen nodig zijn en kijkt in welke stukken van je programmadeel die status ook nog echt geldig is, dan bekruipt je het zelfde gevoel over het gebruik van variabelen als van Dijkstra in zijn artikel had bij Goto-statements: “(…) just too primitive; it is too much an invitation to make a mess of one's program”.

 

Oplossing: geen variabelen meer gebruiken

Maar wat dan? Helemaal geen variabelen meer gebruiken! Immers als je geen variabelen gebruikt, dan kunnen die ook geen fout waarden bevatten. Dit klinkt te eenvoudig om de oplossing te zijn en lijkt geheel ook niet echt realistisch. Maar de praktijk wijst uit: (a) dat het kan en (b) dat de programmatekst er beter leesbaar van wordt.

Al enige jaren probeer ik in mijn (pascal) programma’s het gebruik van variabelen volledig te vermijden. En de conclusie is: ja, dat kan echt. Alleen met de beperkingen die deze taal oplegt, is het soms niet al te praktisch. Maar de conclusie is: ja het kan. En mijn programma’s worden er beter van.

 

Hoe vermijd je variabelen?

Je vermijdt variabelen eenvoudig door het te doen. Door ruim gebruik te maken van functies en procedures en een waarde niet aan een variabele te geven, maar door te geven als parameter, valt heel vele te bereiken.

Is een parameter dan geen variabele? Inderdaad, dat is een parameter niet. Met gebruik making van het juiste parameter-passing mechanisme weet je zeker dat de procedure de waarde niet kan wijzigen.

Zelfs zijn er inmiddels parameter-passing mechanisme, die wijzigen van de parameter binnen de procedure voorkomen. Borland Pascal (in Delphi) kent bijvoorbeeld de volgende constructie van “constant parameters

 

procedure P(const A: integer);

 

De parameter A wordt by-reference doorgegeven, maar de compiler waakt er over dat A ongewijzigd blijft, ook ín de procedure body zelf.

 

Kunnen variabelen altijd vermeden worden? Ik denk van wel, maar het is niet altijd even praktisch. Bijvoorbeeld het gebruik van een loop-variabele in het for-statement kan worden vermeden, zoals Dijkstra al aangaf, door recursie. Ook ik vind dit een wat al te rigoureuze aanpak. In sommige situaties kun je dan toch maar beter wel een variabele gebruiken.

Soms ook vereist een reeds bestaande procedure het gebruik van een variabelen om waarden in terug te geven. En zo zijn er meer situaties.

 

Is het vermijden van variabelen echt nodig?

In een warrige wereld als hierboven beschreven, moeten er maatregelen getroffen worden tegen ongewenste beïnvloeding van variabelen? We hebben inmiddels vele methoden die meehelpen problemen te voorkomen, zoals: “data-hiding”, het niet gebruiken van “side-effects”, betrouwbare technieken voor parameter-passing. Alles is er opgericht om een variabele maar in een beperkt gedeelte van het programma zichtbaar te hebben. Gebruik een zo beperkt mogelijk scope voor variabelen. Dat creëert in ieder geval overzicht. En de kans op ongewenst wijzigen van de variabelen neemt af.

Een tweede maatregel is het vermijden van “moeilijke” if-then-else-constructies, waar de inhoud van variabelen bepaalt wat er gebeurt.

Maar een echte garantie is het uiteindelijk niet, een programma krijgt vaak toch de tendens complexer te worden dan je zou willen. Er is uiteindelijk maar één rigoureus middel. Jawel: het volledig vermijden van variabelen.

 

En als we dan tóch variabelen gebruiken?

Als je toch variabelen moet gebruiken, realiseer je dan tenminste het kwaad dat ze kunnen aanrichten. Houd je dus aan een aantal richtlijnen.

·        Gebruik een variabele altijd maar voor één enkel doel; twee doelen, twee variabelen.

·        Beperkt de scope: gebruik een variabele altijd maar in een zo beperkt mogelijk programmadeel, dat goed te overzien is en goed begrepen wordt.

·        Vermijd vooral globale variabelen, deze zijn immers altijd en overal zichtbaar en dus te wijzigen.

·        Geef de variabele een vooral een goed passende naam, die een status beschrijft.

Als voorbeeld van dat laatste: gebruik niet “I” als variabele-naam, dit zegt helemaal niets. Gebruik ook niet “Paginateller”, want dit geeft het proces aan en niet een status. Een mooie naam zou zijn “AantalAfgedruktePaginas”. Op die manier drukt de naam duidelijk uit wat de waarde van deze variabele voorstelt op elk moment in de tijd.

Als het niet mogelijk is een een-eenduidige statusbeschrijvende naam te kiezen, dan is dat een indicatie dat er wat mis is met het programma.

 

En: werkt het?

Mijn ervaringen met deze aanpak, het geheel vermijden van variabelen dan wel het geven van een goede, statusbeschijvende naam, zijn verbluffend. Het is NIET gemakkelijk, maar de programma’s worden wonderbaarlijk helder en leesbaar. De programmatekst gaat zelfs lijken op een correctheidsbewijs. Het gebruik van verklarend commentaar is bijna overbodig geworden.

 

Daarom ben ik bestaande programma’s gaan voorzien van nieuwe, statusbeschrijvende variabele-namen. Alleen al hierdoor vond ik fouten in programma’s die tot dusverre over het hoofd waren gezien.

Het is deze ervaring die mijn overtuiging sterkt, dat variabelen aanmerkelijk gevaarlijker zijn, dan je op het eerste gezicht zou denken. Er is kennelijk alle reden er heel voorzichtig mee te zijn.

 

Wim Blankenstijn

Maart 2003