Le
ultime due voci del menù Debug permettono di cambiare
in fase di esecuzione (o meglio, in fase di interruzione)
l'ordine delle istruzioni: è una opzione che risulta
molto comoda quando ad esempio ci si accorge di un pezzo di
codice difettoso, in cui il difetto consiste nell'aver eseguito
troppo presto (o troppo tardi) una certa istruzione.
L'ultima
voce del menù, "Mostra istruzione successiva",
permette di tornare subito alla routine in cui il codice è
stato interrotto, mostrando l'istruzione evidenziata in giallo,
che è appunto l'istruzione "successiva" ad
essere eseguita; questo comando è utile quando, in
modalità interruzione, si va a controllare altre porzioni
di codice perdendo di vista il punto di interruzione.
La penultima voce del menù, invece, serve a "impostare"
l'istruzione successiva da eseguire, ottenendo il risultato
descritto prima di modificare l'ordine delle istruzioni; prendiamo
ad esempio in considerazione il codice seguente:
Dim
nElementCount As Integer
Redim
sNomi(nElementCount) As String
nElementCount
= 5
|
Supponiamo
che l'istruzione successiva sia il ridimensionamento del vettore
di stringhe sNomi: tale ridimensionamento avviene in base
alla variabile nElementCount, che palesemente dovrebbe contenere
il numero di elementi da assegnare al vettore.
Peccato però che essa sia inizializzata DOPO il ridimensionamento,
e non PRIMA come sarebbe logico.
Ciò non genera alcun errore, perché all'esecuzione
dell'istruzione Redim la variabile vale zero, e quindi il
vettore conterrebbe un elemento (quello con indice zero, appunto):
può anche darsi che in certe situazioni sia giusto
procedere in questo modo, ma ragionando astrattamente verrebbe
da pensare che l'ordine corretto delle istruzioni sia:
nElementCount
= 5
Redim sNomi(nElementCount)
As String |
Quando,
procedendo col debug, arriviamo all'istruzione Redim e ci
accorgiamo del nostro errore, possiamo posizionare il cursore
sull'istruzione nElementCount = 5 e premere Ctrl+F9
(o l'equivalente comando "Imposta istruzione successiva"):
automaticamente questa istruzione sarà evidenziata
in giallo, ad indicare che essa è diventata l'istruzione
successiva da eseguire.
Dopo
averla eseguita premendo F8, possiamo impostare come
istruzione successiva quella di ridimensionamento:
a
questo punto la variabile nElementCount è già
inizializzata a 5, e quindi il nostro vettore di stringhe
sarà creato correttamente con sei elementi (da 0 a
5) premendo ancora F8.
Naturalmente, dopo aver verificato che in questo modo il programma
viene eseguito correttamente, potremo interrompere l'esecuzione
e invertire l'ordine in cui le istruzioni sono scritte, in
modo che ad una successiva esecuzione non dovremo più
preoccuparci di impostare manualmente l'ordine delle istruzioni
coi comandi del menù Debug.
Un altro modo per ottenere lo stesso risultato è quello
di trascinare la freccia gialla che compare nella barra a
sinistra nella finestra del codice, rilasciandola all'altezza
dell'istruzione da impostare come successiva; l'unica limitazione
è che l'istruzione successiva da eseguire deve essere
compresa nella stessa routine in cui il codice è stato
interrotto.
Quando
un progetto viene avviato dall'IDE di Visual Basic tramite
il tasto F5 e si verifica un errore, è lo stesso IDE
a interrompere l'esecuzione del codice mostrando il punto
in cui si verifica l'errore e consentendo quindi di effettuare
il debug di quella porzione di codice; ovviamente però
ciò non accade se l'errore si verifica quando il programma
è già stato compilato: infatti in tal caso non
c'è l'IDE di Visual Basic a supportare i comandi di
debug e, se non è stato previsto un modo per intercettare
e possibilmente correggere gli errori di run-time, l'unico
esito possibile è il blocco dell'applicazione.
Occorre quindi escogitare un modo per permettere al programma
stesso di sapere quando si verifica un eventuale errore e
provvedere, se possibile, alla sua correzione permettendo
la normale continuazione dell'esecuzione del codice: si tratta
del cosiddetto error handling, ovvero intercettazione
degli errori.
Visual Basic mette a disposizione una comoda istruzione per
implementare l'error handling: l'istruzione On Error GoTo.
Questa istruzione ordina al programma di saltare a un certo
punto del codice, identificato da un'etichetta, quando si
verifica un errore; solitamente la struttura di una routine
con l'error handling è simile alla seguente:
Sub
Prova()
'dichiarazioni
di variabili
'
'
On
Error GoTo
ErrorHandling
'istruzioni
proprie della routine
'
'
'
Exit
Sub
ErrorHandling:
'codice destinato all'intercettazione
dell'errore
'
'
End
Sub
|
Si
possono notare diverse cose: innanzitutto, l'istruzione che
permette l'intercettazione degli errori (On Error GoTo) è
la prima istruzione eseguibile della routine, subito dopo le
dichiarazioni di variabili; questo perché è importante
intercettare un errore in qualunque punto del codice avvenga,
infatti l'intercettazione viene abilitata solo dall'istruzione
On Error GoTo in poi: se tale istruzione fosse posta, supponiamo,
a metà routine, un eventuale errore che si verificasse
prima di essa non sarebbe intercettato; invece ponendola all'inizio
della routine si ha la sicurezza che qualunque errore si verifichi
durante l'esecuzione della routine sarà intercettato.
L'istruzione
On Error GoTo avverte che quando si verifica un errore l'esecuzione
del programma deve saltare al punto identificato dall'etichetta
"ErrorHandling", che si trova in fondo alla routine,
subito dopo l'istruzione Exit Sub: dopo l'etichetta ci sono
le istruzioni che esaminano l'errore verificato (tipicamente
con una Select Case, come vedremo in seguito) e tentano di risolverlo.
L'etichetta è preceduta dall'istruzione Exit Sub per
un motivo molto semplice: evitare che la parte di codice destinata
a intercettare gli errori (ovvero le istruzioni successive all'etichetta)
sia eseguita ad ogni esecuzione della routine.
Senza Exit Sub, infatti, le istruzioni di error handling sarebbero
eseguite in ogni caso, indipendentemente dal verificarsi o meno
di un errore (si tratta pur sempre di istruzioni come le altre),
e ciò può comportare a sua volta degli errori,
oltre ad essere logicamente scorretto.
Per assicurarsi quindi che quelle istruzioni siano eseguite
solo quando effettivamente si verifica un errore, occorre farle
precedere da un'istruzione che interrompa la routine nel caso
in cui non ci sia stato alcun errore: l'istruzione Exit Sub,
per l'appunto.
È
bene precisare a questo punto che le istruzioni per l'intercettazione
e la gestione degli errori riguardano soltanto gli errori di
run-time, non quelli di compilazione o i cosiddetti errori logici:
infatti gli errori di compilazione sono, in parole povere, gli
errori di sintassi, che vengono intercettati dall'IDE di Visual
Basic sin dall'avvio dell'applicazione o al momento di eseguire
una procedura (ovvero durante la compilazione del programma);
gli errori logici invece sono quelli più insidiosi
perché non causano un errore vero e proprio ma fanno
sì che il programma non si comporti nel modo previsto
dallo sviluppatore.
Un banale esempio di errore logico potrebbe essere quello in
cui ci si dimentica di convertire in modo appropriato un'unità
di misura generando risultati assurdi (ad esempio una performance
del 567% anziché del 5,67%): il programma funziona normalmente,
nel senso che non si verifica alcun errore che ne blocchi l'esecuzione,
però non fa quello che dovrebbe, genera risultati sbagliati
a causa di una o più istruzioni impostate in modo scorretto.
Gli errori di run-time, invece, sono quelli che interrompono
l'esecuzione del programma e, se non sono gestiti correttamente,
possono mandarlo in crash senza troppi scrupoli.
Quando si verifica un errore di run-time, Visual Basic imposta
le proprietà dell'oggetto Err, che devono essere esaminate
al fine di capire cosa è successo e come reagire: ogni
errore standard definito in Visual Basic è identificato
da un numero e da una descrizione (proprietà "Number"
e "Description" dell'oggetto Err); vediamo ad esempio
questo pezzo di codice:
Dim
nRisultato As Integer
nRisultato
= CLng(txtNumeratore(mnIndex).Text) / _ CLng(txtDenominatore(mnIndex).Text)
|
dove
la variabile mnIndex è un Integer dichiarata a livello
di modulo che indica quali elementi delle due matrici di controlli
TextBox "txtNumeratore" e "txtDenominatore"
occorre considerare; per il momento non ci interessa sapere
come viene calcolato mnIndex, né cosa indichino esattamente
le variabili coinvolte; basta sapere che anche in un'istruzione
semplice come questa i possibili errori sono almeno quattro:
1)
mnIndex non è compreso tra i limiti inferiore e superiore
delle matrici di controlli: "indice non compreso nell'intervallo"
(errore 9);
2) il contenuto dei TextBox non è un numero, e di conseguenza
la funzione CLng() fallisce: "Tipo non corrispondente"
(errore 13);
3) txtDenominatore(mnIndex).Text è uguale a "0":
"divisione per zero" (errore 11);
4) il rapporto tra i numeri contenuti nei due TextBox è
esterno all'intervallo -32768/+32767 valido per gli integer:
"Overflow" (errore 6)
Per intercettare un'eventuale errore scriviamo allora un'apposita
routine:
Dim
nRisultato As Integer
On
Error GoTo
Errore
nRisultato=
_ CLng(txtNumeratore(mnIndex).Text)/CLng(txtDenominatore(mnIndex).Text)
Exit
Sub
'oppure Exit Function, a seconda
dei casi
Errore:
Select
Case
Err.Number
Case 6 'Overflow
MsgBox "Il numeratore è troppo grande",
vbOkOnly
Case 9 '
indice non compreso nell'intervallo
MsgBox "Non esiste un TextBox con indice "
& CStr(mnIndex), vbOkOnly
Case 11 '
divisione per zero
MsgBox "Il valore inserito in txtDenominatore deve
essere diverso da zero", _ vbOkOnly
Case 13 '
Tipo non corrispondente
MsgBox "Non hai inserito un valore numerico",
vbOkOnly
End Select
End
Sub
'termine della routine
|
In
questo semplice caso ci siamo limitati a visualizzare il tipo
di errore riscontrato, senza tentare di risolverlo; d'altra
parte sarebbe un compito abbastanza arduo da fare automaticamente,
senza ricorrere all'aiuto dell'utente.
In altri casi invece è più semplice risolvere
in modo automatico l'errore; supponiamo ad esempio di voler
aprire un file inesistente in sola lettura:
Open
"c:\pippo.txt" For Input
Access Read As #1 |
Poiché
l'apertura del file è in sola lettura, se esso non esiste
non sarà automaticamente creato (cosa che invece accadrebbe
se il file fosse aperto anche in scrittura); per risolvere il
problema possiamo intercettare l'errore:
On
Error GoTo
Errore
Open
"c:\pippo.txt" For Input
Access Read As #1
Exit
Sub
Errore:
If
Err.Number = 53 Then '
descrizione: impossibile trovare il file
Open "c:\pippo.txt"
For Output Access Write As
#1
Close 1
Resume
End If
End
Sub
|
In
questo caso, se il file non esiste viene creato tentando di
aprirlo in modalità di scrittura, dopodiché il
controllo torna all'istruzione che ha generato l'errore grazie
all'istruzione Resume, che vedremo meglio nella prossima
lezione.
Per ora basti notare che esiste un modo più semplice
ed elegante di evitare questo errore banale, ovvero controllare
in anticipo se il file esiste, prima della sua apertura:
If
Len(Dir$("C:\pippo.txt")) = 0 Then
Open "c:\pippo.txt"
For Output Access Write As
#1
Close 1
End If
Open
"c:\pippo.txt" For Input
Access Read As #1
|
La
funzione Dir() restituisce il nome del file indicato come argomento:
se restituisce la stringa nulla (di lunghezza zero), significa
che il file non esiste: quindi viene aperto in scrittura (cioè
creato) e subito chiuso; dopodiché viene normalmente
aperto in lettura; se invece esiste già, viene aperto
soltanto aperto in lettura. |