Bob Swart (aka Dr.Bob)
Delphi Assertions

Een tijdje terug hadden we het over Delphi Exceptions, de manier om in Delphi je algoritme te scheiden van foutdetectie en foutafhandeling. Deze keer gaan we wat dieper in op extra Delphi hulpmiddelen voor de detectie van fouten, foutsituaties, en dergelijke, onder de naam Assertions.

Alhoewel het om een uitbreiding gaat van Delphi 3, bestaat de Assert() routine en het gebruik ervan eigenlijk al veel langer. Oorspronkelijk afkomstig uit de programmeertaal C, werden assertions het meest gebruikt om de resultaten van funkties te controleren. C funkties, waaronder Windows APIs, geven vaak 0 terug als het goed is, en een foutcode (ongelijk aan nul) indien er iets mis ging. De Assert() routine krijgt als argument de te controleren (pre)conditie; bijvoorbeeld het feit dat de C-funktie nul als resultaat moet hebben. Indien dit het geval is, zal er niks gebeuren. Indien dit echter niet het geval is, zal de Assert routine een foutmelding geven, en daarin tevens vermelden op welke regelnummer van welk source bestand de aanroep binnen de Assert routine faalde. Dit is uitermate handig gebleken om programma's te testen en debuggen zonder al te veel moeite en overhead. Gewoon elke funktie-aanroep binnen een Assert zetten, zoals het volgende fragment:

  assert(haal_de_naam_op(&naam));  // haal de naam op
  assert(lengte_naam(naam));  // kijk of de naam niet leeg is
Zoals uit bovenstaand voorbeeld al blijkt, kunnen C programmeurs de "assert" gebruiken en misbruiken op een manier die ze vrij staat. Een nadeel is dat "assert" op deze manier een onderdeel van het programma blijft, zelfs na het testen en debuggen, terwijl een zuiver gebruik van "assert" in mijn ogen zou moeten worden beperkt tot de zgn. pre-condities.

Pre-condities
Precondities beschrijven de situatie die ten minste waar moet zijn voordat een bepaalde aktie uitgevoerd kan worden. Bijvoorbeeld bij het kopieren van een bestand, is de preconditie dat het (bron)bestand wel bestaat. En zo is bij worteltrekken vaak een preconditie dat het getal niet negatief is, iets wat bij de faculteit funktie altijd moet gelden. Om te voorkomen dat routines die dergelijke algoritmen uitvoeren onnodig de mist in lopen, kunnen we gebruik maken van "assertions" om dus de pre-condities te controleren. Hierdoor kan - net als bij het gebruik van exceptions - het algoritme worden uitgeschreven zonder rekening te hoeven houden met alle mogelijke foute invoer (die zijn immers al door de "assertion" als pre-conditie test eruit gefilterd).

Assert
Hoe ziet nu de aanroep van een assertion eruit? Heel simpel, gewoon een procedure met de naam "assert" die als argument een (boolean) conditie meekrijgt. Als de conditie "True" is dan is er niets aan de hand (de pre-conditie is waar), maar als de conditie tot "False" evalueert, dan zal de "assert" routine een error genereren, waabij tevens de naam van het bestand en de huidige regelnummer vermeld wordt: handig bij het debuggen.

Laten we het eens proberen door in een klein test-programma te testen of een ingevoerd getal wel positief is. Dit kan heel eenvoudig als volgt:

  program assert1;
  {$APPTYPE CONSOLE}
  {$C+}
  var
    i: Integer;
  begin
    repeat
      write('Give a positive number (0 = quit): ');
      readln(i);
      assert(i >= 0)
    until i = 0
  end.
Het draaien van dit eerste programma geeft echter geen bestandsnaam of regelnummer te zien - wat we op basis van voorgaande alinea zouden verwachten - maar een Runtime error 227:
  Give a positive number (0 = quit): -3
  Runtime error 227 at 000039BA
De reden is dat Assertions intern slechts worden ondersteund door gebruik te maken van de SysUtils unit:
  program assert2;
  {$APPTYPE CONSOLE}
  {$C+}
  uses
    SysUtils;
  var
    i: Integer;
  begin
    repeat
      write('Give a positive number (0 = quit): ');
      readln(i);
      assert(i >= 0)
    until i = 0
  end.
En nu zullen we wel zien wat er waar er precies mis gaat als we het getal -3 invoeren:
  Give a positive number (0 = quit): -3
  Exception EAssertionFailed in module ASSERT2.EXE at 0000769A.
  Assertion failure (C:\USR\BOB\MAGAZINE\ASSERT\ASSERT2.DPR, line 14).
Om nog meer invloed uit te kunnen oefenen op het resultaat van een Assert, kunnen we gebruik maken van het optionele tweede argument:
  program assert3;
  {$APPTYPE CONSOLE}
  {$C+}
  uses
    SysUtils;
  var
    i: Integer;
  begin
    repeat
      write('Give a positive number (0 = quit): ');
      readln(i);
      assert(i >= 0,Format('Negative number %d entered',[i]))
    until i = 0
  end.
Nu krijgen we een zeer informatieve melding die ons verteld welk source bestand, welke regel, en welke fout (en zelfs welke ingevoerde waarde) ten tijde van de opgemerkt fout aanwezig waren.
  Give a positive number (0 = quit): -3
  Exception EAssertionFailed in module ASSERT3.EXE at 000076C5.
  Negative number -3 entered (C:\USR\BOB\MAGAZINE\ASSERT\ASSERT3.DPR, line 14).
Uit deze voorbeelden blijkt dat een "assert" - indien hij afgevuurt wordt - in feite een exception genereert die we natuurlijk weer met try-except kunnen afvangen. Het komt er echter op neer dat we met assertions het mechanisme van foutdetectie nog verder kunnen verfijnen, waar exceptions al voor de gehele foutdetectie, foutafhandeling en het algoritme in een soort raamwerk voorzag. Het uiteindelijk algoritme hoeft dus steeds minder doorspekt te worden met foutdetectie en foutafhandelingscode, en zal korter, overzichtelijker en daardoor ook vaak beter onderhoudbaar worden.

Met of Zonder?
Assertions kunnen op twee manier gebruikt worden: als debug middel, of als operationeel produktiemiddel. Als debug middel zijn ze het meest waardevol door ze bijvoorbeeld op te nemen in iedere funktie of procedure aanroep, en dan door "assert" aanroepen de waarden van de parameters te testen. Dit is met name handig tijdens de ontwikkeling van een toepassing, waarbij het nog wel eens wil voorkomen dat (voorlopige) testwaarden worden doorgegeven aan routines die eigenlijk niet voldoen aan een geldig bereik. Een assertion kan dan helpen om dergelijke plaatsen in het "programma-in-ontwikkeling" snel op te sporen en aan te passen. Assertions die op deze manier gebruikt worden zijn eigenlijk alleen nutig tijdens de ontwikkeling van een toepassing, voor het detecteren van iets dat totaal mis is en " in het echt" nooit zou mogen gebeuren, en zouden eigenlijk na afloop allemaal verwijderd moeten worden. Gelukkig bestaat de optie om alle aanroepen naar "assert" door de compiler te laten negeren. Dit betekent dat het mogelijk is de compiler zo in te stellen dat er totaal geen code wordt gegenereerd voor de aanroep van een "assert". De {$C-} compiler optie moeten we hiervoor gebruiken (merk op dat ik de drie voorbeeldprogramma ik steeds {$C+} heb gebruikt om expliciet aan te geven dat we wel code willen laten genereren voor de assertions in ons programma). Behalve deze zgn. "Debug Assertions", gebeurt het echter ook vaak dat iemand de aanroep van een assertion gebruikt om iets te controleren dat "in het echt" best wel eens zou kunnen gebeuren, zoals het niet bestaan van een bestand dat gekopieerd moet worden. Natuurlijk staat het vrij om "assert" ook in deze "operationele" situaties te gebruiken. Echter, het is vrijwel onmogelijk om deze twee vormen van "assert" te mixen. Ga maar na: zodra we de "debug asserts" zouden willen verwijderen uit onze toepassing (door met {$C-} te compileren) zijn we in een klap ook al onze "operationele asserts" kwijt. En dat kan weer leiden tot onverwachtte situaties, zeker aangezien de programmeur niet beter weet dan dat de controle nog steeds in het programma zit (in de vorm van een assert, waar echter geen machinecode meer voor wordt gegenereerd).

Assert Mix
De enige uitzondering op deze regel wordt voorzien door het feit dat de {$C+} en {$C-} compiler opties slecht lokaal binnen een source code bestand werken (maar wel voor het hele bestand). Dus het is mogelijk om van bestand tot bestand de "asserts" uit te zetten, en zelfs in sommige bestanden de "debug asserts" te gebruiken (en uit te zetten in de uiteindelijke versie van het programma) en in het hoofdprogramma en units de "operationele asserts" te gebruiken die dan met {$C+} in het uiteindelijke programma moeten worden gelaten.

Conclusie
Assertions bieden ons een handvat om het mechanisme van foutdetectie nog verder kunnen verfijnen. We kunnen assertions op twee verschillende manieren gebruiken, waarbij het gevaar bestaat dat deze werkwijze door elkaar kan gebeuren. Het is belangrijk hier in ieder geval op source code bestandsniveau strikt in te zijn. Het blijft even nadenken, maar zeker tijdens de ontwerp/bouw/test fase van onderdelen en gehele toepassingen kunnen assertions hun waarde dan - bij bewust en eenduidig gebruik - absoluut bewijzen.
Mocht iemand nog vragen, opmerkingen of suggesties hebben, dan hoor ik die het liefst via .


Dit artikel is eerder verschenen in SDGN Magazine #46 - februari 1998

This webpage © 1998-2006 by webmaster drs. Robert E. Swart (aka - www.drbob42.com). All Rights Reserved.