In diesem Artikel geht es darum, dem ausliefern von Bugs mittels Assertions vorzubeugen. Ich denke mal, die meissten von Euch wissen, das es Assertions gibt und für was die sind, hier trotzdem ein kleiner Crashkurs.

Was ist eine Assertion?

Eine Assertion ist eine Prüfung auf eine unmgliche Situation. Sie gehen etwas in die Richtung von Exceptions. Mit einer Assertion könnt Ihr eine gewisse Bedingung prüfen und gegebenenfalls eine Fehlermeldung (EAssert-Exception) ausgeben, wo die Unit und die Codezeile darin angezeigt wird. Hier der Syntax in Delphi:

Assert(Bedingung, [Fehlertext]);

Die Assertions lassen sich auch mittels Kompilerdirektive oder in den Projektoptionen (Tab "Compiler") ausschalten / deaktivieren.

Bug-Prevention mittels Assertions

So, nach dem Crashkurs nun die Idee des Themas. Also, es gibt immer wieder stellen im Programmcode, wo Ihr denkt: "Hmm... naja, also dieser Fall kann eh nicht eintreffen - es ist nicht mglich, ich brauch das nicht zu berücksichtigen". Es handelt sich also um eine "unmögliche Situation". Wenn Ihr sowas denkt, dann sagt das auch dem Compiler. Nur so kann dieser Euch bei der Fehlersuche helfen was Euch widerum einiges an Zeit sparen kann.

Ein kleines Beispiel anhand des CASE-Statements:

Das folgende Beispiel definiert einen normalen Aufzählungstyp, welcher in einem CASE abgefragt wird. Wichtig ist hierbei der ELSE. Auch wenn es theoretisch nicht passiert darf, das Euer Programm in den ELSE fliegt, "sagt" es dem Compiler, das Ihr nicht damit rechnet.

type 
  TStatus = (stActive, stInactive); 

procedure Check(AStatus: TStatus); 
begin 
  case AStatus of 
    stActive : { Active }; 
    stInactive : { Inactive }; 
    else Assert(false, 'Unknown Statuscode - check CASE-Statement!'); 
  end; 
end;

Stellt Euch vor, unser Programm wächst (soll's ja geben) und wir erweitern den Typ TStatus:

type 
  TStatus = (stActive, stInactive, stPaused); 

procedure Check(AStatus: TStatus); 
begin 
  case AStatus of 
    stActive : { Active }; 
    stInactive : { Inactive }; 
    else Assert(false, 'Unknown Statuscode - check CASE-Statement!'); 
  end; 
end;

Dummerweise haben wir jetzt aber eine Stelle im bestehenden Code nicht um den neuen Status erweitert - in diesem Fall die Check() Methode.

Hätten wir in diesem Beispiel den ELSE nicht, dann würden wir erst einiges später rausfinden, das da im Check() noch ein Bug drin ist, da wir stPaused vergessen haben. Eine längere Debugging-Session wird sich dann aufdrängen.

Mittels des Assert's hier, wird das Programm uns aber beim ersten Ausfhren genau sagen, was das Problem ist (inkl. Unit und Zeilennummer).

Parameter validieren

In eine ähndliche Richtung geht das überprüfen von Funktions-/Methodenparamtern. Wenn Ihr einer Funktion Parameter übergebt, auf welche Ihr angewiesen seit bzw. welche gewisse Kriterien erfüllen sollte, wenn Sie übergeben werden, dann prüft diese auch. Wieder ein kleines Beispiel:

function DoSomething(AText: string); 
begin 
  Assert(AText  '', 'Parameter AText can not be empty!'); 
  { Do something  } 
end;

Es kostet Euch keine Zeit die Zeile reinzuhauen, aber wenn's mal wirklich zum Problem werden sollte, dann erspart Ihr auch schnell mal eine Stunde debugen. Verwendet Ihr öfters Assertions, erkennt ihr Fehlersituationen (Bugs) früher - im optimalen Fall da wo sie entstehen. Dies erspart Euch langes debuggen.

Es gäbe noch viele Beispiele mehr, aber ich denke Ihr seht, was die Idee dahinter ist.

Assertion's vs. Exception's

Es kommt vermutlich schnell eine gewisse paralelle zu den Exceptions auf. Assertions sind genau betrachtet sogar Exceptions - eine spezielle Art von Exceptions. Sie zeigen Sie Euch bereits bei der Fehlermeldung die Unit und die Zeilennummer an, was schon eine feine Sache ist. Kommt hinzu, dass Ihr Assertion's beim Build ausschalten könnt. Ihr könnt also z.B. für das Shiping ein Kompilat mit deaktivierten Assertions machen, und für intern einen "Debug-Build" mit Assertions. Wenn Ihr die Assertions deaktiviert, wird auch kein Code dafür reinkompiliert bzw. gelinkt. Bei den Exceptions müsstet Ihr hier bereits mit $IFDEF's arbeiten.

Meine persöndliche Meinung: um solche "unmöglichen Situationen" abzuchecken, sind Assertions genau das Richtige. Wenn hingegen reguläre Fehler auftreten, welche auch - je nach System - auftreten dürfen, dann sind Exceptions angebrachter. Kurz: Fehler, die der User eigentlich nie zu sehen bekommen sollte, sind bei mir Assertions. Dann ist sofort klar, dass wenn eine Assertions gemolden wird, etwas im Sourcecode faul ist. Ich gehe sogar soweit das wenn ich einen Bugreport lesen, in welchem von einer Assertion berichtet wird, ich das als "Bug" deklariere - da muss dann was faul sein, sonst würde keine Assertion kommen.