Ti piace il sito?
Votalo con un clic sul
banner qui sotto ^_^
Gestione degli errori
In questo articolo affronteremo insieme uno dei problemi che mette in
crisi i progettisti dei sistemi informatici e di conseguenza anche coloro
che li realizzano, i veri ossi duri, cioè programmatori, coloro che
fanno della programmazione uno stile di vita e del caffé la loro
bevanda preferita. Per farla breve, parleremo della famigerata gestione
degli errori ^_^.
Quante volte vi sarà capitato di preparare un pacchetto di installazione
e di presentarvi con un sorriso smaliante a casa di un amico o dal cliente
ed installare la delicatamente preziosa creatura nel territorio ostile,
sul PC nemico? Molte volte vero? Durante l'installazione nulla di strano,
ma poi, con la prima esecuzione il sorriso si trasforma in un'inevitabile
smorfia di disappunto, ma porc... Run-time error?
Che significa, ho fatto tutti i controlli, non manca nulla, ma PERCHE'?
Vi è capitato qualche volta anche questo, vero? Ebbene sì,
siamo prima di tutto uomini, le ragazze non me ne vogliano, e qualche cosa
ci sfugge sempre, a chi più e a chi meno, ma basta anche un solo
errore per fare pessima figura. Come si fa ad evitare gli errori? Direi
che è praticamente impossibile, ma si può minimizzare il danno
gestendo le situazioni impreviste.
Cominciamo subito con un bel esempietto. Facciamo come i ragazzini alle
elementari: dividiamo le mele che stanno nel nostro cesto, tra un gruppo
di amici, dando ad ognuno una quantità pari di cibo proibito. Possiamo
esplicare questo calcolo con una semplice espressione algebrica:
MelePerCapoccia=TotaleMeleNelCesto / NumeroDiCapocce
Dividendo mele per capocce abbiamo risolto il dilemma di tutti i ragazzini
che frequentano i primi anni delle elementari, ma ci rimane il nostro. Questa
istruzione produrrà un errore? I più smaliziati diranno: "Ma
non ci prendere in giro, è facile, si vede subito...". Per chi
non vedesse l'inghippo, diciamo che si potrebbe presentare uno degli errori
più comuni: divisione per zero. Infatti,
se il NumeroDiCapocce è nullo ci sarà un bell'errore "division
by zero" e non vedremo mai il corretto risultato del calcolo. Possiamo
certamente prevedere questo tipo di situazione e porre un'istruzione condizionale
if che devii opportunamente il flusso del programma. Parlando il linguaggio
Visual Basic avremo:
If NumeroCapocce > 0 Then
MelePerCapoccia = TotaleMeleNelCesto / NumeroDiCapocce
Else
MelePerCapoccia = 0
End If
Certamente l'accorgimento risolve questo semplice problema, ma proviamo
a pensare ad un problema più complesso, la cui soluzione si traduce
in alcune centinaia di righe di codice. Siamo sicuri che ci sarà
facile prevedere tutte le anomalie che possono presentarsi? La risposta
è - "Non ci è dato questo dono!". Chi, sano di mente,
si metterebbe a controllare la quantità di memoria disponibile prima
di eseguire un'istruzione banale come quella del calcolo delle mele? Nessuno
a mio avviso, o meglio, qualcuno forse ci ha fatto il pensiero e a questo
punto sicuramente non sta più risolvendo il problema originale, ma
se ne crea di altri ben peggiori. Eppure anche l'errore
di memoria esaurita è un bel cruccio. ma per ora lo sorvoliamo.
Il discorso fatto serve per introdurre una scorciatoia che i progettisti
di VB hanno voluto fornire ai programmatori e si chiama gestione
degli errori. La gestione degli errori avviene tramite, manco a dirlo,
dei gestori degli errori, che per semplicità
di scrittura saranno abbreviati con GdE. Un GdE è un semplice ed
utilissimo meccanismo che cattura gli errori come se fosse una trappola.
Ogni volta che è attivo un gestore degli errori e si verifica un'eccezione,
questa viene prontamente catturata dal gestore, che a questo punto può
fare due cose: proseguire con l'istruzione successiva oppure lasciare al
programmatore la decisione sul da farsi. Per usare i gestori degli errori
è sufficiente conoscere poche istruzioni che elenco di seguito:
On Error Resume Next
On Error GoTo Etichetta
On Error GoTo 0
Analizziamo On Error Resume Next. Questa istruzione
attiva un GdE che può essere assimilato come un gestore automatico.
Alla presenza di qualsiasi errore questo GdE salta all'istruzione successiva.
E' un buon meccanismo di difesa per evitare che il nostro programma vada
in crash, ma ha un grosso difetto: non lascia indizzi sull'entità
dell'errore, in altre parole non ci fa capire che tipo di errore
si è verificato e potrebbe benissimo catturare un'eccezione non facendoci
percepire l'anomalia nel codice. Ha un'altro limite non indifferente: è
in grado di gestire solo una routine per volta. Vediamo come funziona.
Per avere il nostro gestore all'interno di una funzione è sufficiente
attivarlo prima delle istruzioni che crediamo possano presentare degli errori
imprevisti. Visto che siamo molto meticolosi attiviamo il gestore prima
di tutte le istruzioni della nostra funzione, le dichiarazioni preventive
le possimo anche escludere.
Function CalcolaMelePerCapoccia(NumeroMele as Integer, _
NumeroCapocce As Integer) As Single
Dim Risultato As Single
On Error Resume Next
Risultato = 0
Risultato = NumeroMele / NumeroCapocce
'visto che siamo buoni, diamo anche una mela in omaggio
Risultato = Risultato + 1
CalcolaMelePerCapoccia = Risultato
'scrivo il risultato nella finestra di debug
Debug.Print "CalcolaMelePerCapoccia = " & Risultato
End Function
Ora, se per un malaugurato motivo ci capitasse il numero di capocce nullo,
la nostra funzione non manderà in crash l'intero applicativo, ma
si limiterà semplicemente a fornire un risultato apparentemente corretto,
ma formalmente errato perché si è verificato un imprevisto.
Infatti, il GdE farà rapida cattura della divisione per zero e passerà
il controllo all'istruzione successiva. Detto altrimenti, in caso
di errore la CalcolaMelePerCapoccia restituisce 1, ma il risultato è
la conseguenza di un errore e per tanto da ritenersi errato anch'esso. Abbiamo
dunque evidenziato il primo difetto di On Error Resume Next.
Se vogliamo effettuare per N volte consecutive il calcolo delle mele possiamo
scrivere un'altra semplice funzione.
Sub CalcolaMele(NumeroVolte as Integer)
Dim i as Integer
Dim NumeroMele as Integer
Dim NumeroCapocce as Integer
Dim Risultato as Single
On Error Resume Next
for i=1 to NumeroVolte
NumeroMele = Val(InputBox("NumeroMele"))
NumeroCapocce = Val(InputBox("NumeroCapocce"))
Debug.Print "CalcolaMele-NumeroMele = " & NumeroMele
Debug.Print "CalcolaMele-NumeroCapocce = " & NumeroCapocce
Risultato = CalcolaMelePerCapoccia(NumeroMele, NumeroCapocce)
Debug.Print "CalcolaMele-Ciclo " & i+1 & _
" MelePerCapoccia = " & Risultato
next i
End Sub
La funzione InputBox ritorna la stringa digitata
dall'utente, mentre la funzione Val converte la
stringa in un numero, se la stringa non è un numero ritorna zero.
Per capire il secondo difetto del GdE automatico disattiviamo con un commento
(apice davanti all'istruzione ') il GdE nella funzione
CalcolaMelePerCapoccia, ottenendo così la gestione degli errori solo
all'interno di CalcolaMele.
Mandata in esecuzione CalcolaMele non abbiamo nessuna garanzia sull'input
che l'utente produce. Supponiamo digiti 0 per il Numero di capocce. Il GdE
è come uno sceriffo che ha la sua giurisdizione e nel nostro
caso abbiamo uno sceriffo che controlla solo le eccezioni all'interno di
CalcolaMele e non può fare nulla per la CalcolaMelePerCapoccia. Quindi
avremo sicuramente una divisione per zero (NumeroMele / 0) che ci farà
storcere il naso davanti al nostro amico, mentre tutto il nostro gran lavoro
va in crash. Accidenti! Ecco spiegato anche il secondo difetto di questo
GdE. Ovviamente potevamo lasciare attivo anche il GdE in CalcolaMelePerCapoccia,
ma il risultato prodotto non sarebbe stato in ogni caso corretto. Sembrava
uno sceriffo in gamba, ma in realtà è un po' lavativo. In
alcune situazioni particolari può risultare utile, ma è meglio
gestire gli errori in modo più diretto.
Per evitare gli inconvenienti del precedente gestore possiamo utilizzare
On Error Goto Etichetta. Come prima, anche questa
istruzione attiva un GdE, ma in caso di errore il flusso del codice lo
decidiamo noi: possimo fare un salto in un punto ben preciso del nostro
programma, segnalato da un'apposita Etichetta.
In VB un'etichetta è una stringa alfanumerica
seguita da due punti (:). Partiamo subito con
l'esempio. Ripeschiamo la fatidica CalcolaMelePerCapoccia.
Function CalcolaMelePerCapoccia(NumeroMele as Integer, _
NumeroCapocce As Integer) As Single
Dim Risultato As Single
On Error Goto Errore
Risultato = 0
Risultato = NumeroMele / NumeroCapocce
'visto che siamo buoni, diamo anche una mela in omaggio
Risultato = Risultato + 1
CalcolaMelePerCapoccia = Risultato
'scrivo il risultato nella finestra di debug
Debug.Print "CalcolaMelePerCapoccia = " & Risultato
'la funzione ternima in questo punto se non si
'verificano errori Exit FunctionErrore: 'il gestore degli errori salta all'etichetta
'specificata se si verificano errori
MsgBox "Generato da " & Err.Source & " Codice errore: " & _
Err.Number & " Descrizione: " & Err.Description & _
" CalcolaMele non ha funzionato correttamente!", _
"ERRORE!", vbCritical
End Function
Il GdE si attiva esattamente nello stesso modo di quello precedente, ma
è necessario specificare il nome di un'etichetta. Posizioniamo la
nostra etichetta in fondo alla funzione, così potremo aggiungere
e togliere codice senza dover modificare nulla dei calcoli sulle mele. Già
ci stiamo facendo due meloni, manca solo di ingarbugliare i calcoli delle
mele.
Quando si verifica un errore il caro GdE salta all'etichetta specificata
e da qui abbiamo il pieno controllo del codice. Vi pare poco?
Lo scopo primo per un programmatore od un utente in caso di malfunzionamento
del programma è individuare la causa dell'errore. Detto fatto. Scriviamo
subito le istruzioni che ci indichino in modo lampante che si è verificato
un errore aggiungendo più informazioni possibili. Più informazioni
abbiamo e più velocemente riusciremo a risalire alle cause dell'errore.
La MsgBox, che credo non sia necessario spiegare,
permette di visualizzare un messaggio popup e ci possiamo scrivere la Divina
Commedia. Tenete a mente che i romanzi qui non servono,
è necessario essere precisi e relativamente concisi. Ricordate
sempre che il messaggio lo dovrete decodificare al 99,9% voi stessi,
quindi fate le cose per bene subito, così risparmierete del prezioso
tempo poi.
Ci sono da notare un'altro paio di cose nuove. L'istruzione Exit
Sub prima dell'etichetta, questa serve per terminare la procedura
in quel preciso punto, il che significa la totale assenza degli errori durante
l'esecuzione e di conseguenza tutto il codice scritto per il messaggio d'errore
non deve essere eseguito. Exit Sub si utilizza per le procedure, per le
funzioni si usa Exit Function e per le proprietà Exit Property. E
per gli eventi? Visto che sono delle procedure (Sub) si usa Exit Sub.
L'ultima cosa interessante è l'oggetto Err.
Che strano nome. Sarà stato derivato da Error oppure è un
"articolo" delle borgate romane? E' la prima che ho detto? Decisamente!
Questo simpatico oggettino è il nostro più grande amico e
ci aiuta con le preziose informazioni che contiene. La proprietà
Number restituisce il numero di errore che ci può servire
per discriminare tra loro tutti gli errori, mentre la proprietà
Description ci fornisce la descrizione dell'errore ed in ultimo troviamo
la proprietà Source che ci ritorna il nome
dell'oggetto o dell'applicazione che ha generato l'errore. L'oggetto Err
è il posto dove il GdE scarica le informazioni di un errore catturato
ed è sensato accedere alle sue proprietà dopo l'avvento di
un errore. In realtà questo oggetto fa anche la propagazione degli
errori, ma questo, anche se altrettanto interessante, non lo affrontiamo.
Siamo quasi giunti alla fine e voi state diventando dei super guru in fatto
di gestione degli errori, qundi pazientate ancora un pochetto ^_^.
Vi ricordate il GdE automatico che aveva il difetto di mancata giurisdizione
sulle procedure che partono dal suo territorio? Sembra incredibile, ma On
Error Goto Etichetta non ha questo difetto. Volendo, possiamo utilizzare
questo gestore anche per gestire gli errori delle funzioni chiamate direttamente
dal corpo della funzione chimante, evitando inutili sovraccarichi di lavoro.
Ecco di nuovo le due funzioni, ma CalcolaMelePerCapoccia non ha nessun gestore
perché CalcolaMele è in grado di gestire gli errori di entrambe
grazie a On Error Goto Etichetta.
Function CalcolaMelePerCapoccia(NumeroMele as Integer, _
NumeroCapocce As Integer) As Single
Dim Risultato As Single
Risultato = 0
Risultato = NumeroMele / NumeroCapocce
'visto che siamo buoni diamo anche una mela in omaggio
Risultato = Risultato + 1
CalcolaMelePerCapoccia = Risultato
'scrivo il risultato nella finestra di debug
Debug.Print "CalcolaMelePerCapoccia = " & Risultato
End Function
Function CalcolaMelePerCapoccia(NumeroMele as Integer, _
NumeroCapocce As Integer) As Single
Dim Risultato As Single
On Error Goto Errore
Risultato = 0
Risultato = NumeroMele / NumeroCapocce
'visto che siamo buoni, diamo anche una mela in omaggio
Risultato = Risultato + 1
CalcolaMelePerCapoccia = Risultato
'scrivo il risultato nella finestra di debug
Debug.Print "CalcolaMelePerCapoccia = " & Risultato
'la funzione ternima in questo punto se non si
'verificano errori Exit Function
Errore: 'il gestore degli errori salta all'etichetta
'specificata se si verificano errori
MsgBox "Generato da " & Err.Source & " Codice errore: " & _
Err.Number & " Descrizione: " & Err.Description & _
" CalcolaMele non ha funzionato correttamente!", _
"ERRORE!", vbCritical
End Function
I gestori non incidono in modo rilevante sulle prestazioni del codice,
ma è bene non abusarne inutilmente. Ogni gestore degli errori viene
disattivato quando la funzione nella quale è stato reso attivo termina.
Qundi, terminata la funzione, niente gestione degli errori se il controllo
torna in un'altra funzione che non gestisce errori. Se
si esce dalla giurisdizione di uno sheriffo e non si incontrano altri sheriffi
allora gli errori fanno da padroni.
Ed eccoci all'utlima istruzione On Error Goto 0 (zero).
Questa ha un compito facile facile: disattiva qualsiasi gestore degli errori.
Subito dopo questa istruzione nessun errore sarà
più catturato e di nuovo errori padroni. Ecco un'esempio.
Function CalcolaMelePerCapoccia(NumeroMele as Integer, _
NumeroCapocce As Integer) As Single
Dim Risultato As Single
On Error Goto Errore
Risultato = 0
Risultato = NumeroMele / NumeroCapocce
'visto che siamo buoni, diamo anche una mela in omaggio
Risultato = Risultato + 1
'disattivo il gestore degli errori On Error Goto 0 'se succede un errore da qui in poi e
'se non c'è un gestore nella funzione chiamante
'allora l'intero programma va in crash
CalcolaMelePerCapoccia = Risultato
'scrivo il risultato nella finestra di debug
Debug.Print "CalcolaMelePerCapoccia = " & Risultato
'crash sicuro
Risultato = 2 / 0
Exit Function
Errore: 'il gestore degli errori salta all'etichetta
'specificata se si verificano errori
MsgBox "Generato da " & Err.Source & " Codice errore: " & _
Err.Number & " Descrizione: " & Err.Description & _
" CalcolaMelePerCapoccia non ha funzionato correttamente!", _
"ERRORE!", vbCritical
End Function
Avete visto com'è vacile disattivare un gestore degli errori? E'
facile almeno quanto attivarlo. Se non gestite gli errori
nel vostro codice, allora state realizzando sistemi poco robusti.
Ora che avete approfondito l'argomento, potrete evitare la figura del
crash dell'intero sistema. Vi capiterà l'errore segnalato, questo
sì, ma non sarà mai come un'applicativo che termina bruscamente
senza un'apparente motivo ed il conseguente gelo perché si ignora
la causa; con il cliente che vi guarda storto o l'amico che vi deride.
Siamo giunti alla fine, lungi da me la presunzione di aver trattato l'argomento
in modo completo, ma per quanto riguarda lo sviluppo di applicativi non
strutturati in componenti ActiveX vi potete ritenere più che preparati.
Con i gestori catturiamo tutti gli errori gestiti dal sistema e possiamo
rimediare finché non si tratta di gravissimi errori a livello fisico.
Che ve ne pare? Sembra piuttosto utile! Un po' di sana pratica vi aiuterà
a testare le vostre conoscenze. Armatevi di tastiera e fate un sacco di
esperimenti finché non sarete diventati padroni della teoria.