Titel | Inhalt | Suchen | Index | DOC | Handbuch der Java-Programmierung, 3. Auflage |
<< | < | > | >> | API | Kapitel 6 - Anweisungen |
Die Ausführungen in diesem Abschnitt können beim ersten Lesen übersprungen werden. Bei der assert-Anweisung handelt es sich um ein weiterführendes Konzept, das seit dem JDK 1.4 zur Verfügung steht. Neben einem grundlegenden Verständnis von Klassen und Methoden (ab Kapitel 7) setzt es insbesondere Kenntnisse über das Auslösen und Behandeln von Ausnahmen voraus, die erst in Kapitel 12 vermittelt werden. Aus systematischen Gründen soll das Thema jedoch an dieser Stelle behandelt werden. |
|
assert ausdruck1 [ : ausdruck2 ] ; |
Seit dem JDK 1.4 gibt es in Java eine assert-Anweisung. Diese, ursprünglich schon für die erste JDK-Version vorgesehene, dann aber aus Zeitgründen zurückgestellte Anweisung wird C- oder C++-Programmierern wohlbekannt sein. Sie dient dazu, an einer bestimmten Stelle im Programmablauf einen logischen Ausdruck zu plazieren, von dem der Programmierer annimmt, daß er stets wahr ist. Ist das der Fall, fährt das Programm mit der nächsten Anweisung fort, andernfalls wird eine Ausnahme des Typs AssertionError ausgelöst. Ein solcher Ausdruck wird auch als Invariante bezeichnet, denn er bleibt unverändert true, solange das Programm korrekt funktioniert. |
|
Assertions, wie assert-Anweisungen
vereinfachend genannt werden, dienen also dazu, bestimmte Annahmen
über den Zustand des Programmes zu verifizieren und sicherzustellen,
daß diese eingehalten werden. Soll im Programm beispielsweise
überprüft werden, ob eine Variable x
nicht-negativ ist, könnte dazu die folgende assert-Anweisung
verwendet werden:
assert x >= 0;
Das Programm überprüft die Bedingung und fährt fort, wenn sie erfüllt ist. Andernfalls wird eine Ausnahme ausgelöst. Natürlich hätte derselbe Test auch mit Hilfe einer einfachen if-Anweisung ausgeführt werden können. Die assert-Anweisung hat ihr gegenüber jedoch einige Vorteile:
Der im Syntaxdiagramm angegebene ausdruck1 muß immer vom Typ boolean sein, andernfalls gibt es einen Compilerfehler. Fehlt ausdruck2 (er darf von beliebigem Typ sein), wird im Falle des Nichterfülltseins der Bedingung ein AssertionError mit einer leeren Fehlermeldung erzeugt. Wurde ausdruck2 dagegen angegeben, wird er im Fehlerfall an den Konstruktor von AssertionError übergeben und dient als Meldungstext für das Fehlerobjekt.
Aus Kompatibilität zu älteren JDK-Versionen sind Assertions sowohl im Compiler als auch im Interpreter standardmäßig deaktiviert. Um einen Quellcode zu übersetzen, der Assertions enthält, kann dem Java-Compiler ab der Version 1.4 die Option -source 1.4 übergeben werden. Wird dies nicht gemacht, gibt es eine Fehlermeldung, nach der "assert" ab der Version 1.4 ein Schlüsselwort ist und nicht mehr als Bezeichner verwendet werden darf. Ältere Compilerversionen melden einen Syntaxfehler. Wir wollen uns ein einfaches Beispielprogramm ansehen:
001 /* AssertionTest.java */ 002 003 public class AssertionTest 004 { 005 public static void main(String[] args) 006 { 007 assert args.length >= 2; 008 int i1 = Integer.parseInt(args[0]); 009 int i2 = Integer.parseInt(args[1]); 010 assert i2 != 0 : "Teilen durch 0 nicht moeglich"; 011 System.out.println(i1 + "/" + i2 + "=" + (i1/i2)); 012 } 013 } |
AssertionTest.java |
Das Beispielprogramm verwendet zwei Assertions, um sicherzustellen,
daß mindestens zwei Kommandozeilenargumente übergeben werden
und daß das zweite von ihnen nicht 0 ist. Es kann mit folgendem
Kommando übersetzt werden, wenn der Compiler mindestens die Version
1.4 hat:
javac -source 1.4 AssertionTest.java
Um das Programm laufen zu lassen, kennt der Java-Interpreter ab der Version 1.4 die Kommandozeilenoptionen -enableassertions und -disableassertions, die mit -ea bzw. -da abgekürzt werden können. Ihre Syntax lautet:
java [ -enableassertions | -ea ] [:PaketName... | :KlassenName ] java [ -disableassertions | -da ] [:PaketName... | :KlassenName ] |
Werden die Optionen ohne nachfolgenden Paket- oder Klassennamen angegeben, werden die Assertions für alle Klassen mit Ausnahme der Systemklassen java.* an- bzw. ausgeschaltet. Wird, durch einen Doppelpunkt getrennt, ein Klassenname angegeben, gilt die jeweilige Option nur für diese Klasse. Wird ein Paketname angegeben (von einem Klassennamen durch drei angehängte Punkte zu unterscheiden), erstreckt sich die Option auf alle Klassen innerhalb des angegebenen Pakets. Die Optionen können mehrfach angegeben werden, sie werden der Reihe nach ausgewertet. Wird keine dieser Optionen angegeben, sind die Assertions deaktiviert.
Soll unser Beispielprogramm mit aktivierten Assertions ausgeführt
werden, kann also folgendes Kommando verwendet werden:
java -ea AssertionTest
In diesem Fall gibt es sofort eine Fehlermeldung, denn die erste assert-Anweisung
ist nicht erfüllt. Rufen wir das Programm mit zwei Zahlen als
Argumente auf, wird erwartungsgemäß deren Quotient berechnet:
java -ea AssertionTest 100 25
Die Ausgabe lautet:
100/25=4
Wenn das zweite Argument dagegen 0 ist, gibt es eine Fehlermeldung,
weil die zweite Assertion nicht erfüllt ist. Auch in diesem Fall
steigt das Programm mit einem AssertionError
aus, der zusätzlich die Fehlermeldung »Teilen durch 0 nicht
moeglich« enthält, die wir nach dem Doppelpunkt in Zeile 010
angegeben haben:
Exception in thread "main" java.lang.AssertionError:
Teilen durch 0 nicht moeglich
...
Wird das Programm mit deaktivierten Assertions aufgerufen, verhält es sich, als wären die Zeilen 007 und 010 gar nicht vorhanden. In diesem Fall gibt es die üblichen Laufzeitfehler, die bei einem Zugriff auf ein nicht vorhandenes Array-Element oder die Division durch 0 entstehen.
Genau genommen war das vorige Programm kein wirklich gutes Beispiel für die Anwendung von Assertions. Das Überprüfen der an ein Programm übergebenen Kommandozeilenparameter sollte nämlich besser einer IllegalArgumentException überlassen werden:
001 public class Listing0608 002 { 003 public static void main(String[] args) 004 { 005 if (args.length < 2) { 006 throw new IllegalArgumentException(); 007 } 008 ... 009 } 010 } |
Genau wie bei der Übergabe eines Arguments an eine öffentliche Methode sollte es nicht einfach möglich sein, deren Überprüfung zur Laufzeit abzuschalten. Da ein Programm bei der Übergabe von Werten an öffentliche Schnittstellen keinerlei Annahmen darüber machen kann, ob diese Werte korrekt sind, sollte die Überprüfung dieser Werte immer aktiv sein. Die Verwendung von Assertions empfiehlt sich also in diesem Fall nicht. Weitere Beispiele für derartige öffentliche Schnittstellen sind etwa die Daten, die über eine grafische Benutzerschnittstelle in ein Programm gelangen oder die aus Dateien oder über Netzwerkverbindungen eingelesen werden. In all diesen Fällen sollten nicht-abschaltbare Fehlerprüfungen anstelle von Assertions verwendet werden.
Der Einsatz von Assertions ist dagegen immer dann sinnvoll, wenn Daten aus unbekannten Quellen bereits verifiziert sind. Wenn also das Nichterfülltsein einer Assertion einen Programmfehler anzeigt und nicht fehlerhafte Eingabedaten. Beispiele sind:
Sowohl Pre- als auch Postconditions wurden mit der Programmiersprache Eiffel, die Bertrand Meyer in seinem Buch »Object-oriented Software Construction« 1988 vorgestellt hat, einer breiteren Öffentlichkeit bekannt. Das im JDK 1.4 implementierte Assertion-Konzept entspricht allerdings nicht in allen Aspekten Meyer's ausgefeiltem »Programming by Contract«. Dort gibt es beispielsweise explizite Schlüsselwörter für Pre- und Postconditions, und es ist möglich, in Postconditions auf den »alten« Wert eines Parameters zuzugreifen (also auf den, den er beim Eintritt in die Methode hatte). |
|
Dennoch stellen Assertions ein wichtiges Hilfsmittel dar, um Programme zuverlässiger und besser lesbar zu machen. Das folgende Programm zeigt verschiedene Anwendungen von Assertions am Beispiel einer einfachen Klasse zur Speicherung von Listen von Ganzzahlen:
001 public class SimpleIntList 002 { 003 private int[] data; 004 private int len; 005 006 public SimpleIntList(int size) 007 { 008 this.data = new int[size]; 009 this.len = 0; 010 } 011 012 public void add(int value) 013 { 014 //Precondition als RuntimeException 015 if (full()) { 016 throw new RuntimeException("Liste voll"); 017 } 018 //Implementierung 019 data[len++] = value; 020 //Postcondition 021 assert !empty(); 022 } 023 024 public void bubblesort() 025 { 026 if (!empty()) { 027 int cnt = 0; 028 while (true) { 029 //Schleifeninvariante 030 assert cnt++ < len: "Zu viele Iterationen"; 031 //Implementierung... 032 boolean sorted = true; 033 for (int i = 1; i < len; ++i) { 034 if (sortTwoElements(i - 1, i)) { 035 sorted = false; 036 } 037 } 038 if (sorted) { 039 break; 040 } 041 } 042 } 043 } 044 045 public boolean empty() 046 { 047 return len <= 0; 048 } 049 050 public boolean full() 051 { 052 return len >= data.length; 053 } 054 055 private boolean sortTwoElements(int pos1, int pos2) 056 { 057 //Private Preconditions 058 assert (pos1 >= 0 && pos1 < len); 059 assert (pos2 >= 0 && pos2 < len); 060 //Implementierung... 061 boolean ret = false; 062 if (data[pos1] > data[pos2]) { 063 int tmp = data[pos1]; 064 data[pos1] = data[pos2]; 065 data[pos2] = tmp; 066 ret = true; 067 } 068 //Postcondition 069 assert data[pos1] <= data[pos2] : "Sortierfehler"; 070 return ret; 071 } 072 } |
SimpleIntList.java |
Precondition-Assertions sind in den Zeilen 058 und 059 zu sehen. Sie stellen sicher, daß die Methode mit gültigen Array-Indizes aufgerufen wird. Eine Precondition, die als RuntimeException implementiert wurde, findet sich in Zeile 015 und prüft, ob die Liste vor dem Einfügen eines weiteren Elements bereits voll ist. Während die Postcondition in Zeile 021 eher formaler Natur ist, überprüft jene in Zeile 069, ob das Sortieren der Elemente tatsächlich erfolgreich war. Die Schleifeninvariante in Zeile 030 stellt sicher, daß der Bubblesort nicht zu viele Arraydurchläufe macht, was ein Indiz dafür wäre, daß er in eine Endlosschleife geraten wäre.
Normalerweise sollten die Ausdrücke in Assertions keine Nebeneffekte enthalten. Also keinen Code, der Variablen verändert oder den Status des Programmes auf andere Weise modifiziert. Der Grund dafür ist, daß diese Nebeneffekte bei abgeschalteten Assertions natürlich nicht ausgeführt werden. Das Programm würde sich also anders verhalten, als wenn die Assertions angeschaltet wären. Dadurch können sehr schwer zu findende Fehler entstehen. Generell gilt, daß die Funktionalität des Programms nicht davon abhängen sollte, ob die Assertions an- oder abgeschaltet sind. |
|
Allerdings kann es berechtigte Ausnahmen geben. Ein Gegenbeispiel liefert etwa das vorige Listing in Zeile 030. Hier wird innerhalb der Assertion die Zählervariable cnt inkrementiert, also eine Variable verändert. Das ist allerdings unkritisch, denn diese wird auch nur innerhalb der Assertion benötigt, spielt ansonsten im Programm aber keine Rolle. Variablen, die auch an anderen Stellen im Programm verwendet werden, sollten allerdings nicht innerhalb von Assertions verändert werden.
Wie schon erwähnt, gibt es Assertions erst seit der Version 1.4 des JDK. Ihre Verwendung wirft leider einige Kompatibilitätsprobleme auf:
Was folgt daraus? Assertions lassen sich nur dann sinnvoll einsetzen, wenn der Entwickler einen JDK-1.4.Compiler besitzt und davon ausgehen kann, daß auf allen Zielsystemen ein 1.4-kompatibles Laufzeitsystem vorhanden ist. Letzteres ist - zumindest zum Zeitpunkt, an dem diese Zeilen geschrieben werden - leider nicht unbedingt der Fall. Der Reiz von Java liegt ja gerade in seiner Plattformunabhängigkeit, und bis auf allen wichtigen Zielsystemen ein JDK-1.4-Laufzeitsystem zur Verfügung stehen wird, könnte es noch einige Zeit dauern. Wegen der zur Installation eines neueren JDKs erforderlichen Betriebssystempatches auf UNIX-Systemen sind heute - mehr als zwei Jahre nach der Veröffentlichung des JDK 1.3 - immer noch viele UNIX-Systeme lediglich mit einem 1.2er JDK ausgestattet oder wurden gerade erst auf 1.3 umgestellt. Vermutlich wird es noch einige Zeit dauern, bis das JDK 1.4 flächendeckend verfügbar sein wird.
Fazit: Assertions sind ein ausgezeichnetes Mittel, um Code lesbarer und robuster zu gestalten. Während der Debugphase helfen sie bei der Fehlersuche. Sofern keine Kompatibilität zu älteren JDK-Versionen erforderlich ist, sollten sie daher unbedingt verwendet werden. Kann dagegen nicht sichergestellt werden, daß die Zielsysteme mindestens das JDK 1.4 unterstützen, können Assertions nicht verwendet werden.
Titel | Inhalt | Suchen | Index | DOC | Handbuch der Java-Programmierung, 3. Auflage, Addison Wesley, Version 3.0.1 |
<< | < | > | >> | API | © 1998-2003 Guido Krüger, http://www.javabuch.de |