Il debugger

L'inserimento di messaggi di tracciamento (e delle asserzioni) comporta tipicamente una grossa perdita di tempo (decidere dove e quali messaggi inserire, analizzare l'output, attivarli e disattivarli, ...).

I programmatori professionisti si affidano invece ai cosiddetti debugger (disinfestatori): sono programmi speciali che vengono utilizzati per eseguire un altro programma e analizzare il suo stato e comportamento durante l'esecuzione di singole istruzioni.

I moderni ambienti di sviluppo contengono debugger integrati che permettono di arrestare l'esecuzione del programma in punti precisi, ispezionare la pila di esecuzione, il contenuto di variabili locali, la stato degli oggetti creati, e molto altro ancora.

Per utilizzare un debugger basta comprendere tre concetti basilari:

  • come impostare alcuni punti di arresto (breakpoint)
  • come ispezionare il contenuto delle variabili
  • come far proseguire il programma una riga alla volta (single step debug)




Impostare i punti di arresto (breakpoint)

In Eclipse, i breakpoint si impostano con un semplice doppio click col pulsante sinistro a lato della riga desiderata (vedrete comparire un pallino azzurro). Come avvertenza generale, è sconsigliabile avere più comandi sulla stessa riga (altrimenti non sarà possibile associare breakpoint ad alcuni di essi).

Con un click del pulsante destro sopra il pallino di un breakpoint potrete impostare delle guardie avanzate sul punto di arresto scegliendo breakpoint properties e poi Common (ad esempio per fare in modo che il programma si interrompa solo quando una certa istruzione viene eseguita per la seconda volta, oppure se certe variabili hanno valori opportuni). Se vengono impostate delle guardie il pallino verrà affiancato da un punto interrogativo.

Sempre col pulsante destro del mouse, potrete disabilitare breakpoint selettivamente senza rimuoverli (il pallino diventerà bianco). Per rimuoverli invece è sufficiente un doppio click col pulsante sinistro.




Eseguire programmi in modalità debug

Una volta impostati i breakpoint ritenuti utili, per eseguire il debugger basta scegliere
    Run -> Debug As -> Java Application
oppure usare la combinazione di tasti Shift+Alt+D J

Quando viene raggiunto il primo breakpoint (senza guardia o la cui guardia sia soddisfatta), si aprirà la prospettiva di debug (previa vostra conferma), che presenta molte viste interessanti.

La vista Debug (in alto a sinistra) mostra il contenuto della pila di esecuzione, dove compaiono tutte le istanze dei vari metodi attualmente sospesi (tipicamente quello in fondo alla pila sarà il metodo main e quello in cima alla pila il metodo contenente il breakpoint che è stato raggiunto).

La vista Variables mostra le variabili locali del metodo e permette di ispezionarne il valore. Se le variabili riferiscono oggetti è possibile conoscere l'identificatore univoco del'oggetto (id = ...) e ispezionarne lo stato con un click sul triangolino che compare a sinistra del nome della variabile.

Con un click del pulsante destro sul nome della variabile è possibile modificarne il contenuto prima di riprendere l'esecuzione.




Resume, step-into e step-over

L'esecuzione può essere riattivata con modalità diverse associate alle icone che compaiono in cima alla vista di debug.

Il rettangolino giallo affiancato dal triangolo verde (Resume) riprende l'esecuzione normale fino al prossimo breakpoint impostato (o alla terminazione del programma).

Il consueto quadratino rosso (Terminate) provoca la terminazione del programma.

Sottolineiamo infine due importanti modalità single-step (indicate con diversi tipi di frecce gialle):

  • step-into che riprende l'esecuzione del programma una riga alla volta e che in caso di invocazioni di metodi porta all'interno di essi
  • step-over che riprende l'esecuzione del programma una riga alla volta senza arrestarsi all'interno di eventuali metodi invocati




Sei strategie per il debug

  1. Scoprite come riprodurre l'errore (individuate i dati in input che lo causano)

  2. Semplificate l'errore (individuate i dati più semplici possibili che lo causano)

  3. Divide et impera (eseguite il main procedendo in modalità step-over sino a quando l'anomalia si verifica: il metodo che l'ha causata è l'ultimo eseguito prima che fosse possibile riscontrare il problema, quindi possiamo ri-eseguire il debug facendo step-into in quella particolare invocazione e procedendo di nuovo con step-over sulle istruzioni del metodo, e così via)

  4. Procedete consapevolmente (durante il debug confrontate costantemente i valori correnti delle variabili con quelli che vi aspettereste)

  5. Controllate tutti i dettagli (se mentre cercate di risolvere un'anomalia incappate in un'altra non ignoratela: è preferibile prendere un appunto per il problema originale, cercare di correggere la nuova anomalia e poi tornare al problema originario)

  6. Correggete gli errori solo dopo averli compresi (soluzioni tampone possono essere rapide ma spesso hanno il grosso svantaggio di causare problemi ancora più gravi in altri punti del codice)




Esempi

Per fare pratica con l'uso del debugger consideriamo due semplici esempi riuniti nella classe TestDebug:

Calcolo del fattoriale ricorsivo. Lo scopo della demo è:

  1. mostrare come si imposta un breakpoint con guardia,
  2. mostrare come la pila di esecuzione possa contenere diverse istanze dello stesso metodo,
  3. mostrare modalità step-over e step-into,
  4. mostrare come sia possibile modificare il contenuto di variabili locali e il codice durante il debugging,
  5. modificare il codice per consentire l'uso di pacchetti di prova.

Ordinamento di un array. Lo scopo della demo è:

  1. mostrare tecniche di stack-tracing e di logging,
  2. mostrare condivisione di oggetti tra metodi diversi e effetti collaterali,
  3. mostrare allocazione dinamica di variabili locali,
  4. modificare il codice per consentire l'uso di pacchetti di prova.