ELEMENTI DI
PROGETTAZIONE SOFTWARE
Massimiliano Redolfi
Lezione 7: la fortezza
Perché un sistema sia robusto ogni suo componente
deve essere robusto.
Una semplice metodo
Main.java
public class Main {
public static int eval(int a, int b) {
return a / b;
}
…
}
Il metodo eval è robusto?
NO! ad esempio se invoco eval(12, 0) ottengo un’eccezione! proviamo…
Vedi jEx16
Una semplice metodo
Main.java
public class Main {
public static int eval(int a, int b) {
return a / b;
}
public static void main(String[] args) {
Scanner reader = new Scanner(System.in);
PrintStream writer = System.out;
int a, b;
writer.println("Inserire il valore di a:");
a = reader.nextInt();
writer.println("Inserire il valore di b:");
b = reader.nextInt();
writer.printf("%d / %d = %d\n", a, b, eval(a, b));
reader.close();
}
}
Il metodo eval è robusto? ed il modo in cui lo utilizziamo?
Una semplice metodo: irrobustiamo la chiamata
Main.java
public class Main {
public static int eval(int a, int b) {
return a / b;
}
public static void main(String[] args) {
Scanner reader = new Scanner(System.in);
PrintStream writer = System.out;
int a, b;
writer.println("Inserire il valore di a:");
a = reader.nextInt();
do {
writer.println("Inserire il valore di b:");
b = reader.nextInt();
} while(b == 0);
writer.printf("%d / %d = %d\n", a, b, eval(a, b));
reader.close();
}
}
i bastioni della fortezza
try/catch
Irrobustiamo la chiamata: alziamo i muri try/catch
Main.java
public static void main(String[] args) {
Scanner reader = new Scanner(System.in);
PrintStream writer = System.out;
try
{
int a, b;
writer.println("Inserire il valore di a:");
a = reader.nextInt();
writer.println("Inserire il valore di b:");
b = reader.nextInt();
writer.printf("%d / %d = %d\n", a, b, eval(a, b));
reader.close();
}
catch(Exception ex) {
writer.printf("Si è verificato un errore!");
}
}
Il blocco try delimita il perimetro di controllo
Il blocco catch intercetta le eccezioni che avvengono all’interno di tale perimetro
Gestiamo diverse tipologie di eccezioni
Esistono diverse tipologie di Exception, il blocco catch può essere utilizzato per intercettare alcuni tipi e
lasciarne passare altri, lasciando ad altre parti del sistema il compito di gestirle
La classe Exception può essere derivata per generare subclass specifiche
Main.java
try {
int a, b;
writer.println("Inserire il valore di a:");
a = reader.nextInt();
writer.println("Inserire il valore di b:");
b = reader.nextInt();
writer.printf("%d / %d = %d\n", a, b, eval(a, b));
reader.close();
}
catch(ArithmeticException ex) {
writer.printf("Si è verificato un errore nell'utilizzo delle funzioni matematiche");
}
catch(NullPointerException ex2) {
// gestione NullPointerException
}
catch(Exception ex3) {
// fallback di quelle exception non gestite precedentemente
}
Generiamo eccezioni: throw
E’ possibile generare esplicitamente un’eccezione tramite la parola chiave throw
public static int eval(int a, int b) {
if(b == 0)
throw new ArithmeticException("Il dividendo deve essere diverso da 0!");
return a / b;
}
Creiamo eccezioni specifiche al contesto
La classe Exception può essere derivata per generare subclass specifiche
NegativeIntegerException.java
package it.unibs.eps;
public class NegativeIntegerException extends Exception {
public NegativeIntegerException() {
this("");
}
public NegativeIntegerException(String msg) {
super(msg);
}
}
Dichiariamo le possibili eccezioni di un metodo: throws
Quando un metodo può generare eccezioni non standard deve dichiararlo
public static int eval(int a, int b) throws NegativeIntegerException {
if(b == 0)
throw new ArithmeticException("Il dividendo deve essere diverso da 0!");
if(b < 0 || a < 0)
throw new NegativeIntegerException("La funzione elabora solo valori positivi");
return a / b;
}
Esercizio: la resurrezione…
Creare tramite un ciclo do-while un modo per far riprendere il programma anche nel caso si verifichi un
errore…
Main.java
public static void main(String[] args) {
Scanner reader = new Scanner(System.in);
PrintStream writer = System.out;
boolean ok = false;
do {
try {
int a, b;
writer.println("Inserire il valore di a:");
a = reader.nextInt();
writer.println("Inserire il valore di b:");
b = reader.nextInt();
writer.printf("%d / %d = %d\n", a, b, eval(a, b));
reader.close();
ok = true;
}
catch(ArithmeticException ex) {
writer.printf("Si è verificato un errore nell'utilizzo …: %s", ex.getMessage());
}
// … omissis …
}while(!ok);
}
Vedi jEx17
i bastioni della fortezza
stack trace, rethrowing & finally block
Vedi jEx18
Il Contesto: lo stack trace
Un’eccezione porta con sé il suo contesto, to stack delle chiamate al momento in cui si è verificata
l’eccezione stessa…
catch(NegativeIntegerException ex3) {
writer.printf("Eccezione utilizzo di interi negativi: %s", ex3);
writer.println("lo stack al momento dell'eccezione era...");
ex3.printStackTrace(writer);
}
Rilanciare un’eccezione
Se non viene gestita completamente un’eccezione DEVE essere rilanciata affinché qualcuno se ne occupi
catch(NegativeIntegerException ex3) {
writer.printf("Eccezione utilizzo di interi negativi: %s", ex3);
writer.println("lo stack al momento dell'eccezione era...");
ex3.printStackTrace(writer);
throw ex3;
}
E’ possibile anche rilanciare eccezioni di tipo diverso
catch(NegativeIntegerException ex3) {
writer.printf("Eccezione utilizzo di interi negativi: %s", ex3);
writer.println("lo stack al momento dell'eccezione era...");
ex3.printStackTrace(writer);
throw new RuntimeException("Errore interno impossibile proseguire", ex3);
}
Exception chaining: ex3 rappresenta la inner exception, quella che ha generato l’errore
C’è un unico punto da cui uscire: finally
Se esiste un blocco finally associato ad un blocco try si passerà sempre da tale blocco al termine
dell’elaborazione del blocco try stesso:
caso 1: senza errori: try —> finally
caso 2: con errori: try —> catch —> finally (sempre che esista catch altrimenti come caso 1)
finally {
writer.println("...da qui si passa sempre!");
}
Exception Mantra
Non intercettare un’eccezione se non si sa come gestirla
Lasciare che le eccezioni risalgano lo stack
Intercettare eccezioni al livello applicativo corretto
Utilizzare cicli di retry per tentare di fissare un problema e rieseguire
il codice che ha generato l’eccezione
Un’eccezione può essere gestita anche solo parzialmente, in tal caso deve
essere rilanciata la stessa eccezione od un’altra per la gestione completa
Se non si sa come gestire un’eccezione terminare il programma nel miglior
modo possibile
Lo schema di gestione delle eccezioni deve essere semplice