In un programma possono manifestarsi diversi tipi di errore ed è sempre utile saperli distinguere per poterli rimuovere velocemente:
Il primo passo per rimuovere un errore è capire con che tipo di errore hai a che fare. Sebbene le sezioni seguenti siano organizzate in base al tipo di errore alcune tecniche sono applicabili a più di una situazione.
Gli errori di sintassi sono facili da eliminare quando hai capito dov'è il problema. Sfortunatamente i messaggi di errore non sono sempre utili: i messaggi più comuni sono SyntaxError: invalid syntax (sintassi non valida) e SyntaxError: invalid token (token non valido) che di per sé non sono di grande aiuto.
Il messaggio fornisce indicazioni sulla riga di programma dove il problema si verifica, anche se questa riga in realtà è il punto in cui Python si è accorto dell'errore e non necessariamente dove questo si trova. Molto spesso l'errore è nella riga precedente rispetto a quella indicata dal messaggio.
Se stai scrivendo in modo incrementale il tuo programma è facile indicare immediatamente dove risiede il problema dato che deve trovarsi nelle ultime righe che hai aggiunto.
Se stai copiando il codice da un libro controlla accuratamente se l'originale è uguale a ciò che hai scritto. Controlla ogni carattere senza dimenticare che il codice riportato dal libro potrebbe anche essere sbagliato. Se vedi qualcosa che sembra un errore di sintassi, probabilmente lo è.
Ecco qualche sistema per evitare gli errori di sintassi più comuni:
Se malgrado i controlli non hai ottenuto risultati passa alla sezione seguente.
Se il compilatore si ostina a dire che c'è un errore e tu non lo vedi probabilmente state guardando due diversi pezzi di codice. Controlla se il programma su cui stai lavorando è lo stesso che Python cerca di eseguire. Se non sei sicuro introduci un errore di sintassi proprio all'inizio ed eseguilo di nuovo: se il compilatore non vede il nuovo errore di sintassi con ogni probabilità state guardando due cose diverse.
Se questo accade, un approccio standard è quello di ricominciare da zero con un nuovo programma tipo "Hello, World!" e controllare che questo venga eseguito. Poi gradualmente aggiungi pezzi di codice fino ad arrivare a quello definitivo.
Quando il tuo programma è sintatticamente corretto Python lo può importare ed eseguire. Cosa potrebbe andare storto?
Questo problema è comune quando il tuo codice consiste di classi e funzioni ma non c'è del codice che inizia l'esecuzione con una chiamata. Questo può essere un comportamento voluto quando il tuo codice deve essere importato in un altro modulo per fornire classi e funzioni.
Se questo non è intenzionale controlla di invocare una funzione per iniziare l'esecuzione o eseguila direttamente dal prompt interattivo. Vedi anche la sezione "Flusso di esecuzione" in seguito.
Se il programma si ferma e sembra di essere bloccato senza fare nulla diciamo che è "in blocco". Spesso questo significa che il flusso del programma è all'interno di un ciclo o di una ricorsione infiniti.
Quando hai a che fare con cicli sospetti puoi sempre aggiungere alla fine del corpo del ciclo un'istruzione print per stampare i valori delle variabili usate nella condizione ed il valore della condizione.
Per esempio:
while x > 0 and y < 0 :
# fai qualcosa con x
# fai qualcosa con y
print "x: ", x
print "y: ", y
print "condizione: ", (x > 0 and y < 0)
Quando esegui il programma sono stampate tre righe ogni volta che viene reiterato il ciclo. La condizione d'uscita sarà falsa solo nell'ultima esecuzione. Se il ciclo continua ad essere eseguito potrai controllare i valori di x e y e forse potrai capire perché non vengono aggiornati correttamente.
La maggior parte delle volte una ricorsione infinita porterà all'esaurimento della memoria disponibile: il programma funzionerà finché ci sarà memoria disponibile, poi verrà mostrato un messaggio d'errore Maximum recursion depth exceeded.
Se sospetti che una funzione o un metodo stiano causando una ricorsione infinita, inizia a controllare il caso base: deve sempre essere presente una condizione all'interno della funzione che possa ritornare senza effettuare un'ulteriore chiamata alla funzione stessa. Se il caso base non è presente è il caso di ripensare l'algoritmo.
Se è presente il caso base ma sembra che il flusso di programma non lo raggiunga mai aggiungi un'istruzione print all'inizio della funzione o metodo per stamparne i parametri. Se durante l'esecuzione i parametri non si muovono verso il caso base probabilmente significa che la ricorsione non funziona.
Se non sei sicuro che il flusso di esecuzione si stia muovendo lungo il programma aggiungi un'istruzione print all'inizio di ogni funzione per stampare un messaggio del tipo "entro nella funzione xxx" dove xxx è il nome della funzione. Quando il programma è in esecuzione avrai una traccia del suo flusso.
Se qualcosa va storto durante l'esecuzione Python stampa un messaggio che include il nome dell'eccezione, la linea di programma dove si è verificato il problema e una traccia.
La traccia identifica la funzione che si stava eseguendo al momento dell'errore, la funzione che l'aveva chiamata, la funzione che aveva chiamato quest'ultima e così a ritroso fino ad arrivare al livello superiore. Mostra cioè il cammino che ha portato all'interno della funzione malfunzionante. Come ulteriori informazioni sono anche indicate le linee di programma dove ciascuna funzione viene chiamata.
Il primo passo è quello di esaminare il posto nel programma dove l'errore si è verificato e cercare di capire cos'è successo. Questi sono i più comuni errori in esecuzione:
Uno dei problemi con l'uso dell'istruzione print durante il debug è che puoi rimanere letteralmente sommerso da una valanga di messaggi. Ci sono due modi per procedere: semplificare le stampe o semplificare il programma.
Per semplificare le stampe puoi rimuovere o commentare le istruzioni print che non servono più, combinarle o formattare la stampa per ottenere una forma più semplice da leggere.
Per semplificare il programma ci sono parecchie cose che puoi fare. Prima di tutto riduci il programma per farlo lavorare su un piccolo insieme di dati. Se stai ordinando un array usa un array piccolo. Se il programma accetta un inserimento di dati dall'operatore cerca il più piccolo pacchetto di dati che genera il problema.
In secondo luogo ripulisci il programma. Rimuovi il codice morto e riorganizza il programma per renderlo il più leggibile possibile. Se sospetti che il problema risieda in una parte profondamente annidata del codice prova a riscrivere quella parte in modo più semplice. Se sospetti di una funzione complessa prova a dividerla in funzioni più piccole da testare separatamente.
Spesso il solo processo di trovare un insieme di dati che causa il problema ti porta a scoprirne la causa. Se trovi che il programma funziona in una situazione ma non in un'altra questo ti dà un notevole indizio di cosa stia succedendo.
Riscrivere un pezzo di codice può aiutarti a trovare piccoli bug difficili da individuare soprattutto se fai un cambiamento nel codice che credi non vada ad influire nel resto del programma e invece lo fa.
Gli errori di semantica sono i più difficili da scovare perché il compilatore e l'interprete non forniscono informazioni riguardo che cosa ci sia di sbagliato nel tuo programma. L'unica cosa che sai è ciò che il programma dovrebbe fare e che questo non è ciò che il programma effettivamente fa.
Il primo passo è quello di comprendere la connessione tra il codice ed il comportamento che stai osservando, con la conseguente creazione di ipotesi per giustificare ciò che vedi. Una delle cose che rendono il tutto così difficile è il fatto che il computer sia così veloce.
Spesso ti capiterà di desiderare di poter rallentare il programma fino ad una velocità più "umana" ed effettivamente questo è ciò che fanno alcuni programmi appositamente studiati per il debug. Il tempo trascorso a inserire qualche istruzione print ben piazzata è comunque breve se confrontato con tutta la procedura che occorre mettere in atto per configurare opportunamente il debugger, per inserire ed eliminare i punti di interruzione nel programma e per verificare passo per passo tutta l'esecuzione del programma.
Dovresti farti qualche domanda:
Per poter programmare devi avere un modello mentale di come il programma lavora: se ottieni un programma che non si comporta come desideri spesso la colpa non è nel programma in sé ma nel tuo modello mentale.
Il modo migliore per correggere il tuo modello mentale è quello di spezzare i suoi componenti (di solito le funzioni ed i metodi) e testare ogni componente in modo indipendente. Quando hai trovato la discrepanza tra il tuo modello e la realtà puoi risolvere il problema.
Dovresti costruire e testare i componenti man mano che sviluppi il programma così ti troveresti, in caso di problemi, soltanto con piccole parti di codice da controllare.
Scrivere espressioni complesse va bene finché queste sono leggibili ma ricorda che possono rendere problematico il debug. È sempre una buona norma spezzare un'espressione complessa in una serie di assegnazioni anche usando variabili temporanee.
Per esempio:
self.Mano[i].AggiungeCarta \
(self.Mano[self.TrovaVicino(i)].ProssimaCarta())
Può essere riscritta come:
Vicino = self.TrovaVicino(i)
Prossima = self.Mano[Vicino].ProssimaCarta()
self.Mano[i].AggiungeCarta (Prossima)
La versione esplicita è più semplice da leggere poiché i nomi delle variabili forniscono una documentazione aggiuntiva ed è anche più semplice da controllare in fase di debug dato che puoi stampare i valori delle variabili intermedie.
Un altro problema collegato alle espressioni complesse è che talvolta l'ordine di valutazione può non essere ciò che ci si aspetta. Per tradurre l'espressione
in Python devi scrivere:
x 2 pi
y = x / 2 * math.pi;
Questo non è corretto perché moltiplicazione e divisione hanno la stessa precedenza e sono valutate da sinistra a destra. Così questa espressione viene valutata x pi/2.
Un buon sistema per effettuare il debug delle espressioni è aggiungere le parentesi per rendere esplicita la valutazione:
y = x/(2*math.pi);
Quando non sei sicuro dell'ordine di valutazione usa le parentesi. Non solo il programma sarà corretto ma sarà anche più leggibile da parte di chi non ha memorizzato le regole di precedenza.
Se hai un'istruzione return con un'espressione complessa non hai modo di stampare il valore restituito da una funzione. Anche stavolta è il caso di usare una variabile temporanea. Invece di:
return self.Mano[i].TrisRimossi()
puoi scrivere:
Conteggio = self.Mano[i].TrisRimossi()
return Conteggio
Ora hai modo di stampare il valore di Conteggio prima di ritornare dalla funzione.
Prova innanzitutto a staccarti dal computer per qualche minuto. I computer emettono onde elettromagnetiche che influenzano il cervello causando un bel po' di effetti spiacevoli:
Se credi di soffrire di qualcuno di questi sintomi alzati e vatti a fare quattro passi. Quando ti sei calmato torna a pensare al programma. Cosa sta facendo? Quali sono le possibili cause? Quand'è stata l'ultima volta che si è comportato a dovere? Cos'è stato fatto in seguito?
Talvolta è necessario parecchio tempo per trovare un bug ed è molto più efficace una ricerca fatta dopo aver lasciato sgombra la mente per qualche tempo. Alcuni tra i posti migliori per trovare i bug (senza bisogno di un computer!) sono i treni, le docce ed il letto, appena prima di addormentarsi.
Se il problema si verifica in ufficio di venerdì pomeriggio fai finta di lavorarci, ma pensa ad altro: non fare modifiche che potresti rimpiangere il lunedì mattina...
Capita. Anche i migliori programmatori a volte rimangono bloccati: qualche volta lavori su un programma così a lungo che non riesci più a vedere l'errore. due occhi "freschi" sono ciò che ci vuole.
Prima di tirare dentro qualcun altro nella caccia al bug devi essere sicuro di aver esaurito ogni possibile tecnica qui descritta. Il tuo programma dovrebbe essere il più semplice possibile e dovresti lavorare sul più piccolo insieme di dati che causa il problema. Dovresti avere una serie di istruzioni print nei posti appropriati con una stampa comprensibile dei rispettivi valori di controllo. Dovresti aver capito il problema tanto da poterlo esprimere in modo conciso.
Quando chiedi l'aiuto di qualcuno ricorda di dare tutte le informazioni di cui può aver bisogno:
Quando hai trovato il bug fermati un secondo e cerca di capire come avresti potuto trovarlo più in fretta. La prossima volta che riscontrerai un comportamento simile, questo sarà molto utile.