Appendix A DebugNell’operazione di rimozione degli errori da un programma, è opportuno distinguere i vari tipi di errore in modo da poterli rintracciare più velocemente:
La prima cosa da fare nel debug è capire con che tipo di errore abbiamo a che fare. Anche se i paragrafi che seguono sono organizzati per tipo di errore, alcune tecniche sono applicabili in più di una situazione. A.1 Errori di sintassiGli errori di sintassi sono in genere facili da sistemare, una volta capito in cosa consistono. Purtroppo, il messaggio di errore spesso è poco utile. Quelli più comuni sono: SyntaxError: invalid syntax e SyntaxError: invalid token, nessuno dei quali è molto esplicativo. In compenso, il messaggio comunica il punto del programma dove si è verificato il problema. Più precisamente, dice dove Python ha notato il problema, che non è necessariamente il punto in cui si trova l’errore. A volte l’errore è prima del punto indicato dal messaggio, spesso nella riga precedente. Se state costruendo il programma in modo incrementale, è molto probabile che l’errore sia nell’ultima riga che avete aggiunto. Se state copiando il codice da un libro, cominciate confrontando attentamente il vostro codice con quello del libro. Controllate ogni carattere. Ricordate però che anche il libro può essere sbagliato, e se vedete qualcosa che somiglia a un errore di sintassi, potrebbe esserlo. Ecco alcuni modi di evitare i più comuni errori di sintassi:
Se nulla di tutto questo funziona, continuate con il paragrafo seguente... A.1.1 Continuo a fare modifiche ma non cambia nulla.Se l’interprete dice che c’è un errore ma non lo trovate, può essere che voi e l’interprete non stiate guardando lo stesso codice. Controllate il vostro ambiente di programmazione per assicurarvi che il programma che state modificando sia proprio quello che Python sta tentando di eseguire. Se non ne siete certi, provate a mettere deliberatamente un evidente errore di sintassi all’inizio del programma e rieseguitelo. Se l’interprete non trova l’errore, non state eseguendo il nuovo codice. Alcune cause possibili:
Se vi bloccate e non riuscite a capire cosa sta succedendo, un’alternativa è ripartire con un nuovo programma come “Ciao, mondo!”, e accertarvi di avere un programma ben conosciuto da eseguire. Quindi, aggiungete gradualmente i pezzi del programma originale a quello nuovo. A.2 Errori di runtimeUna volta che il programma è sintatticamente corretto, Python può leggerlo e quantomeno cominciare ad eseguirlo. Cosa può succedere di spiacevole? A.2.1 Il programma non fa assolutamente nulla.È il problema più frequente se il vostro file consiste di funzioni e classi, ma in realtà non invoca alcuna funzione per avviare l’esecuzione. Può essere una cosa intenzionale, se intendete utilizzarlo solo come modulo da importare allo scopo di fornire le classi e le funzioni. Se non è questo il caso, assicuratevi che nel programma ci sia una chiamata di funzione e che il flusso di esecuzione la raggiunga (vedete anche il paragrafo “Flusso di esecuzione” più avanti). A.2.2 Il programma si blocca.Se un programma si blocca e pare che non stia succedendo nulla, spesso vuol dire che è incappato in un ciclo infinito o in una ricorsione infinita.
Ciclo infinitoSe sospettate che vi sia un ciclo infinito e di sapere quale ciclo in particolare stia causando il problema, aggiungete un’istruzione di stampa alla fine del ciclo in modo da visualizzare il valore delle variabili nella condizione e il valore della condizione. Per esempio: while x > 0 and y < 0 : # fa qualcosa con x # fa qualcosa con y print('x: ', x) print('y: ', y) print("condizione: ", (x > 0 and y < 0)) Ora, eseguendo il programma, vedrete tre righe di output per ogni ripetizione del ciclo. All’ultimo passaggio, la condizione dovrebbe risultare False. Se il ciclo continua, vedrete comunque i valori di x e y, e potrete capire meglio il motivo per cui non vengono aggiornati correttamente. Ricorsione infinitaIl più delle volte, una ricorsione infinita provoca un errore di Maximum recursion depth exceeded, dopo che il programma è stato in esecuzione per qualche istante. Se sospettate che una funzione stia provocando una ricorsione infinita, controllate innanzitutto che esista un caso base. Deve esistere un qualche tipo di condizione che provoca il ritorno della funzione senza generare un’altra chiamata ricorsiva. Se no, occorre ripensare l’algoritmo e stabilire un caso base. Se il caso base c’è ma sembra che il programma non lo raggiunga mai, aggiungete un’istruzione di stampa all’inizio della funzione, in modo da visualizzare i parametri. Ora, eseguendo il programma vedrete alcune righe di output con i valori dei parametri per ogni chiamata della funzione. Se i parametri non tendono verso il caso base, avrete qualche spunto per capire il perché. Flusso di esecuzioneSe non siete sicuri di come il flusso di esecuzione si muova dentro il vostro programma, aggiungete delle istruzioni di stampa all’inizio di ogni funzione con un messaggio del tipo “sto eseguendo la funzione pippo”, dove pippo è il nome della funzione. Ora, eseguendo il programma, verrà stampata una traccia di ogni funzione che viene invocata. A.2.3 Quando eseguo il programma è sollevata un’eccezione.Se qualcosa non va durante l’esecuzione, Python stampa un messaggio che include il nome dell’eccezione, la riga del programma dove il problema si è verificato, ed un traceback. Il traceback identifica la funzione che era in esecuzione, quella che l’aveva chiamata, quella che a sua volta l’aveva chiamata e così via. In altre parole, traccia la sequenza di chiamate di funzione che hanno condotto alla situazione attuale. Include anche il numero di riga del file dove è avvenuta ciascuna chiamata. Innanzitutto bisogna esaminare il punto del programma dove è emerso l’errore e vedere se si riesce a capire cosa è successo. Questi sono alcuni dei più comuni errori in esecuzione:
Il debugger Python (pdb) è utile per catturare le eccezioni, perché vi permette di esaminare lo stato del programma immediatamente prima dell’errore. Potete leggere approfondimenti su pdb sul sito https://docs.python.org/3/library/pdb.html. A.2.4 Ho aggiunto talmente tante istruzioni di stampa che sono sommerso di output.Una controindicazione delle istruzioni di stampa nel debugging è che si rischia di essere inondati di messaggi. Ci sono due modi di procedere: o semplificate l’output o semplificate il programma. Per semplificare l’output, potete rimuovere o commentare le istruzioni di stampa superflue, o accorparle, o dare all’output un formato più leggibile. Per semplificare il programma, ci sono diverse opzioni. Primo, riducete il problema che il programma sta affrontando. Per esempio, se state cercando una lista, cercatene una piccola. Se il programma riceve input dall’utente, dategli il dato più semplice che causa il problema. Secondo, ripulite il programma. Togliete il codice inutile e riorganizzate il programma in modo da renderlo il più facile possibile da leggere. Per esempio, se sospettate che l’errore sia in una parte profondamente nidificata del programma, cercate di riscrivere quella parte con una struttura più semplice. Se sospettate di una corposa funzione, provate a suddividerla in funzioni più piccole e a testarle separatamente. Spesso, il procedimento di ricercare il caso di prova più circoscritto porta a trovare l’errore. Se riscontrate che il programma funziona in un caso ma non in un altro, questo vi dà una chiave di lettura di quello che sta succedendo. Allo stesso modo, riscrivere un pezzo di codice vi può aiutare a trovare errori insidiosi. Una modifica che pensavate ininfluente sul programma, e che invece influisce, può farvi trovare il bandolo della matassa. A.3 Errori di semanticaGli errori di semantica sono i più difficili da affrontare, perché l’interprete non fornisce informazioni su ciò che non va. Sapete per certo solo quello che il programma dovrebbe fare, ma non fa. Innanzitutto occorre stabilire una connessione logica tra il testo del programma e il comportamento che vedete. Vi serve un’ipotesi di cosa sta facendo in realtà il programma. Quello che rende difficili le cose è che i computer eseguono le operazioni in tempi rapidissimi. Vi capiterà di desiderare di poter rallentare il programma ad una velocità umana, e in effetti con alcuni strumenti di debug potete farlo. Ma il tempo che ci vuole per inserire alcune istruzioni di stampa ben calibrate è spesso più breve di quello necessario a impostare il debugger, inserire e togliere i punti di interruzione, ed eseguire i passi per portare il programma dove si verifica l’errore . A.3.1 Il mio programma non funziona.Dovreste porvi queste domande:
Per programmare, vi serve un modello mentale di come funziona il programma. Se scrivete un programma che non fa quello che volete, spesso il problema non è nel programma ma nel vostro modello mentale. Il modo migliore per correggere il vostro modello mentale è spezzare il programma nei suoi componenti (di solito funzioni e metodi) e provare indipendentemente ogni singolo componente. Quando avrete trovato la discrepanza tra il vostro modello e la realtà, potrete risolvere il problema. Naturalmente, dovreste costruire e provare i componenti mentre state sviluppando il programma. Così, se vi imbattete in un problema, dovrebbe esserci solo una piccola quantità di codice di cui occorre verificare l’esattezza. A.3.2 Ho una grande e complicata espressione che non fa quello che voglio.Scrivere espressioni complesse va bene fino a quando restano leggibili, ma poi possono diventare difficili da correggere. Un buon consiglio è di spezzare un’espressione complessa in una serie di assegnazioni a variabili temporanee. Per esempio: self.mani[i].aggiungiCarta(self.mani[self.trovaVicino(i)].togliCarta()) Può essere riscritta così: vicino = self.trovaVicino(i) cartaScelta = self.mani[vicino].togliCarta() self.mani[i].aggiungiCarta(cartaScelta) La versione esplicita è più leggibile perché i nomi delle variabili aggiungono informazione, ed è più facile da correggere perché potete controllare i tipi delle variabili intermedie e visualizzare il loro valore. Un altro problema che si verifica con le grandi espressioni è che l’ordine di valutazione delle operazioni può essere diverso da quello che pensate. Per esempio, nel tradurre in Python l’espressione x/2 π, potreste scrivere: y = x / 2 * math.pi È sbagliato, perché moltiplicazione e divisione hanno la stessa priorità e vengono calcolate da sinistra verso destra; quindi quell’espressione calcola x π / 2. Un buon modo di fare il debug delle espressioni è aggiungere delle parentesi per rendere esplicito l’ordine delle operazioni. y = x / (2 * math.pi) Usate le parentesi ogni volta che non siete certi dell’ordine delle operazioni. Non solo il programma sarà corretto (nel senso che farà quello che volete), sarà anche più leggibile da altre persone che non hanno imparato a memoria l’ordine delle operazioni A.3.3 Ho una funzione che non restituisce quello che voglio.Se avete un’istruzione return associata ad un’espressione complessa, non avete modo di stampare il risultato prima del ritorno. Di nuovo, usate una variabile temporanea. Per esempio, anziché: return self.mani[i].togliUguali() potete scrivere: conta = self.mani[i].togliUguali() return conta Ora potete stampare il valore di conta prima che sia restituito. A.3.4 Sono proprio bloccato e mi serve aiuto.Per prima cosa, staccatevi dal computer per qualche minuto. I computer emettono onde che influenzano il cervello, causando questi sintomi:
Se accusate qualcuno di questi sintomi, alzatevi e andate a fare una passeggiata. Quando vi siete calmati, ripensate al programma. Cosa sta facendo? Quali sono le possibili cause del suo comportamento? Quand’era l’ultima volta che avete avuto un programma funzionante, e cosa avete fatto dopo? A volte per trovare un bug è richiesto solo del tempo. Io trovo spesso bug mentre non sono al computer e distraggo la mente. Tra i posti migliori per trovare bug: in treno; sotto la doccia; a letto appena prima di addormentarsi. A.3.5 No, ho davvero bisogno di aiuto.Capita. Anche i migliori programmatori a volte si bloccano. Magari avete lavorato talmente a lungo sul programma da non riuscire a vedere un errore. Un paio di occhi freschi sono quello che ci vuole. Prima di rivolgervi a qualcun altro, dovete fare dei preparativi. Il vostro programma dovrebbe essere il più semplice possibile, e dovete fare in modo di lavorare sul più circoscritto input che causa l’errore. Dovete posizionare delle istruzioni di stampa nei posti adatti (e l’output che producono deve essere comprensibile). Il problema va compreso abbastanza bene da poterlo descrivere in poche parole. Quando portate qualcuno ad aiutarvi, assicuratevi di dargli tutte le informazioni che servono:
Quando trovate l’errore, prendetevi un attimo di tempo per pensare cosa avreste potuto fare per trovarlo più velocemente: la prossima volta che incontrerete qualcosa di simile, vi sarà più facile scoprire l’errore. Ricordate che lo scopo non è solo far funzionare il programma, ma imparare a farlo funzionare. |
ContributeIf you would like to make a contribution to support my books, you can use the button below. Thank you!
Are you using one of our books in a class?We'd like to know about it. Please consider filling out this short survey.
|