Nel post sulla gestione degli errori di runtime con Visual Basic .NET (a cui si rimanda: link) abbiamo visto la sintassi del gestore di eccezioni Try..Catch..Finally e un esempio di utilizzo. L’esempio, molto semplice, utilizzava un solo gestore di eccezioni con tre blocchi Catch. Il massimo dell’efficienza nella gestione degli errori di runtime, però, la si ottiene con un uso strutturato di questo gestore. Il linguaggio Visual Basic .NET, infatti, offre anche la possibilità di utilizzare più gestori di eccezioni nidificati (o annidati) (combinati uno dentro l’altro, ndr).
Si ricorda che quando in un gestore di eccezioni si utilizzano più blocchi Catch, al verificarsi di un errore di runtime in un’istruzione del blocco Try, l’esecuzione del programma viene sospesa su quell’istruzione e i blocchi Catch vengono esaminati, nello stesso ordine in cui compaiono all’interno del gestore, alla ricerca del primo fra questi che presenti un filtro che accetti l’eccezione che è stata generata:
- quando questo blocco Catch viene trovato, prima di tutto vengono eseguite le istruzioni presenti al suo interno (che devono rimediare all’errore che si è verificato), i blocchi Catch successivi vengono saltati e l’esecuzione del programma continua, eseguendo prima il blocco Finally, se è presente, e poi proseguendo oltre l’istruzione End Try;
- quando questo blocco Catch, invece, non viene trovato, l’esecuzione del programma riprende nel blocco Try dal punto in cui era stata sospesa, con tutte le conseguenze che il verificarsi dell’errore comporta e, se queste non sono “distruttive” per l’applicazione (crash del programma), l’esecuzione del programma continua, eseguendo prima il blocco Finally, se è presente, e poi proseguendo oltre l’istruzione End Try.
Quello appena descritto è l’uso più semplice che si può fare di un gestore di eccezioni. Vediamo ora un semplice esempio con un uso strutturato di questo gestore.
Esempio
Riprendiamo l’algoritmo del ‘Maggiore di una sequenza di numeri’ che abbiamo progettato in un altro post (a cui si rimanda: link) e del quale riportiamo qui la tabella delle variabili e il flow chart dell’algoritmo finale:
traduciamolo in Visual Basic:
L’interfaccia è stata progettata prevedendo che il programma prima si procuri il dato su quanti sono i numeri che compongono la sequenza, attraverso la casella di testo inserita nel Form, e con il clic sul bottone, poi, inizi a procurarsi i numeri della sequenza, uno alla volta, utilizzando una InputBox.
Procedendo con la codifica in Visual Basic dell’algoritmo finale così come è stato progettato, si ottiene il seguente codice:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
Public Class Form1 Private Sub btnDetermina_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnDetermina.Click Dim q, c As Integer Dim v, max As Double 'quanti sono i numeri della sequenza? q = txtQ.Text 'inizializza il contatore c = 1 Do While c <= q 'ad ogni iterazione si procura un numero della sequenza v = InputBox("Inserisci il valore n." & c, "Input") If c = 1 Then 'è la prima iterazione del ciclo max = v Else 'è una iterazione diversa dalla prima If v > max Then max = v End If End If c += 1 Loop 'fornisce in output il maggiore della sequenza trovato MsgBox("Il valore più grande fra i " & q & " valori inseriti è: " & max) End Sub End Class |
Poiché il compilatore non segnala alcun errore e, inoltre, la progettazione dell’algoritmo non contiene errori logici, siamo certi che il programma sin qui scritto è esente sia da errori formali, sia da errori logici. In fase di debugging però si può rilevare che ci sono alcune righe che possono dare origine ad errori di runtime.
In particolare le istruzioni delle righe 6 e 11 possono generare delle eccezioni appartenenti alla classe FormatException. In entrambe le righe, infatti, viene eseguita una conversione di tipo implicita di un dato di input, dal formato stringa ad un formato numerico: se l’utente inserisce un input non valido (una sequenza non interpretabile come un numero, perché per esempio contenente delle lettere – ndr), allora l’esecuzione della conversione di tipo determina un errore che genera appunto un’eccezione di formato non valido.
Più in particolare, la riga 6 prevede che l’utente inserisca un numero intero (di tipo Integer), mentre la riga 11 si aspetta di ricevere in input un numero reale (di tipo Double); pertanto per generare delle eccezioni più specifiche, converrebbe rendere esplicite quelle conversioni di tipo implicite, per esempio con l’uso del metodo Parse, come di seguito mostrato:
1 2 3 4 5 |
... q = Integer.Parse(txtN.Text) 'riga 6 ... v = Double.Parse(InputBox("Inserisci il valore n." & c, "Input")) 'riga 11 ... |
Entrambe le linee di codice individuate come possibili fonti di errori di runtime, devono essere inserite in un blocco Try di un gestore di eccezioni, in modo da catturare le eventuali eccezioni generate.
Questa volta, però, non possiamo utilizzare un solo gestore di eccezioni, infatti:
- nel caso della riga 6, all’uscita dal blocco Catch del gestore di eccezioni, il flusso di esecuzione del programma deve riprendere dall’istruzione End Sub in modo da dare all’utente la possibilità di correggere l’input inserito nella casella di testo e rifare clic sul bottone per far ripartire l’esecuzione della Sub;
- nel caso della riga 11, invece, all’uscita dal blocco Catch del gestore di eccezioni, il flusso del programma deve riprendere rientrando nel ciclo di ripetizione, senza eseguire l’iterazione che ha determinato il verificarsi dell’errore di runtime, offrendo così all’utente la possibilità di correggere l’ultimo numero di input della sequenza che ha generato l’errore, mantenendo salvo anche il regolare flusso del programma.
Ciò può essere ottenuto utilizzando due gestori di eccezioni distinti e annidati, come mostrato di seguito:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
Public Class Form1 Private Sub btnDetermina_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnDetermina.Click Dim q, c As Integer Dim v, max As Double Try q = Integer.Parse(txtQ.Text) c = 1 Do While c <= q Try v = Double.Parse(InputBox("Inserisci il valore n." & c, "Input")) Catch ex As FormatException MsgBox("Devi inserire un numero reale!", MsgBoxStyle.Exclamation, "Input non valido") Continue Do End Try If c = 1 Then max = v Else If v > max Then max = v End If End If c += 1 Loop MsgBox("Il più grande fra i " & q & " valori inseriti è: " & max) Catch ex As FormatException MsgBox("Devi inserire un valore intero!", MsgBoxStyle.Exclamation, "Input non valido") txtQ.Focus() End Try End Sub End Class |
Con quanto scritto sopra si ottiene proprio quello che volevamo, poiché, si fa notare, ciascun blocco Catch fa riferimento sempre al proprio blocco Try di appartenenza, nel senso che può intercettare solo le eccezioni generate da errori di runtime che si verificano in corrispondenza di istruzioni inserite nel blocco Try di appartenenza.
Per capire più facilmente qual è il blocco Try a cui ciacun blocco Catch appartiene, conviene mantenere visivamente separate le diverse strutture Try..Catch, dando a ciascuna una diversa indentazione (tabulazione, ndr). Un grosso aiuto in tal senso viene fornito dall’editor dell’IDE di Visual Basic che automaticamente allinea ciascun blocco Catch con il relativo blocco Try di appartenenza.
Conclusione
Grazie alla possibilità di utilizzare i gestori di eccezioni in modo strutturato, si riesce ad avere una gestione delle eccezioni molto efficiente e completa, con la possibilità di fornire all’utente informazioni puntuali circa l’errore che si è verificato e le azioni da intraprendere per correggerlo.
P.S. Nell’esempio precedente, utilizzando un ulteriore gestore di eccezioni, si potrebbe pensare di gestire in maniera specifica anche il clic sul bottone ‘Annulla’ delle InputBox utilizzate per prelevare gli input dei numeri della sequenza, in modo da determinare, per esempio, l’annullamento dell’operazione di input dell’intera sequenza. Un’idea per poterlo fare, potrebbe essere quella di generare un’eccezione in corrispondenza della pressione del bottone ‘Annulla’ dell’InputBox. Ma questo lo vedremo in un altro post (Generae eccezioni).