|
Assert in Java: tecniche e filosofia d'uso
Una delle più importanti novità del JDK 1.4 rispetto
alle precedenti versioni è l'introduzione di un servizio
di Assert. Si tratta della più significativa modifica
introdotta nel linguaggio dal 1997, data in cui vennero
introdotte le classi interne. Questo caso tuttavia si
distingue dal precedente per almeno due ragioni: in
primo luogo l'introduzione delle Assert ha comportato
significative modifiche nella JVM, cosa che rende il
bytecode prodotto dal nuovo compilatore incompatibile
con le precedenti JVM; in secondo luogo con le assert
viene introdotto per la prima volta nel linguaggio Java
un costrutto di meta programmazione, ossia un costrutto
che invece di dire al computer "cosa deve fare" in un
determinato momento, suggerisce "qualcosa che non dovrebbe
mai succedere" ad un certo punto dell'esecuzione. L'esistenza
di un simile costrutto permette di mettere in pratica
una forma semplificata di programmazione per contratto,
una tecnica di programmazione che aiuta a realizzare
programmi più robusti imponendo la dichiarazione di
precondizioni, postcondizioni ed invarianti.
Cosa sono le assert
Le assert sono istruzioni che verificano la verità
di una condizione booleana, e provocano la terminazione
del programma (mediante il lancio di un AssertionError)
nel caso in cui tale condizione risulti falsa. Ad esempio
l'istruzione:
assert a + b > 0;
provocherà la terminazione del programma
qualora la somma dei valori a e b sia uguale o inferiore
a zero. In prima istanza, la semantica dell'assert si
riconduce ad una forma compatta per espressioni del
tipo:
if(!(a+b>0))
throw new AssertException();
Al di la dell'eleganza di un costrutto
compatto, esistono sostanziali differenze tra i due
casi, sia sul piano tecnico che su quello filosofico.
Da un punto di vista tecnico, il costrutto delle assert
prevede la possibilità di disabilitare in blocco il
controllo delle condizioni: come vedremo più avanti,
le assert vengono usate essenzialmente in fase di test
e debugging: durante la normale esecuzione, è possibile
disabilitarle, eliminando in tal modo l'overhead legato
alla loro gestione. Ma esiste anche una differenza assai
sottile sul piano filosofico, che rende l'assert un
qualcosa di completamente diverso da qualunque altro
costrutto presente in Java. Contrariamente a quanto
avviene con i costrutti standard dei linguaggi imperativi,
una assert non rappresenta un ordine, ma un punto di
vista: essa indica una condizione che il programmatore
ritiene debba essere vera in un determinato momento
dell'esecuzione di un programma. La violazione di tale
condizione causerà la terminazione del programma, dal
momento che si è verificato qualcosa che il programmatore
non aveva previsto. Mai e in nessun caso una assert
potrà contenere direttive che influenzino la normale
esecuzione del programma.
L'utilizzo delle assert permette
al programmatore di verificare la consistenza interna
di un programma al fine di renderlo più stabile; nel
contempo aggiunge espressività al codice, rendendolo
più leggibile. Ma per comprendere a fondo la filosofia
di utilizzo, può essere utile una breve introduzione.
Sherlock Holmes e la filosofia delle Assert
Nel racconto "L'avventura degli omini danzanti"
Sherlock Holmes, il più famoso detective letterario
che la storia ricordi, si trova a dover affrontare un
caso di omicidio, per il quale viene accusata la persona
sbagliata. L'apparenza a volte inganna, ma la logica,
se usata nel giusto modo, può aiutare a rimettere le
cose a posto:
Lo studio era un locale non molto grande, coperto su
tre pareti dai libri, con uno scrittoio di fronte ad
una finestra che dava sul giardino. Per prima cosa,
dedicammo la nostra attenzione al corpo del povero gentiluomo,
la cui massiccia figura giaceva in mezzo alla stanza.
Le vesti in disordine indicavano che era stato bruscamente
risvegliato dal sonno. Il proiettile, sparato dal davanti,
era rimasto nel corpo dopo avere attraversato il cuore.
Non c'erano tracce di polvere da sparo, né sulla vestaglia
né sulle mani. Secondo il medico, la signora invece
mostrava tracce di polvere sul viso ma non sulle mani.
"L'assenza di tracce di polvere sulle mani non significa
nulla; avrebbe avuto molto significato, invece, la loro
presenza", disse Holmes. "Se il proiettile non è difettoso
e la polvere non schizza indietro, si possono sparare
molti colpi senza che ne rimanga traccia. Ora suggerirei
di rimuovere il corpo del signor Cubitt. Immagino, dottore,
che lei non abbia recuperato il proiettile che ha ferito
la signora?"
"Prima di poterlo recuperare occorre un complicato intervento
chirurgico. Ma nella pistola ci sono ancora quattro
proiettili. Due sono stati sparati, provocando due ferite,
quindi il conto dei proiettili torna."
"Così sembrerebbe", convenne Holmes. "Forse può anche
dirmi che fine ha fatto il proiettile che ha ovviamente
colpito il bordo della finestra?"
Si era improvvisamente girato indicando, col lungo indice
sottile, un foro che attraversava l'estremità inferiore
del telaio della finestra, circa un pollice sopra il
bordo.
"Per Giove!", esclamò l'ispettore. "Come diamine ha
fatto a vederlo?"
"L'ho visto perché lo stavo cercando."
"Fantastico!", disse il dottore. "Lei ha senz'altro
ragione, signore; e allora, ci deve essere stata una
terza persona. Ma chi poteva essere, e come ha fatto
ad andarsene?"
"E' questo il problema che dobbiamo risolvere", disse
Holmes. "Ispettore Martin, lei ricorderà che le domestiche
hanno dichiarato di aver sentito odore di polvere da
sparo uscendo dalle loro stanze, e che le ho detto che
si trattava di un elemento di estrema importanza?"
"Sì, lo ricordo; ma confesso di non aver capito il motivo
della sua raccomandazione."
"Ci porta a desumere che, al momento dello sparo, tanto
la finestra che la porta della stanza erano aperte.
Altrimenti, il fumo dell'esplosione non si sarebbe potuto
diffondere così rapidamente nella casa. Per questo,
bisognava che nella stanza ci fosse corrente. Porta
e finestra, però, sono rimaste aperte solo per pochi
minuti."
"Come può provarlo?"
"Perché la candela non aveva sgocciolato."
"Magnifico!", esclamò l'ispettore. "Magnifico!"
"Essendo certo che, al momento della tragedia, la finestra
era aperta, ho pensato che nella faccenda poteva essere
coinvolta una terza persona, che aveva sparato dall'esterno.
Un proiettile diretto contro questa persona avrebbe
potuto colpire il telaio della finestra. Ho cercato
e, voilà, c'era il segno del proiettile.
La tecnica investigativa di Holmes è basata sulla deduzione.
"Se escludi l'impossibile, ciò che rimane, per quanto
improbabile, non può che essere la verità", era solito
dire. Tuttavia in informatica non è permesso escludere
l'impossibile: manca la possibilità, come dimostrato
dal Teorema di Rice, di realizzare programmi che dimostrino
la correttezza logica di un generico altro programma.
La frase di Holmes
"L'assenza di tracce di polvere
sulle mani non significa nulla; avrebbe avuto molto
significato, invece, la loro presenza"
si riflette in una celebre frase
di Martin Fowler a proposito dei test basati sulle assert:
"I test rivelano la presenza di
bugs, non la loro assenza"
Dal momento che non possiamo mai
essere sicuri di non aver commesso errori, possiamo
comunque seminare alcune trappole nei punti critici
del codice, in modo da ottenere il maggior numero possibile
di indicazioni qualora si verifichi un errore inaspettato.
Al pari di Holmes, possiamo dedurre
l'esistenza di un errore dalla violazione di una condizione
invariante (il numero dei proiettili sparati è superiore
a quello dei proiettili di una sola pistola), o dalla
verifica di una precondizione (la finestra aperta) unitamente
alla invalidazione di una postcondizione (l'assenza
di sgocciolamento della candela). Come vedremo, esiste
un gran numero di situazioni in cui è utile mettere
in atto questo tipo di controlli all'interno del codice.

Figura 1 - L'abilità di Sherlock Holmes
nell'arte della deduzione logica
può venirci in aiuto durante la ricerca di un bug
Sintassi della assert
L'istruzione assert prevede due costrutti:
assert
booleanExpression;
assert booleanExpression : message;
Il secondo di questi costrutti permette
di specificare un messaggio da visualizzare qualora
la condizione logica venga meno. Tale messaggio può
contenere anche informazioni dettagliate sul caso che
ha provocato il fallimento del programma, ad esempio
assert
a + b > c : "La somma di " + a + " con " + b + "
ha dato un risultato minore o eguale di" + c;
Compilazione ed esecuzione di codice contenente
assert
L'uso delle assert richiede delle opzioni speciali
in fase di compilazione e di esecuzione. L'uso di un
esempio passo-passo dovrebbe mettere in chiaro tutto
quello che è necessario sapere. Per prima cosa si proceda
a copiare il seguente programma in un file dal nome
AssertTest.java:
public class AssertTest {
public static void main(String args[]) {
byte b = 0;
for ( int i = 0; i <= 64; i++) {
assert i < 64
: "Errore di Overflow";
b = (byte)(i * 2);
System.out.println("b
= " + b);
}
}
}
Si noti che la assert in quinta riga
richiede che il valore di i sia minore di 64, per evitare
un errore di overflow nella istruzione successiva. Si
noti anche che il ciclo for in quarta riga provocherà
in ultima istanza la violazione di tale condizione.
Per fare in modo che il compilatore
accetti codice contenente asserzioni, è necessario utilizzare
lo speciale flag -source 1.4 da riga di comando. Tale
soluzione è resa necessaria dal fatto di dover garantire
la compatibilità con il codice per JDK 1.4, che permetteva
di usare la parola "assert" come nome di variabile o
di metodo.
javac
-source 1.4 AssertTest.java
Per default le assertion sono disabilitate:
se proviamo a lanciare il programma con il comando
java AssertTest
otterremo un output di questo tipo:
....
b = 122
b = 124
b = 126
b = -128
si noti che l'ultimo valore è negativo,
dal momento che l'ultimo valore indotto dal ciclo for
ha prodotto un overflow.
Se vogliamo abilitare il controllo
delle assert in fase di esecuzione, dobbiamo usare il
flag -ea
java -ea
AssertTest
in questo caso il programma terminerà
prima di raggiungere l'overflow:
....
b = 122
b = 124
b = 126
java.lang.AssertionError: Errore di Overflow
at AssertTest.main(AssertTest.java:5)
Exception in thread "main"
Sostanzialmente questo e' tutto ciò
che è necessario sapere per utilizzare il costrutto
Assert nei propri programmi. Ad ogni buon conto, i flag
di attivazione dispongono di una serie di opzioni, che
verranno descritte nel prossimo paragrafo.
Abilitazione e disabilitazione
selettiva
Oltre al flag -ea (Enable Assertion), è disponibile
un flag complementare -da (Disable Assertion). Per entrambi
è possibile specificare un parametro che può assumere
i seguenti valori:
- Nessun valore
le assertion vengono abilitate o disabilitate in tutte
le classi (escluse le classi di sistema, disabilitate
per default)
- nomeDiPackage...
le assertion vengono abilitate o disabilitate nel
package indicato e in tutti i suoi sotto package.
- ...
Abilita o disabilita le assertion nel package di default.
- nomeDiClasse
Abilita o disabilita le assertions nella classe specificata
In questo modo è possibile specificare in modo molto
preciso e dettagliato la modalità di esecuzione. Ad
esempio la seguente riga di comando esegue la classe
AssertionTest, dopo aver abilitato le assertion nel
package it.mokabyte.provaAssertion e nei suoi eventuali
subpackage:
java -ea:it.mokabyte.provaAssertion...
AssertionTest
E' possibile replicare ciascuno di
questi flag, in modo da ottenere il risultato desiderato.
Il seguente comando lancia la classe AssertionTest dopo
aver abilitato la le assertion nel package it.mokabyte.provaAssertion
e nei suoi eventuali subpackage, con l'esclusione del
sottopackage subPackage1 e della classe Class1A:
java -ea:it.mokabyte.provaAssertion...
-da:it.mokabyte.provaAssertion.subPackage1... -da:it.mokabyte.provaAssertion.Class1A
AssertionTest
I flag -ea e -da permettono di abilitare
o disabilitare le assertion su qualunque package, compresi
i package di sistema. Tuttavia, quando si usano i flag
senza parametro, le assertion sono disabilitate sulle
classi di sistema. Per gestire in modo esplicito l'abilitazione
o la disabilitazione delle assert nelle classi di sistema,
è possibile ricorrere ai flag -esa (Enable System Assertions)
e -dsa (Disable System Assertions).
Conclusioni
Questo articolo ha introdotto il costrutto Assert,
introdotto in Java a partire dal JDK 1.4. Come si è
potuto constatare, si tratta di un costrutto dalla sintassi
molto semplice, che richiede tuttavia qualche piccola
attenzione in fase di compilazione ed esecuzione del
codice. Il mese prossimo analizzeremo in profondità
i principali scenari di utilizzo della assert, come
costrutto per il controllo di precondizioni, postcondizioni
ed invarianti.
Bibliografia
Questo documento ha dato il via al processo che
ha portato all'introduzione delle assertion in Java.
http://www.jcp.org/jsr/detail/41.jsp
Gli appassionati di archeologia informatica
possono sfogliare le specifiche originali di Oak, il
linguaggio di James Gosling antenato di Java, che inizialmente
includeva il supporto alle assert:
http://java.sun.com/people/jag/green/OakSpec0.2.ps
Un articolo su Java World sulle Assert:
Part 1: Understand the mechanics of Java's new assertion
facility
http://www.javaworld.com/javaworld/jw-11-2001/jw-1109-assert.html
Part 2: Understand the methodology impact of Java's
new assertion facility
http://www.javaworld.com/javaworld/jw-12-2001/jw-1214-assert.html?
Il teorema di Rice asserisce che
qualunque proprietà non ovvia di un formalismo di calcolo
è indecidibile. Una versione della dimostrazione del
teorema può essere trovata su:
http://www.math.ohio-state.edu/~cgon/rices.html
La raccolta completa di romanzi e
racconti di Conan Doyle, che costituisce il cosiddetto
Canone, può essere consultata al seguente indirizzo:
http://www.bakerstreet221b.de/canon/
L'immagine stereotipa di Sherlock
Holmes, con il berretto da caccia, la mantellina e la
pipa Calabash, non rende giustizia nè alla descrizione
letteraria del personaggio, nè all'opera dei numerosi
illustratori che, dalla fine dell'800, hanno dato vita
a rappresentazioni del detective di Baker Street. Una
storia completa dell'iconografia di Sherlock Holmes
può essere trovata all'indirizzo:
http://www.holmesonscreen.com/Definitive.htm
Chi fosse interessato invece alla
più completa raccolta di illustrazioni sul detective
di Baker Street, può consultare la Pinacoteca Holesiana:
http://www.bakerstreet221b.de/gallery.htm
|