Verwendung von Zusicherungen in Java

Das Schreiben von Programmen, die zur Laufzeit korrekt funktionieren, kann eine Herausforderung sein. Dies liegt daran, dass unsere Annahmen darüber, wie sich unser Code bei der Ausführung verhält, häufig falsch sind. Die Verwendung der Assertions-Funktion von Java ist eine Möglichkeit, um zu überprüfen, ob Ihre Programmierlogik einwandfrei ist.

In diesem Tutorial werden Java-Zusicherungen vorgestellt. Sie lernen zunächst, was Zusicherungen sind und wie Sie sie in Ihrem Code angeben und verwenden. Als Nächstes erfahren Sie, wie Sie Assertions verwenden, um Vor- und Nachbedingungen durchzusetzen. Schließlich werden Sie Behauptungen mit Ausnahmen vergleichen und herausfinden, warum Sie beide in Ihrem Code benötigen.

download Code herunterladen Laden Sie den Quellcode herunter, um Beispiele in diesem Tutorial zu erhalten. Erstellt von Jeff Friesen für JavaWorld.

Was sind Java-Behauptungen?

Vor JDK 1.4 verwendeten Entwickler häufig Kommentare, um Annahmen zur Programmkorrektheit zu dokumentieren. Kommentare sind jedoch als Mechanismus zum Testen und Debuggen von Annahmen nutzlos. Der Compiler ignoriert Kommentare, daher gibt es keine Möglichkeit, sie zur Fehlererkennung zu verwenden. Entwickler aktualisieren Kommentare häufig nicht, wenn sie Code ändern.  

In JDK 1.4 wurden Assertions als neuer Mechanismus zum Testen und Debuggen von Annahmen über unseren Code eingeführt. Assertions  sind im Wesentlichen kompilierbare Entitäten, die zur Laufzeit ausgeführt werden, sofern Sie sie für Programmtests aktiviert haben. Sie können Zusicherungen programmieren, um Sie über Fehler zu informieren, bei denen die Fehler auftreten, und so die Zeit erheblich verkürzen, die Sie sonst für das Debuggen eines fehlerhaften Programms benötigen würden.

Assertions werden verwendet, um die Anforderungen zu kodifizieren, die ein Programm korrekt machen oder nicht, indem Bedingungen (Boolesche Ausdrücke) auf wahre Werte getestet und der Entwickler benachrichtigt werden, wenn solche Bedingungen falsch sind. Die Verwendung von Zusicherungen kann Ihr Vertrauen in die Richtigkeit Ihres Codes erheblich erhöhen.

So schreiben Sie eine Zusicherung in Java

Zusicherungen werden über die assertAnweisung und die java.lang.AssertionErrorKlasse implementiert . Diese Anweisung beginnt mit dem Schlüsselwort assertund wird mit einem booleschen Ausdruck fortgesetzt. Es wird syntaktisch wie folgt ausgedrückt:

assert BooleanExpr ;

Wenn der BooleanExprWert true ergibt, geschieht nichts und die Ausführung wird fortgesetzt. Wenn der Ausdruck jedoch als falsch ausgewertet wird, AssertionErrorwird er instanziiert und ausgelöst, wie in Listing 1 gezeigt.

Listing 1:AssertDemo.java (Version 1)

öffentliche Klasse AssertDemo {public static void main (String [] args) {int x = -1; behaupten x> = 0; }}

Die Behauptung in Listing 1 zeigt die Überzeugung des Entwicklers an, dass die Variable xeinen Wert enthält, der größer oder gleich 0 ist. Dies ist jedoch eindeutig nicht der Fall. assertDie Ausführung der Anweisung führt zu einem Wurf AssertionError.

Kompilieren Sie Listing 1 ( javac AssertDemo.java) und führen Sie es mit aktivierten Assertions aus ( java -ea AssertDemo). Sie sollten die folgende Ausgabe beachten:

Ausnahme im Thread "main" java.lang.AssertionError bei AssertDemo.main (AssertDemo.java:6)

Diese Nachricht ist insofern etwas kryptisch, als sie nicht identifiziert, was den Auslöser verursacht hat AssertionError. Wenn Sie eine informativere Nachricht wünschen, verwenden Sie die folgende assertErklärung:

assert BooleanExpr : expr ;

Hier exprist jeder Ausdruck (einschließlich eines Methodenaufrufs), der einen Wert zurückgeben kann. Sie können keine Methode mit einem voidRückgabetyp aufrufen . Ein nützlicher Ausdruck ist ein Zeichenfolgenliteral, das den Grund für den Fehler beschreibt, wie in Listing 2 gezeigt.

Listing 2:AssertDemo.java (Version 2)

öffentliche Klasse AssertDemo {public static void main (String [] args) {int x = -1; behaupten x> = 0: "x <0"; }}

Kompilieren Sie Listing 2 ( javac AssertDemo.java) und führen Sie es mit aktivierten Zusicherungen aus ( java -ea AssertDemo). Dieses Mal sollten Sie die folgende leicht erweiterte Ausgabe beobachten, die den Grund für das Auslösen enthält AssertionError:

Ausnahme im Thread "main" java.lang.AssertionError: x <0 bei AssertDemo.main (AssertDemo.java:6)

In beiden Fällen führt die Ausführung AssertDemoohne die -eaOption (Zusicherungen aktivieren) zu keiner Ausgabe. Wenn Zusicherungen nicht aktiviert sind, werden sie nicht ausgeführt, obwohl sie noch in der Klassendatei vorhanden sind.

Vor- und Nachbedingungen

Assertions testen die Annahmen eines Programms, indem sie überprüfen, ob die verschiedenen Vor- und Nachbedingungen nicht verletzt werden, und den Entwickler benachrichtigen, wenn ein Verstoß auftritt:

  • Eine Vorbedingung ist eine Bedingung, die vor der Ausführung einer Codesequenz als wahr ausgewertet werden muss. Voraussetzungen stellen sicher, dass Anrufer ihre Verträge mit Callees halten.
  • Eine Nachbedingung ist eine Bedingung, die nach Ausführung einer Codesequenz als wahr ausgewertet werden muss. Nachbedingungen stellen sicher, dass Callees ihre Verträge mit Anrufern halten.

Voraussetzungen

Sie können Voraussetzungen für öffentliche Konstruktoren und Methoden erzwingen, indem Sie explizite Überprüfungen durchführen und bei Bedarf Ausnahmen auslösen. Bei privaten Hilfsmethoden können Sie Voraussetzungen erzwingen, indem Sie Zusicherungen angeben. Betrachten Sie Listing 3.

Listing 3:AssertDemo.java (Version 3)

import java.io.FileInputStream; import java.io.InputStream; import java.io.IOException; Klasse PNG {/ ** * Erstellen Sie eine PNG-Instanz, lesen Sie die angegebene PNG-Datei und dekodieren Sie sie * in geeignete Strukturen. * * @param Dateispezifikationspfad und Name der zu lesenden PNG-Datei * * @throws NullPointerException when filespecis *null* / PNG (String filespec) löst eine IOException aus {// Erzwinge Voraussetzungen in nicht privaten Konstruktoren und // Methoden. if (filespec == null) löst eine neue NullPointerException aus ("filespec is null"); try (FileInputStream fis = neuer FileInputStream (Dateispezifikation)) {readHeader (fis); }} private void readHeader (InputStream is) löst eine IOException aus {// Bestätige, dass die Voraussetzung in privaten // Hilfsmethoden erfüllt ist. assert is! = null: "null übergeben an is"; }} öffentliche Klasse AssertDemo {public static void main (String [] args) löst IOException aus {PNG png = new PNG ((args.length == 0)? null: args [0]); }}

Die PNGKlasse in Listing 3 ist der minimale Anfang einer Bibliothek zum Lesen und Dekodieren von PNG-Bilddateien (Portable Network Graphics). Der Konstruktor vergleicht explizit filespecmit nullund wirft, NullPointerExceptionwenn dieser Parameter enthält null. Der Punkt ist, die Voraussetzung zu erzwingen, die filespecnicht enthält null.

Es ist nicht angebracht anzugeben, assert filespec != null;da die im Javadoc des Konstruktors erwähnte Voraussetzung (technisch) nicht berücksichtigt wird, wenn Zusicherungen deaktiviert werden. (In der Tat wäre es eine Ehre, weil FileInputStream()würde werfen NullPointerException, aber Sie sollten nicht auf undokumentiertes Verhalten angewiesen sein.)

Dies assertist jedoch im Zusammenhang mit der privaten readHeader()Hilfsmethode angemessen , die schließlich abgeschlossen wird, um den 8-Byte-Header einer PNG-Datei zu lesen und zu dekodieren. Die Voraussetzung, dass isimmer ein Wert ungleich Null übergeben wird, gilt immer.

Nachbedingungen

Nachbedingungen werden normalerweise über Zusicherungen angegeben, unabhängig davon, ob die Methode (oder der Konstruktor) öffentlich ist oder nicht. Betrachten Sie Listing 4.

Listing 4:AssertDemo.java (Version 4)

public class AssertDemo { public static void main(String[] args) { int[] array = { 20, 91, -6, 16, 0, 7, 51, 42, 3, 1 }; sort(array); for (int element: array) System.out.printf("%d ", element); System.out.println(); } private static boolean isSorted(int[] x) { for (int i = 0; i  x[i + 1]) return false; return true; } private static void sort(int[] x) { int j, a; // For all integer values except the leftmost value ... for (int i = 1; i  0 && x[j - 1] > a) { // Shift left value -- x[j - 1] -- one position to its right -- // x[j]. x[j] = x[j - 1]; // Update insert position to shifted value's original position // (one position to the left). j--; } // Insert a at insert position (which is either the initial insert // position or the final insert position), where a is greater than // or equal to all values to its left. x[j] = a; } assert isSorted(x): "array not sorted"; } }

Listing 4 presents a sort() helper method that uses the insertion sort algorithm to sort an array of integer values. I’ve used assert to check the postcondition of x being sorted before sort() returns to its caller.

The example in Listing 4 demonstrates an important characteristic of assertions, which is that they’re typically expensive to execute. For this reason, assertions are usually disabled in production code. In Listing 4, isSorted() must scan through the entire array, which can be time-consuming in the case of a lengthy array.

Assertions vs. exceptions in Java

Developers use assertions to document logically impossible situations and detect errors in their programming logic. At runtime, an enabled assertion alerts a developer to a logic error. The developer refactors the source code to fix the logic error and then recompiles this code.

Developers use Java’s exception mechanism to respond to non-fatal (e.g., running out of memory) runtime errors, which may be caused by environmental factors, such as a file not existing, or by poorly written code, such as an attempt to divide by 0. An exception handler is often written to gracefully recover from the error so that the program can continue to run.

Assertions are no substitute for exceptions. Unlike exceptions, assertions don’t support error recovery (assertions typically halt program execution immediately — AssertionError isn’t meant to be caught); they are often disabled in production code; and they typically don’t display user-friendly error messages (although this isn’t an issue with assert). It’s important to know when to use exceptions rather than assertions.

When to use exceptions

Suppose you’ve written a sqrt() method that calculates the square root of its argument. In a non-complex number context, it’s impossible to take the square root of a negative number. Therefore, you use an assertion to fail the method if the argument is negative. Consider the following code fragment:

public double sqrt(double x) { assert x >= 0 : "x is negative"; // ... }

It’s inappropriate to use an assertion to validate an argument in this public method. An assertion is intended to detect errors in programming logic and not to safeguard a method from erroneous arguments. Besides, if assertions are disabled, there is no way to deal with the problem of a negative argument. It’s better to throw an exception, as follows:

public double sqrt(double x) { if (x < 0) throw new IllegalArgumentException("x is negative"); // ... }

The developer might choose to have the program handle the illegal argument exception, or simply propagate it out of the program where an error message is displayed by the tool that runs the program. Upon reading the error message, the developer can fix whatever code led to the exception.

Möglicherweise haben Sie einen subtilen Unterschied zwischen der Behauptung und der Fehlererkennungslogik festgestellt. Die Assertionstests x >= 0, während die Fehlererkennungslogik testet x < 0. Die Behauptung ist optimistisch: Wir gehen davon aus, dass das Argument in Ordnung ist. Im Gegensatz dazu ist die Fehlererkennungslogik pessimistisch: Wir gehen davon aus, dass das Argument nicht in Ordnung ist. Zusicherungen dokumentieren die korrekte Logik, während Ausnahmen ein falsches Laufzeitverhalten dokumentieren.

In diesem Tutorial haben Sie gelernt, wie Sie mithilfe von Assertions die korrekte Programmlogik dokumentieren. Sie haben auch erfahren, warum Behauptungen keinen Ersatz für Ausnahmen darstellen, und Sie haben ein Beispiel gesehen, in dem die Verwendung einer Ausnahme effektiver wäre.

Diese Geschichte "Verwendung von Assertions in Java" wurde ursprünglich von JavaWorld veröffentlicht.