11.2 Assertions

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.
Stefan Schneider Wed, 01/19/2011 - 20:28

11.2.1 Übungen (Assertions)

11.2.1 Übungen (Assertions)

Übung 1: Einfügen von Assertions

Nutzen Sie das Beispiel aus dem Abschnitt zu Schnittstellen.

UML Diagramm Euro

Modifizieren Sie die Klasse Euro so, daß

  • Beim Setzen des Centbetrags eine Assertion geworfen wird falls der Centbetrag nicht im korrekten Wertebereich ist. Nutzen Sie die erweiterte Syntax um eine vernünftige Fehlermeldung auszugeben.
  • Melden  Sie beim Multiplizieren eines Eurobetrags einen Faktor von Null (0) mit Hilfe einer Assertion. Nutzen Sie die erweiterte Syntax um eine Fehlermeldung auszugeben.

Starten Sie das Programm mit der Klasse TestEuro: Es sollte wie zuvor funktionieren

Starten Sie die Klasse TestEuro so, daß Assertions beachtet werden.

  • Welche Option muß man beim Programmstart einfügen?
  • Modifizieren Sie das Testprogramm so, dass die Assertion für die Multiplikation ausgelöst wird. Der Fall einer Multiplikation mit Null wird im aktuellen Testprogramm nicht getestet.
Stefan Schneider Sat, 04/06/2013 - 17:13

11.2.2 Lösungen (Assertions)

11.2.2 Lösungen (Assertions)

Übung 1: Einfügen von Assertions

Klasse Euro

package s1.block11;
import static java.lang.Math.abs;
import static java.lang.Math.signum;
/**
 *
 * @author s@scalingbits.com
 * @version 1.1
 */
public class Euro extends Number implements Waehrung, Comparable{
    /**
     * Der gesamte Betrag wird intern in Cents verwaltet
     */
    public final long cents;
 
    public Euro(long euros, long cents) {
        assert ((cents>=0) && (cents < 101)): "Cents Bereichsverletzung";
        // Ignoriere Centsbetrag wenn er nicht im richtigen Intervall ist
        if ((cents<0) || (cents>=100))
            cents=0;
       this.cents = (abs(euros)*100+cents) *(long)signum(euros);
    }
    @Override
    public int intValue() {
        return (int)cents/100;
    }
    @Override
    public long longValue() {
        return cents/100;
    }
    @Override
    public float floatValue() {
        // Signum und Absolutwert sind notwendig
        // da -2.20= -(2 + 0.2) sind. Falsch: -2 + 0.2 ergibt -1.8!
        return (float)cents/100f;
    }
    @Override
    public double doubleValue() {
        // Signum und Absolutwert sind notwendig
        // da -2.20= -(2 + 0.2) sind. Falsch: -2 + 0.2 ergibt -1.8!
        return (double)cents/100d;
    }
    @Override
    public String symbol() {
        return "€";
    }
    @Override
    public String toString() {
        // Füge eine Null bei Centbeträgen zwischen 0 und 9 eine
        String leerstelle = ((abs(cents)%100)<10) ? "0" : "";
        return Long.toString(cents/100L) + "." + leerstelle +
                Long.toString(abs(cents%100L)) + symbol();
    }
    @Override
    public Waehrung mult(double d) {
        assert (d!=0): "Multplikation mit " + d + "nicht erlaubt";
        long temp;
        temp = (long)((double)cents *d);
        return new Euro(temp/100L,abs(temp%100L));
    }
    @Override
    public int compareTo(Object o) {
    int result;
        if (o instanceof Euro) {
            Euro e = (Euro) o;
            result = (int)(this.cents-e.cents);
        }
        else {result = -1;} // Alles was kein Euro ist, ist kleiner
    return result;
    }
}

 

Stefan Schneider Sat, 04/06/2013 - 17:13

11.3.3 Lernziele (Assertions)

11.3.3 Lernziele (Assertions)

Am Ende dieses Blocks können Sie:

  • ... mit Hilfe von Java-Assertions Invarianten der Anwendung implementieren
  • ... die Auswirkung von Annahmen (Assertions) auf die Wartbarkeit von Anwendungen beschreiben
  • ... Javaannahmen (Assertions) zur Laufzeit gezielt an und ausschalten
  • ... den Einsatz von Assertions (Annahmen) abwägen gegen die Verwendung von Ausnahmen (Exceptions)

Lernzielkontrolle

Sie sind in der Lage die folgenden Fragen zu beantworten: Fragen zu Annahmen (Assertions))

Stefan Schneider Wed, 01/09/2013 - 19:16