Oggi
cominciamo un argomento al quale non sempre viene riservata
la giusta importanza nei manuali di programmazione: il debug
del codice.
Spesso i manuali trattano il debug in uno degli ultimi capitoli,
come se fosse una delle ultime cose che un programmatore deve
sapere per svolgere bene il proprio lavoro; invece è
vero proprio il contrario: avere difficoltà ad eseguire
un corretto ed efficiente debug significa avere difficoltà
a creare applicazioni valide ed affidabili, anche se un programmatore
conoscesse a memoria tutte le istruzioni di un linguaggio.
Infatti il debug è la procedura che permette a uno
sviluppatore di scovare e correggere i "bug", ovvero
gli errori e le imperfezioni presenti nel codice sorgente
di un'applicazione: poiché anche in applicazioni semplici
è quasi impossibile evitare del tutto errori o semplici
sviste, è necessario sempre rivedere il funzionamento
del programma sviluppato con l'aiuto degli strumenti di debug
per verificare che tutto funzioni correttamente. Anzi, nella
stragrande maggioranza dei casi le operazioni di debug sono
molto più lunghe e complesse dello sviluppo vero e
proprio dell'applicazione, e la complessità del debug
è direttamente proporzionale (se non addirittura più
che proporzionale) alla complessità dell'applicazione
da testare.
Nell'IDE di Visual Basic, un intero menù è dedicato
al debug del codice: al suo interno ci sono varie voci che
consentono allo sviluppatore di eseguire la propria applicazione
istruzione dopo istruzione, di vedere in ogni momento il valore
di tutte le variabili utilizzate dall'applicazione, di interrompere
o modificare il flusso di esecuzione delle istruzioni.
Altre funzionalità relative al debug si trovano nel
menù Visualizza, ed è possibile anche utilizzare
una barra degli strumenti dedicata al debug: basta cliccare
col pulsante destro del mouse sulla barra degli strumenti
standard e selezionare la voce "debug".
Finora
noi ci siamo limitati a una sorta di primitivo debug utilizzando
la finestra Immediata per verificare il valore di certe variabili
dopo l'esecuzione di alcune istruzioni: ora vedremo come sia
possibile fare la stessa cosa in modo molto più potente
ed efficiente.
Innanzitutto consideriamo le prime quattro voci del menù
"Debug": la prima, "Esegui istruzione",
indica la possibilità di eseguire l'applicazione un'istruzione
alla volta, secondo la modalità comunemente definita
"passo-passo" (single step), cioè
appunto un passo alla volta; ad essa è associata il
tasto F8, che premuto in sequenza permette di evidenziare
man mano (in giallo, secondo l'impostazione standard modificabile
nelle opzioni dell'IDE) la riga di istruzioni che sta per
essere eseguita.
Mentre col tasto F5 si dà avvio all'esecuzione
dell'intera applicazione, senza interruzioni (a meno che si
verifichi un errore), col tasto F8 si entra in modalità
"interruzione" (visibile nella barra del titolo
dell'IDE di Visual Basic) perché l'esecuzione del codice
viene interrotta dopo ogni istruzione, fino a quando non si
preme nuovamente F8, oppure F5 o altri tasti
con analoga funzione.
Solitamente, premendo per la prima volta F8 viene evidenziata
in giallo la riga di dichiarazione della routine dell'evento
load del form principale dell'applicazione: infatti questo
è normalmente il primo evento che viene generato all'avvio
di un'applicazione; questo è quello che succede, ad
esempio, se provate ad eseguire passo-passo il progetto del
"campo minato" affrontato qualche lezione fa.
Quando si è in modalità interruzione, è
possibile conoscere il valore di ogni variabile posizionando
semplicemente il puntatore del mouse sul nome della variabile
che interessa: comparirà un tooltip col valore corrente
della variabile.
Poiché quando una riga di codice è evidenziata
l'esecuzione è momentaneamente interrotta, è
anche possibile modificare le istruzioni per vedere come cambia
la situazione: in questo modo è molto semplice correggere
errori banali come quelli di battitura dei valori da assegnare
alle variabili.
Errori che, per quanto facili da risolvere, talvolta possono
essere molto difficili da individuare: gli strumenti di debug
forniscono un valido aiuto a questo scopo.
Il debug passo-passo può risultare spesso molto noioso,
perché costringe ad eseguire un'istruzione alla volta
anche nelle porzioni di codice che abbiamo già controllato
e che sappiamo funzionare bene, prima di arrivare al punto
in cui pensiamo che si verifichi un errore e che quindi va
controllato attentamente.
Consideriamo ad esempio il seguente codice:
Private
Sub
Form_Load()
Dim i As
Integer
Call Inizializza
Call Ridimensiona
i = Conta()
MsgBox i, "Contatore"
End Sub |
Nell'evento
Load del form sono richiamate due subroutine e una funzione,
la quale restituisce un Integer assegnato alla variabile i:
il valore di tale variabile è poi visualizzato tramite
un'istruzione MsgBox; a prescindere dalle subroutine, si vede
subito che l'ultima istruzione contiene un errore, perché
il secondo parametro dovrebbe essere una costante numerica
mentre la variabile nel codice c'è una stringa: ci
aspettiamo quindi un errore "Tipo non corrispondente",
o "Type Mismatch".
In apparenza, basta premere sei volte il tasto F8 per
giungere all'istruzione incriminata; in realtà, quando
viene evidenziata una riga che richiama una routine, premendo
F8 si dà inizio all'esecuzione di quella routine
e saranno man mano evidenziate le righe di codice di quella
routine: è chiaro che se le routine "Inizializza",
"Ridimensiona" e "Conta" contengono migliaia
di righe di codice oppure cicli con migliaia di iterazioni,
passerà parecchio tempo prima di riuscire a giungere,
premendo il tasto F8, all'ultima istruzione dell'evento Load.
Per evitare questo inconveniente è possibile usare
la modalità step over (menù "Esegui
istruzione/routine"), attivabile con la combinazione
di tasti maiusc + F8: con questa modalità l'IDE
di Visual Basic non esegue un'istruzione alla volta ma esegue
un'istruzione o una routine; se la riga evidenziata in giallo
è un'istruzione semplice, sarà eseguita quell'istruzione
come nella modalità passo-passo; se invece è
una chiamata a una funzione o subroutine, premendo F8
(single step) il debug passerà in rassegna le
singole istruzioni della routine, mentre premendo maiusc
+ F8 (step over) sarà eseguita in colpo
solo l'intera routine.
Se ci troviamo col debug nel bel mezzo di una routine e vogliamo
tornare subito all'istruzione successiva alla sua chiamata,
possiamo premere ctrl + maiusc + F8 (menù "Esci
da istruzione/routine"): ciò fa sì che
tutte le istruzioni comprese tra quella che sta per essere
eseguita (evidenziata in giallo) e l'uscita dalla routine
siano eseguite in un colpo solo.
Spesso capita che si voglia esaminare con attenzione una piccola
parte di una routine (solitamente, per la legge di Murphy,
questa piccola parte si trova verso la fine della routine
),
così che la modalità step over non possa essere
utilizzata (altrimenti verrebbe eseguita tutta in una volta
l'intera routine e la modalità single step risulti
decisamente noiosa.
Per giungere subito al punto del codice che ci interessa esaminare
senza consumarsi il dito premendo F8 esiste una soluzione
molto semplice: impostare un punto di interruzione (breakpoint)
sulla riga iniziale del blocco di codice di cui vogliamo fare
il debug.
In questo modo sarà sufficiente premere F5 per
avviare il progetto e quando l'esecuzione arriverà
al punto di interruzione l'IDE di Visual Basic passerà
in modalità interruzione evidenziando in giallo la
riga su cui abbiamo impostato il breakpoint, consentendoci
così di effettuare il debug senza ulteriori perdite
di tempo.
Il tasto per attivare un punto di interruzione sulla riga
su cui si trova il cursore è F9, mentre una
seconda pressione del tasto F9 rimuove il breakpoint
appena inserito; c'è poi la combinazione ctrl +
maiusc + F9 che rimuove in una volta sola tutti i punti
di interruzione presenti nel progetto.
Un altro modo ancora per ottenere lo stesso risultato consiste
semplicemente nel posizionare il cursore sulla riga in cui
l'esecuzione deve interrompersi e premere la combinazione
ctrl + F8: ciò equivale alla voce di menù
"Esegui fino al cursore", ed è come se su
quella riga ci fosse un punto di interruzione.
Mentre però il cursore può trovarsi solo su
una riga, i punti di interruzione possono essere molteplici,
anche su righe successive: essi rappresentano quindi uno strumento
più flessibile.
Non tutte le righe di codice possono essere contrassegnate
da un breakpoint: ad esempio non possono esserlo le etichette
utilizzate in combinazione con l'istruzione GoTo, i commenti,
oppure le istruzioni di dichiarazione di variabili.
Il motivo è che queste righe identificano "codice
non eseguibile": mentre per i commenti e le etichette
di riga questo è abbastanza ovvio, lo è un po'
meno per le dichiarazioni di variabili; le dichiarazioni rappresentano
codice "non eseguibile" perché il loro unico
scopo è quello di assegnare un nome e determinate caratteristiche
alle variabili dichiarate: lo spazio di assegnazione però
è già archiviato all'avvio dell'applicazione
(o della routine se si tratta di variabili locali), infatti
anche se una variabile è dichiarata in fondo a una
routine, oppure all'interno di un costrutto If
Then,
essa è comunque creata all'inizio della routine (oppure
anche se non si verifica la condizione della If).
Del resto non avrebbe senso eseguire più volte una
dichiarazione di variabile: la variabile dichiarata resterebbe
sempre la stessa, non possono esserne create più copie
distinte ma con lo stesso nome.
L'unica istruzione di dichiarazione che può essere
eseguita è l'istruzione Redim: ma in questo caso l'istruzione
deve anche allocare lo spazio di memoria da riservare alle
matrici dichiarate, quindi è ovvio che si tratta di
codice eseguibile.
Tornando al nostro menù "Debug", il secondo
gruppo di voci è quello relativo alle espressioni di
controllo: queste sono espressioni che possono essere aggiunte
o rimosse da una lista visualizzabile nella finestra omonima
(vedi menù Visualizza) e consentono di tenere sempre
sotto controllo il loro valore; le espressioni possono essere
anche molto complesse e dipendere da più variabili.
Supponiamo ad esempio di voler eseguire il debug di questo
costrutto (tratto dalla routine mnuNew_Click del nostro Campo
Minato):
If
Mina(4 * x1 + y1).Tag > -1 Then
Mina(4 * x1 + y1).Tag = Mina(4 * x1 + y1).Tag + 1
End If |
Per
sapere se la condizione della If è verificata, occorre
conoscere il valore di x1, di y1 e della proprietà
Tag dell'elemento 4*x1+y1 del vettore Mina: per capire se
è corretto che la condizione si verifichi oppure no,
potremmo ogni volta controllare il valore di x1 e di y1, calcolare
il risultato dell'espressione 4*x1+y1, e infine verificare
che la proprietà Tag della mina corrispondente sia
maggiore di -1.
Un modo semplice per evitare tutto ciò è selezionare
l'intera espressione "Mina(4 * x1 + y1).Tag > -1"
e aggiungerla alle espressioni di controllo: nella finestra
omonima vedremo in tempo reale il valore assunto da questa
espressione, sapendo così subito se l'istruzione sottesa
dalla If sarà eseguita oppure no.
Ognuna di queste espressioni può essere modificata
tramite l'apposito comando di menù, ed è inoltre
possibile avere un riscontro immediato del valore di un'espressione
utilizzando il comando "controllo immediato", che
però non aggiunge l'espressione selezionata alla lista
di espressioni di controllo esistente.
Noi finora abbiamo usato la finestra immediata per raggiungere
lo scopo a cui è destinata la finestra delle espressioni
di controllo.
Ogni espressione è caratterizzata da un contesto,
definito quando si aggiunge l'espressione alla lista, e che
dipende sostanzialmente dall'area di validità delle
variabili contenute nell'espressione: nei punti in cui tali
variabili non sono definite, l'espressione diventa "fuori
contesto" e quindi non è possibile conoscere il
valore dell'espressione.
Volendo, è possibile aggiungere come espressione di
controllo anche una semplice variabile, ma è possibile
tenere sotto controllo tutte le variabili definite ad esempio
nel form corrente semplicemente usando la finestra "variabili
locali", che permette agevolmente di navigare la struttura
delle matrici, di copiare o modificare il valore di ogni singola
variabile definita in un form o in un modulo.
Nella parte alta della finestra è indicata la routine
alla quale appartengono le variabili visualizzate nella finestra
(le variabili locali, appunto) e c'è un pulsante con
tre puntini tramite il quale è possibile accedere allo
stack di chiamate.
Lo stack delle chiamate è sostanzialmente l'elenco
delle routine di cui l'esecuzione è cominciata ma non
è ancora finita:
quando in un punto del codice si verifica la chiamata a una
routine, questa comincia ad essere eseguita ma la routine
dalla quale è partita la chiamata non è ancora
terminata, per cui si ritrovano ad essere entrambe sullo stack;
se nella seconda routine c'è una chiamata ad una terza
routine, anche quest'ultima sarà aggiunta allo stack,
che funziona come un magazzino in cui l'ultimo lotto aggiunto
è il primo ad essere prelevato (in gergo si dice che
lo stack è di tipo LIFO: Last In,
First Out): infatti prima di terminare l'esecuzione
della prima routine è necessario che termini la seconda
routine, ma prima che termini quest'ultima è necessario
che termini la terza routine, ovvero l'ultima di cui è
cominciata l'esecuzione. Visualizzando lo stack di chiamate,
è possibile scegliere quale delle routine presenti
nello stack deve essere "attivata" per poterne consultare
le variabili locali.
Lo stack di chiamate è visibile anche indipendentemente
dalla finestra delle variabili locali, scegliendo l'apposita
voce dal menù "Visualizza" o premente ctrl+
L.
|