Oggi cominciamo un argomento al quale non sempre viene riserva la giusta importanza nei manuali di programmazione: il debug del codice. Spesso i manuali trtano il debug in uno degli ultimi capitoli, come se fosse una delle ultime cose che un programmore 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 programmore conoscesse a memoria tutte le istruzioni di un linguaggio. Infti il debug è la procedura che permette a uno sviluppore 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 sviluppo 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ù è dedico al debug del codice: al suo interno ci sono varie voci che consentono allo sviluppore di eseguire la propria applicazione istruzione dopo istruzione, di vedere in ogni momento il valore di tutte le variabili utilizze dall'applicazione, di interrompere o modificare il flusso di esecuzione delle istruzioni. Altre funzionalità relive al debug si trovano nel menù Visualizza, ed è possibile anche utilizzare una barra degli strumenti dedica al debug: basta cliccare col pulsante destro del mouse sulla barra degli strumenti standard e selezionare la voce "debug". Finora noi ci siamo limiti a una sorta di primitivo debug utilizzando la finestra Immedia 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 qutro 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 è associa 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 evidenzia in giallo la riga di dichiarazione della routine dell'evento load del form principale dell'applicazione: infti questo è normalmente il primo evento che viene genero all'avvio di un'applicazione; questo è quello che succede, ad esempio, se prove ad eseguire passo-passo il progetto del "campo mino" affronto qualche lezione fa. Quando si è in modalità interruzione, è possibile conoscere il valore di ogni variabile posizionando semplicemente il puntore del mouse sul nome della variabile che interessa: comparirà un tooltip col valore corrente della variabile. Poiché quando una riga di codice è evidenzia 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 btitura 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à controllo e che sappiamo funzionare bene, prima di arrivare al punto in cui pensiamo che si verifichi un errore e che quindi va controllo tentamente. Consideriamo ad esempio il seguente codice :
Prive Sub Form_Load()
Dim i as Integer
Call Inizializza
Call Ridimensiona
i = Conta()
MsgBox i, "Contore"
End Sub
Nell'evento load del form sono richiame due subroutine e una funzione, la quale restituisce un integer assegno alla variabile i: il valore di tale variabile è poi visualizzo 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 Mismch". In apparenza, basta premere sei volte il tasto F8 per giungere all'istruzione incrimina; in realtà, quando viene evidenzia una riga che richiama una routine, premendo F8 si dà inizio all'esecuzione di quella routine e saranno man mano evidenzie 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"), tivabile 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 evidenzia in giallo è un'istruzione semplice, sarà eseguita quell'istruzione come nella modalità passo-passo; se invece è una chiama 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 chiama, 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 (evidenzia in giallo) e l'uscita dalla routine siano eseguite in un colpo solo.
Spesso capita che si voglia esaminare con tenzione 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 utilizza (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à automicamente in modalità interruzione evidenziando in giallo la riga su cui abbiamo imposto il breakpoint, consentendoci così di effettuare il debug senza ulteriori perdite di tempo. Il tasto per tivare 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 risulto 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 contrassegne da un breakpoint: ad esempio non possono esserlo le etichette utilizze 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 determine carteristiche alle variabili dichiare: lo spazio di assegnazione però è già archivio all'avvio dell'applicazione (o della routine se si trta di variabili locali), infti anche se una variabile è dichiara in fondo a una routine, oppure all'interno di un costrutto if…then, essa è comunque crea 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 dichiara resterebbe sempre la stessa, non possono esserne cree 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 mrici dichiare, quindi è ovvio che si trta di codice eseguibile.
Tornando al nostro menù "Debug", il secondo gruppo di voci è quello relivo 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 (trto dalla routine mnuNew_Click del nostro Campo Mino):
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 è verifica, 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 risulto 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 modifica tramite l'apposito comando di menù, ed è inoltre possibile avere un riscontro immedio del valore di un'espressione utilizzando il comando "controllo immedio", che però non aggiunge l'espressione seleziona alla lista di espressioni di controllo esistente. Noi finora abbiamo uso la finestra immedia per raggiungere lo scopo a cui è destina la finestra delle espressioni di controllo. Ogni espressione è carterizza 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 mrici, di copiare o modificare il valore di ogni singola variabile definita in un form o in un modulo. Nella parte alta della finestra è indica la routine alla quale appartengono le variabili visualizze nella finestra(le variabili locali, appunto) e c'è un pulsante con tre puntini tramite il quale è possibile accedere allo stack di chiame. Lo stack delle chiame è sostanzialmente l'elenco delle routine di cui l'esecuzione è comincia ma non è ancora finita: quando in un punto del codice si verifica la chiama a una routine, questa comincia ad essere eseguita ma la routine dalla quale è partita la chiama non è ancora termina, per cui si ritrovano ad essere entrambe sullo stack; se nella seconda routine c'è una chiama 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 prelevo (in gergo si dice che lo stack è di tipo LIFO: Last In, First Out): infti 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 è comincia l'esecuzione. Visualizzando lo stack di chiame, è possibile scegliere quale delle routine presenti nello stack deve essere "tiva" per poterne consultare le variabili locali. Lo stack di chiame è visibile anche indipendentemente dalla finestra delle variabili locali, scegliendo l'apposita voce dal menù "Visualizza" o premente ctrl+L.