Umfangreiche Testmanagement Lösung für komplexe Herausforderungen.
Try-catch und Assertions in Java
Sie haben Ihren Code verfasst und sind der festen Überzeugung, dass dieser korrekt ist. Schließlich haben Sie alle möglichen Szenarien detailliert im Kopf durchgespielt und auch der Compiler zeigt sich erst einmal mit Ihrem Code zufrieden. Eine unschöne Überraschung lässt häufig nicht lange auf sich warten, wenn das Programm nach wenigen Augenblicken aufgrund einer Exception oder eines Error abstürzt. Eine bekannte Situation. Sie machen sich ernüchtert an das Debugging. Doch was ist der Unterschied zwischen einer Exception und einem Error? Und wann können Sie diese zu Ihrem Vorteil nutzen?
Exceptions und Errors
In Java sind Exceptions und Errors direkte Unterklassen der Klasse Throwable. Sie werden vom JVM instanziiert, sobald ein abnormaler Programmablauf registriert wird. Allerdings können Exceptions und Errors auch explizit vorprogrammiert sein.
public class ExceptionExperiments {
public static void main(String[] args) throws Exception {
throw new Exception();
}
}
Abb. 1 Dieses Programm stürzt aufgrund einer explizit gerufenen Exception ab.
Errors beschreiben schwerwiegende Ereignisse, welche die weitere Ausführung des Programms verhindern. Hierzu zählt z.B. der OutOfMemoryError oder der StackOverflowError. Exceptions sind hingegen unerwartete Situationen, mit denen das Programm konfrontiert wird. Wenn nicht entsprechend behandelt, führen Exceptions ebenfalls zum Programmabsturz. Häufig anzutreffende Exceptions sind z.B. die FileNotFoundException oder die IndexOutOfBoundException.
Behandlung von Exceptions
Um das Programm gegen potenziell auftretende Exceptions abzusichern, müssen diese behandelt werden. Durch die Behandlung wird das Programm darüber informiert, an welchen Stellen im Code eine Exception auftreten kann und wie in solchen Fällen vorzugehen ist. Eine Behandlung kann sogar verpflichtend sein, wenn sogenannte checked Exceptions auftreten. Wird beispielsweise ein Programm zum Öffnen und Auslesen einer Datei instruiert, ist davon auszugehen, dass diese Datei möglicherweise nicht existiert, was in einer FileNotFoundException resultiert. Dieser Fall muss zwingend behandelt werden, weshalb die FileNotFoundException eine checked Exception ist. Bevor eine checked Exception nicht behandelt wurde, kann der Code nicht kompiliert (und somit auch nicht ausgeführt) werden. Die ArithmeticException tritt immer dann auf, wenn durch Null dividiert wird. Sie kann, muss aber nicht behandelt werden, deshalb ist sie ein Beispiel der unchecked Exceptions. Ob eine unchecked Exception behandelt werden sollte, hängt oft von der jeweiligen Situation ab.
public class ExceptionExperiments {
public static void main(String[] args) {
uncheckedExceptionExample();
checkedExceptionExample();
}
public static void uncheckedExceptionExample() {
System.out.println(3/0);
}
public static void checkedExceptionExample() {
File file = new File("HelloWorld.txt");
Scanner myReader = new Scanner(file);
while (myReader.hasNextLine()) {
System.out.println(myReader.nextLine());
}
myReader.close();
}
}
Abb. 2 Die Methode uncheckedExceptionExample wird zu einer ArithmeticException führen, denn Division durch Null ist verboten. Allerdings kann der Code so nicht kompiliert werden. Die Methode checkedExceptionExample könnte eine FileNotFoundException verursachen und muss behandelt werden!
Behandlung von Exceptions durch try-catch(-finally)
Exceptions lassen sich effektiv durch das try-catch-Konstrukt behandeln. Dabei enthält die try-Klausel den Code, der eine Exception hervorrufen kann. In der catch-Klausel werden die abzufangenden Exceptions sowie weitere Anweisungen angegeben, die das Programm nach dem Abfangen der Exception befolgen soll. Optional kann eine finally-Klausel hinzugefügt werden, die immer nach der try- oder nach der catch-Klausel ausgeführt wird. Dies kann besonders hilfreich sein, wenn benutzte Ressourcen abschließend aufgeräumt, d. h. geschlossen, gespeichert und freigelassen, werden sollen.
public class ExceptionExperiments {
public static void main(String[] args) {
checkedExceptionExample();
}
public static void checkedExceptionExample() {
try {
File file = new File("HelloWorld.txt");
Scanner myReader = new Scanner(file);
while (myReader.hasNextLine()) {
System.out.println(myReader.nextLine());
}
myReader.close();
}
catch (FileNotFoundException fnfe) {
System.out.println("File does not exist.");
}
finally {
//Ggf. erforderliche Aufräumarbeiten
}
}
}
Abb. 3 Die checked Exception FileNotFoundException wird durch das try-catch-finally-Konstrukt behandelt. Tritt die Exception auf, so wird sie abgefangen und die Nachricht "File does not exist." wird in die Konsole ausgegeben.
Delegieren von Exceptions
Alternativ zu einem try-catch-Konstrukt können Exceptions weitergegeben werden. Die Voraussetzung ist, dass die Exceptions zusammen mit dem Schlüsselwort throws im Methodenkopf gekennzeichnet werden. Die Behandlung von Exceptions obliegt dann der Anrufer-Methode.
public class ExceptionExperiments {
public static void main(String[] args) {
try {
checkedExceptionExample();
}
catch (FileNotFoundException fnfe) {
System.out.println("File does not exist.");
}
finally {
//Ggf. erforderliche Aufräumarbeiten
}
}
public static void checkedExceptionExample() throws FileNotFoundException {
File file = new File("HelloWorld.txt");
Scanner myReader = new Scanner(file);
while (myReader.hasNextLine()) {
System.out.println(myReader.nextLine());
}
myReader.close();
}
}
Abb. 4 Die Exception FileNotFoundException wurde von der Methode checkedExceptionExample an die Anrufer-Methode main delegiert. Diese fängt die Exception mit einem try-catch-finally-Konstrukt ab.
Assertions
Mithilfe von Assertions lassen sich schnell Annahmen über die Programmlogik testen. Dabei wird das erwartete Verhalten in eine Boolean-Bedingung umformuliert, welche durch eine Assertion ausgewertet wird. Bei Diskrepanzen wird ein AssertionError generiert und das Programm abgebrochen. Aus diesem Grund eignen sich Assertions hervorragend für das Aufdecken von Bugs und sind ein unabdingbarer Bestandteil von Software-Tests. Der Error AssertionError gehört der Klasse Error an. Analog zu Exceptions können Errors in einem try-catch-Konstrukt abgefangen werden, um einen Programmabsturz zu verhindern. Jedoch weisen Errors in der Regel auf ernste Umstände hin, unter denen die weitere Ausführung des Programms nicht mehr möglich sein sollte. Daher wird von jeglichen Versuchen zur Behandlung des AssertionError sowie anderer Errors abgeraten. Es gilt als gute Praxis, nur eine Assertion pro Test zu implementieren. Der Test wird abgebrochen, sobald diese Assertion fehlschlägt. Wird die Assertion von weiterem Code gefolgt, wird dieser nicht ausgeführt. Diese Art von Assertion wird daher auch als Hard-Assertion bezeichnet.
public class AssertionExperiments {
int a = 2, b = 30;
@Test
public void testMultipleHardAssertions() {
Assert.assertEquals(a + b, 31);
Assert.assertEquals(a * b, 60);
}
Abb. 5 Der Test schlägt beim Auswerten der ersten Assertion fehl und wird abgebrochen. Die zweite Assertion wird nicht ausgewertet.
Wenn mehrere voneinander unabhängige Aspekte getestet werden sollen, kann dies mithilfe von sogenannten Soft-Assertions in einem einzigen Test bewerkstelligt werden. Soft-Assertions sind Bestandteil des Testing-Frameworks TestNG, der auf dem standardmäßig verwendeten JUnit aufbaut. Bei Verwendung von Soft-Assertions gilt der Test als endgültig bestanden oder fehlgeschlagen, erst wenn alle im Test befindlichen Soft-Assertions ausgewertet wurden.
Zusammenfassung
Mithilfe von try-catch-Konstrukten können Programme gegen Exceptions abgesichert und somit robuster gestaltet werden. Sie kommen daher gleichermaßen in der Entwicklung und Qualitätssicherung von Software zum Einsatz. Die Verwendung von try-catch-Konstrukten ist in vielen Fällen optional. Spätestens jedoch, wenn eine checked Exception aufzutreten droht, ist die Behandlung durch einen try-catch-Konstrukt erforderlich. Auch bei der Absicherung des Programms gegen unchecked Exceptions bieten try-catch-Konstrukte eine deutlich bessere Alternative zu unpraktischen und schlecht lesbaren if-Klauseln. Da Assertions ein Mittel zur Überprüfung des Programmverhaltens darstellen, werden sie überwiegend in Software-Tests verwendet. Sie generieren einen AssertionError, wenn Annahmen über das Programmverhalten nicht bestätigt werden. Assertions ersetzen unpraktische und schlecht lesbare if-Klauseln mit explizit vorprogrammierten Errors. Soft-Assertions ermöglichen das Testen mehrerer unabhängiger Aspekte in einem einzigen Test, sodass auf kontroverse Umwege über Hard-Assertions in try-catch-Konstrukten verzichtet werden kann.
try-catch | Hard-Assertion | Soft-Assertion | |
---|---|---|---|
Gebiet der Anwendung | Software-Entwicklung & Software-Tests | Software-Tests | Software-Tests |
Zweck | Behandelt Exceptions, verhindert Programmabbruch und erhöht die Robustheit. | Generiert einen Error, wenn getestete Annahme über Programmlogik nicht bestätigt wird. | Zur Prüfung mehrerer Annahmen über die Programmlogik in einem einzigen Test. Generiert einen Error, wenn mindestens eine Annahme nicht bestätigt wird. |
Ersetzt | if-Klauseln zum Absichern des Programms gegen unchecked Exceptions | if-Klauseln mit expliziten Errors in Software-Tests | Hard-Assertions in try-catch-Konstrukten in Software-Tests |