Le Asserzioni in Java

Concetti Chiave
  • Le asserzioni sono utilizzate per verificare condizioni che dovrebbero essere sempre vere durante l'esecuzione del programma.
  • L'operatore assert consente di creare asserzioni che, se falliscono, lanciano un'eccezione AssertionError.
  • Le asserzioni sono utili per il debugging e la verifica delle condizioni durante lo sviluppo, ma non dovrebbero essere utilizzate per il controllo degli errori in codice rilasciato.
  • Le asserzioni possono essere abilitate o disabilitate tramite opzioni della riga di comando durante l'esecuzione del programma.

La parola chiave assert

Un'importante parola chiave del linguaggio Java è assert.

Viene utilizzata durante lo sviluppo di un programma per creare un'asserzione, che è una condizione che dovrebbe risultare sempre vera (o sempre falsa) durante l'esecuzione di porzioni del programma.

Ad esempio, si potrebbe avere un metodo che dovrebbe sempre restituire un valore intero positivo. Si potrebbe testarlo asserendo che il valore restituito sia maggiore di zero utilizzando un'istruzione assert. A tempo di esecuzione, se la condizione è vera, non ha luogo nessun'altra azione. Tuttavia, se la condizione è falsa, allora viene lanciato un AssertionError. Le asserzioni sono spesso utilizzate durante i test per verificare che una condizione attesa sia effettivamente soddisfatta. Non sono solitamente utilizzate per il codice rilasciato. Infatti, le asserzioni devono essere rimosse quando si rilascia una versione finale del programma.

La parola chiave assert ha due forme. La prima è mostrata qui:

assert condizione;

Qui, condizione è un'espressione che deve restituire un risultato Boolean. Se il risultato è true, allora l'asserzione è vera e non ha luogo nessun'altra azione. Se la condizione è false, allora l'asserzione fallisce e viene lanciato un oggetto AssertionError predefinito.

La seconda forma di assert è mostrata qui:

assert condizione: expr;

In questa versione, expr è un valore che viene passato al costruttore di AssertionError. Questo valore è convertito nel suo formato stringa e visualizzato se un'asserzione fallisce. Tipicamente, si specificherà una stringa per expr, ma qualsiasi espressione non-void è consentita purché definisca una conversione stringa ragionevole.

Ecco un esempio che utilizza assert. Verifica che il valore restituito di ottieniNum() sia positivo.

// Programma che mostra l'uso
// delle asserzioni
class DemoAsserzione {

    static int val = 3;

    // Metodo che restituisce un intero
    static int ottieniNum() {
        return val--;
    }

    public static void main(String[] args)
    {
        int n;
        for(int i=0; i < 10; i++) {
            n = ottieniNum();
            // Questa asserzione fallirà quando n è pari a zero
            assert n > 0;
            System.out.println("n è " + n);
        }
    }

}

Per abilitare il controllo delle asserzioni a tempo di esecuzione, si deve specificare l'opzione -ea. Ad esempio, per abilitare le asserzioni per DemoAsserzione, si esegue utilizzando questa riga:

java -ea DemoAsserzione

Dopo aver compilato ed eseguito come appena descritto, il programma crea il seguente output:

n è 3
n è 2
n è 1
Exception in thread "main" java.lang.AssertionError
at DemoAsserzione.main(DemoAsserzione.java:18)

In main(), vengono effettuate chiamate ripetute al metodo ottieniNum(), che restituisce un valore intero. Il valore restituito di ottieniNum() è assegnato a n e poi testato utilizzando questa istruzione assert:

// Questa asserzione fallirà quando n è pari a zero
assert n > 0;

Questa istruzione fallirà quando n è uguale a 0, cosa che accadrà dopo la quarta chiamata. Quando questo accade, viene lanciata un'eccezione.

Come spiegato, è possibile specificare il messaggio visualizzato quando un'asserzione fallisce. Ad esempio, se si sostituisce

// Questa asserzione fallirà quando n è pari a zero
assert n > 0: "Il valore di n non è positivo!";

per l'asserzione nel programma precedente, allora verrà generato il seguente output:

n è 3
n è 2
n è 1
Exception in thread "main" java.lang.AssertionError:
Il valore di n non è positivo!
at DemoAsserzione.main(DemoAsserzione.java:18)

Un punto importante da comprendere riguardo alle asserzioni è che non ci si deve affidare ad esse per eseguire alcuna azione effettivamente richiesta dal programma. La ragione è che normalmente, il codice rilasciato verrà eseguito con le asserzioni disabilitate. Ad esempio, si consideri questa variazione del programma precedente:

// Programma che mostra l'uso
// delle asserzioni
class DemoAsserzione {

    static int val = 3;

    // Metodo che restituisce un intero
    static int ottieniNum() {
        return val--;
    }

    public static void main(String[] args)
    {
        int n;
        for(int i=0; i < 10; i++) {
            n = ottieniNum();
            // Questo non è un modo corretto di usare
            // le asserzioni!
            assert (n = ottieniNum()) > 0;
            System.out.println("n è " + n);
        }
    }

}

In questa versione del programma, la chiamata a ottieniNum() è spostata all'interno dell'istruzione assert. Sebbene questo funzioni bene se le asserzioni sono abilitate, causerà un malfunzionamento quando le asserzioni sono disabilitate, perché la chiamata a ottieniNum() non verrà mai eseguita! Infatti, n deve ora essere inizializzato, perché il compilatore riconoscerà che potrebbe non essere assegnato un valore dall'istruzione assert.

Le asserzioni possono essere abbastanza utili perché semplificano il tipo di controllo degli errori che è comune durante lo sviluppo. Ad esempio, prima di assert, se si voleva verificare che n fosse positivo nel programma precedente, si doveva utilizzare una sequenza di codice simile a questa:

if(n < 0) {
    System.out.println("n è negativo!");
    // Si potrebbe lanciare un'eccezione
    return;
}

Con assert, si ha bisogno di una sola riga di codice. Inoltre, non si devono rimuovere le istruzioni assert dal codice rilasciato, basta disabilitarle.

Opzioni per Abilitare e Disabilitare le Asserzioni

Quando si esegue il codice, è possibile disabilitare tutte le asserzioni utilizzando l'opzione -da. È possibile abilitare o disabilitare un pacchetto specifico (e tutti i suoi sottopacchetti) specificando il suo nome seguito da tre punti dopo l'opzione -ea o -da. Ad esempio, per abilitare le asserzioni in un pacchetto chiamato MioPacchetto, si usa

java -ea:MioPacchetto... NomeClasseMain

Per disabilitare le asserzioni in MioPacchetto, si usa

java -da:MioPacchetto... NomeClasseMain

È possibile anche specificare una classe con l'opzione -ea o -da. Ad esempio, questo abilita DemoAsserzione individualmente:

java -ea:DemoAsserzione DemoAsserzione