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