11.2 Assertions

 

 

Assertions (engl. Versicherung, Zusage) erlauben den Entwicklern für Testzwecke bestimmte Randbedingungen zu prüfen die immer erfüllt sein sollen.

Assertions geben dem Entwickler die Möglichkeit logische Bedingungen für die folgenden Fälle zu Programmieren:

  • Interne Invarianten
  • Invarianten im Kontrollfluß
  • Vorbedingungen, Nachbedingungen, Klasseninvarianten

Durch die Implementierung dieser Invarianten kann der Entwickler die Qualität seiner Implementierung erhöhen, da Bedingungen geprüft werden können die nie verletzt sein sollen.

Der Unterschied zu Ausnahmen (Exceptions) besteht darin, dass Assertions immer gelten sollten und diese daher nicht im Normalfall kontrolliert werden müssen, da sie den Programmablauf nur unnötig verlangsamen würden. Assertions haben eine große Ähnlichkeit mit Ausnahmen sie dienen jedoch unterschiedlichen Zwecken:

Vergleich Assertions und Ausnahmen
  Assertion Ausnahme (Exception)
Einsatzbereich Nur wenn die Logik des Programm in den Augen des Entwicklers verletzt wird Jederzeit da sie Teil der regulären Ablaufsteuerung sind
Auswirkungen bei der normalen Programmausführung Keine. Sie werden nicht geprüft! Können immer Auftreten.
Sichtbarkeit für Endanwender Nie: Wenn das Programm nicht explizit mit entsprechenden Optionen (-ea) gestartet wurde Nur wenn sie nicht abgefangen und behandelt werden
Zielgruppe Helfen dem Entwickler bei der Fehlersuche (auch beim Endanwender)

Zusammenarbeit der Entwickler: Teil der regulären externen Spezifikation von Klassen

Endanwender: Klassifikationschema für Fehlermeldungen bei Programmabbrüchen (Unbehandelte Ausnahmen)

Theoretischer Hintergrund Erlauben das Implementieren von Invarianten oder Vor- und Nachbedingungen von Schleifen und Routinen

Elegantes Konstrukt zum Verlassen von Blöcken zur Behandlung von seltenen Ereignissen.

Implementierter Code wird übersichtlicher da man nicht bei jeder Operation einzeln Sonderfäller prüfen muss

Assertions bieten die folgenden Vorteile für den Entwickler:

  • Der Entwickler kann Annahmen von denen er ausgeht als logische Ausdrücke implementieren und ist nicht auf Kommentare angewiesen.
  • Die Konsistenzprüfungen werden im Normalfall nicht abgearbeitet und produzieren daher keinerlei Laufzeitkosten für den Anwender
  • Sie geben dem Entwickler die Möglichkeit auch nach der Auslieferung seiner Anwendung zusätzliche Informationen durch Einschalten des Assertionchecking zu erhalten.
    • Hintergrund: Bei C und C++ Anwendungen werden bei Kundenproblemen oft spezielle Programme mit extra Debuginformationen ausgeliefert. Dies ist bei Java nicht nötig

Notation

Einfache Variante einer Assertion:

assert Ausdruck1;

Der Ausdruck Audruck1 wird ausgewertet. Hat das Ergebnis den Wert true (wahr) so ist die Zusage wahr und das Programm weiter ausführt. Trifft die Zusage (Assertion) nicht zu wird das Programm mit einem AssertionError abgebrochen (falls es den entspechenden Optionen zum Checken der Assertions aufgerufen wurde).

Eine zweite Syntaxvariante ist:

assert Ausdruck1: Ausdruck2;

Hier fährt das Programm wie im Fall zuvor mit der Ausführung fort wenn Ausdruck1 wahr ist. Ist Ausdruck1 jedoch unwahr wird Ausdruck2 ausgewertet und der entsprechenden Instanz von AssertionError als Parameter mitgegeben und dann in der Fehlermeldung mit ausgegeben. Dieser Rückgabewert unterstützt den Entwickler bei der Analyse des aufgetretenen Fehlerfalls. 

Einschalten der Prüfungen im Laufzeitsystem

Das Prüfen von Assertions kann beim Starten einer Javaanwendung mit den Optionen -ea bzw. -enableassertions eingeschaltet werden oder mit der Option -da bzw. -disableassertions ausgeschaltet werden. Die Option wird vor dem Klassennamen dessen main() Methode gestartet werden soll angegeben:

java -ea Klassenname1
java -ea paketname1... Klassenname1
java -ea paketname1.Klassename2 Klassenname1

java -da Klassenname1
java -da paketname1... Klassenname1 java -da paketname1.Klassename2 Klassenname1java 

 Wichtig: Die Notation mit den drei Punkten  paketname1... ist teil der Aufrufsyntax. Mit ihr werden die Assertions für alle Klassen in einem Paket angeschaltet.

Anwendungsbeispiele

Überprüfen korrekter Wertebereiche

Prüfen des Personenalters bei Rentenberechnungen

assert ((personenAlter>0) && (personenAlter<150)); 
assert (rentenEintrittsAlter>0); 

Die gleichen Assertions in der Variante mit einer Ausgabe für die Konsole

assert ((personenAlter>0) && (personenAlter<150)): personenAlter; 
assert (rentenEintrittsAlter>0): "negatives Renteneintrittsalter "+ rentenEintrittsAlter;  

Das Kontrollieren des Werts eines Monats:

int monat;
...
switch (monat)
{
  case 1: case 2: case 3: System.out.println("Q1");
      break;
  case 4: case 5: case 6: System.out.println("Q2");
      break;
  case 7: case 8: case 9: System.out.println("Q3");
      break;
  case 10: case 11: case 12: System.out.println("Q4");
      break;
  default: assert false;
}

Prüfen eines Kontrollflusses

In einen Programm soll eine der Bedingungen Ausdruck1 oder Ausdruck2 immer erfüllt sein.

void testMethode()
{
   for (int k = 0; k<= 99; k++)
   {
      if (k == 50)
         return;
   }
   assert false;
}

Die Assertion kann Prüfen ob ein Fehlerfall vorliegt.

Geschichtlicher Hintergrund

Assertions wurden in Java durch JSR 42 (A Simple Assertion Facility) in JDK 1.4 eingeführt. Dies führt zu einem gewissen Kompatiblitätsproblem:

  • Quellcode der das Schlüsselwort assert als normalen Bezeichner in JDK 1.3 verwendete wird in JDK 1.4 nicht übersetzen da das Schlüsselwort nicht als Namen akzeptiert wird
  • Quellcode der für JDK 1.4 geschrieben wurde wird nicht unter JDK 1.3 übersetzen, da javac in JDK 1.3 nicht der Syntax von assertions umgehen kann.