Come utilizzare le asserzioni in Java

Scrivere programmi che funzionano correttamente in fase di esecuzione può essere impegnativo. Questo perché le nostre ipotesi su come si comporterà il nostro codice una volta eseguito sono spesso sbagliate. L'utilizzo della funzionalità di asserzioni di Java è un modo per verificare che la logica di programmazione sia corretta.

Questo tutorial introduce le asserzioni Java. Per prima cosa imparerai cosa sono le asserzioni e come specificarle e usarle nel tuo codice. Successivamente, scoprirai come utilizzare le asserzioni per applicare precondizioni e postcondizioni. Infine, confronterai le asserzioni con le eccezioni e scoprirai perché hai bisogno di entrambe nel codice.

download Ottieni il codice Scarica il codice sorgente per gli esempi in questo tutorial. Creato da Jeff Friesen per JavaWorld.

Cosa sono le asserzioni Java?

Prima di JDK 1.4, gli sviluppatori spesso utilizzavano i commenti per documentare le ipotesi sulla correttezza del programma. Tuttavia, i commenti sono inutili come meccanismo per testare e eseguire il debug delle ipotesi. Il compilatore ignora i commenti, quindi non è possibile utilizzarli per il rilevamento dei bug. Inoltre, gli sviluppatori spesso non aggiornano i commenti durante la modifica del codice.  

In JDK 1.4, le asserzioni sono state introdotte come nuovo meccanismo per testare ed eseguire il debug di ipotesi sul nostro codice. In sostanza, le asserzioni  sono entità compilabili che vengono eseguite in fase di esecuzione, assumendo che tu le abbia abilitate per il test del programma. È possibile programmare asserzioni per notificare i bug in cui si verificano i bug, riducendo notevolmente la quantità di tempo che altrimenti si spenderebbe per il debug di un programma in errore.

Le asserzioni vengono utilizzate per codificare i requisiti che rendono un programma corretto o meno testando le condizioni (espressioni booleane) per i valori veri e notificando allo sviluppatore quando tali condizioni sono false. L'utilizzo delle asserzioni può aumentare notevolmente la fiducia nella correttezza del codice.

Come scrivere un'asserzione in Java

Le asserzioni vengono implementate tramite l' assertistruzione e la java.lang.AssertionErrorclasse. Questa istruzione inizia con la parola chiave asserte continua con un'espressione booleana. Si esprime sintatticamente come segue:

assert BooleanExpr ;

Se BooleanExprrestituisce true, non accade nulla e l'esecuzione continua. Se l'espressione restituisce false, tuttavia, AssertionErrorviene creata un'istanza e generata, come dimostrato nel Listato 1.

Listato 1:AssertDemo.java (versione 1)

public class AssertDemo {public static void main (String [] args) {int x = -1; asserire x> = 0; }}

L'asserzione nel Listato 1 indica la convinzione dello sviluppatore che la variabile xcontenga un valore maggiore o uguale a 0. Tuttavia, chiaramente non è così; l' assertesecuzione dell'istruzione risulta in un lancio AssertionError.

Compilare il listato 1 ( javac AssertDemo.java) ed eseguirlo con le asserzioni abilitate ( java -ea AssertDemo). Dovresti osservare il seguente output:

Eccezione nel thread "main" java.lang.AssertionError in AssertDemo.main (AssertDemo.java:6)

Questo messaggio è alquanto criptico in quanto non identifica cosa ha causato il AssertionErrorlancio. Se desideri un messaggio più informativo, utilizza la assertdichiarazione espressa di seguito:

asserire BooleanExpr : expr ;

Di seguito exprè presente qualsiasi espressione (inclusa una chiamata a un metodo) che può restituire un valore: non è possibile richiamare un metodo con un voidtipo restituito. Un'espressione utile è una stringa letterale che descrive il motivo dell'errore, come dimostrato nel listato 2.

Listato 2:AssertDemo.java (versione 2)

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

Compilare il listato 2 ( javac AssertDemo.java) ed eseguirlo con le asserzioni abilitate ( java -ea AssertDemo). Questa volta, dovresti osservare il seguente output leggermente espanso, che include il motivo del lancio AssertionError:

Eccezione nel thread "main" java.lang.AssertionError: x <0 in AssertDemo.main (AssertDemo.java:6)

Per entrambi gli esempi, l'esecuzione AssertDemosenza l' -eaopzione (abilita asserzioni) non produce alcun output. Quando le asserzioni non sono abilitate, non vengono eseguite, sebbene siano ancora presenti nel file di classe.

Presupposti e postcondizioni

Le asserzioni testano le ipotesi di un programma verificando che le sue varie precondizioni e postcondizioni non siano violate, avvisando lo sviluppatore quando si verifica una violazione:

  • Una precondizione è una condizione che deve essere valutata come vera prima dell'esecuzione di una sequenza di codice. I presupposti garantiscono che i chiamanti mantengano i loro contratti con i chiamati.
  • Una postcondizione è una condizione che deve restituire true dopo l'esecuzione di una sequenza di codice. Le postcondizioni garantiscono che i chiamati mantengano i loro contratti con i chiamanti.

Presupposti

You can enforce preconditions on public constructors and methods by making explicit checks and throwing exceptions when necessary. For private helper methods, you can enforce preconditions by specifying assertions. Consider Listing 3.

Listing 3:AssertDemo.java (version 3)

import java.io.FileInputStream; import java.io.InputStream; import java.io.IOException; class PNG { /** * Create a PNG instance, read specified PNG file, and decode * it into suitable structures. * * @param filespec path and name of PNG file to read * * @throws NullPointerException when filespec is * null */ PNG(String filespec) throws IOException { // Enforce preconditions in non-private constructors and // methods. if (filespec == null) throw new NullPointerException("filespec is null"); try (FileInputStream fis = new FileInputStream(filespec)) { readHeader(fis); } } private void readHeader(InputStream is) throws IOException { // Confirm that precondition is satisfied in private // helper methods. assert is != null : "null passed to is"; } } public class AssertDemo { public static void main(String[] args) throws IOException { PNG png = new PNG((args.length == 0) ? null : args[0]); } }

The PNG class in Listing 3 is the minimal beginning of a library for reading and decoding PNG (portable network graphics) image files. The constructor explicitly compares filespec with null, throwing NullPointerException when this parameter contains null. The point is to enforce the precondition that filespec not contain null.

It’s not appropriate to specify assert filespec != null; because the precondition mentioned in the constructor’s Javadoc would not (technically) be honored when assertions were disabled. (In fact, it would be honored because FileInputStream() would throw NullPointerException, but you shouldn’t depend on undocumented behavior.)

However, assert is appropriate in the context of the private readHeader() helper method, which will be completed eventually to read and decode a PNG file’s 8-byte header. The precondition that is always be passed a non-null value will always hold.

Postconditions

Postconditions are typically specified via assertions, regardless of whether or not the method (or constructor) is public. Consider 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.

Potresti aver notato una sottile differenza tra l'asserzione e la logica di rilevamento degli errori. I test di asserzione x >= 0, mentre i test della logica di rilevamento degli errori x < 0. L'asserzione è ottimistica: presumiamo che l'argomento sia OK. Al contrario, la logica di rilevamento degli errori è pessimistica: assumiamo che l'argomento non sia corretto. Le asserzioni documentano la logica corretta, mentre le eccezioni documentano un comportamento di runtime non corretto.

In questo tutorial hai imparato come utilizzare le asserzioni per documentare la logica del programma corretta. Hai anche imparato perché le asserzioni non sostituiscono le eccezioni e hai visto un esempio in cui l'utilizzo di un'eccezione sarebbe più efficace.

Questa storia, "Come usare le asserzioni in Java" è stata originariamente pubblicata da JavaWorld.