2.41. Debugging#
La maggior parte degli script che falliscono sulla camera fallisce in uno di tre modi: sollevano un’eccezione, producono un valore errato oppure si bloccano. Ognuno richiede un diverso insieme di strumenti.
2.41.1. Leggere un traceback#
Quando uno script solleva un’eccezione e nessuno la gestisce, il REPL o l’IDE stampa un traceback – una traccia della catena di chiamate, dallo script più esterno fino alla riga che ha sollevato l’eccezione.
Un traceback si legge dal basso verso l’alto:
La riga in fondo indica la classe dell’eccezione e il suo messaggio (
ValueError: invalid literal for int()...).Ogni blocco
File "...", line N, in <name>sopra di essa è un frame – una chiamata più in profondità man mano che si sale.Il frame in cima è il punto in cui lo script è iniziato; il frame in fondo è il punto in cui l’errore si è verificato.
Leggi prima la riga in fondo per capire cosa è andato storto, poi risali per vedere come lo script è arrivato lì. I numeri di riga indicano le posizioni esatte nel sorgente dello script.
2.41.2. Debugging con print#
Il modo più rapido per scoprire cosa sta facendo uno script è stampare i valori sospetti. Tre funzioni integrate rendono le stampe più utili:
repr()– restituisce la rappresentazione testuale in stile sviluppatore di un valore.print(repr(value))distingue"5"da5eNoneda"None", cosa che un sempliceprint()non può fare.type()– restituisce la classe di un valore.print(type(value))è il modo per scoprire se la variabile che «dovrebbe essere un int» è in realtà una stringa.len()– la lunghezza di una sequenza o di una collezione. Una frazione sorprendentemente grande dei bug sono problemi di off-by-one o di dimensioni non corrispondenti.
print("got:", repr(value), "type:", type(value), "len:", len(value))
Inserisci una print all’interno di ogni ramo che ti interessa – entrambi i rami di un if, ogni blocco except, il corpo di un ciclo che sospetti venga eseguito zero volte. Il costo è una riga di output; il valore è scoprire se il percorso di codice che pensi venga eseguito è davvero quello che viene eseguito.
2.41.3. Esplorare un oggetto#
Due funzioni integrate rispondono alla domanda «cosa posso fare con questa cosa»:
dir()– restituisce un elenco di ogni nome definito su un oggetto: metodi, attributi, dunder, tutto quanto.help()– stampa la docstring (e, su CPython, la firma) di una funzione, un metodo o una classe.
Usale insieme: dir trova il nome, help spiega cosa fa.
2.41.3.1. Trovare un nome con dir#
>>> dir([1, 2, 3])
['__add__', '__class__', '__contains__', '__delitem__',
'__eq__', '__ge__', ..., 'append', 'clear', 'copy',
'count', 'extend', 'index', 'insert', 'pop', 'remove',
'reverse', 'sort']
La prima parte dell’elenco è composta dai metodi dunder, ereditati da ogni oggetto; i nomi che vale la pena cercare di solito vengono dopo di essi. dir funziona su qualsiasi cosa – una classe, un’istanza, un modulo, un tipo integrato:
>>> import json
>>> dir(json)
['__name__', 'dump', 'dumps', 'load', 'loads']
Quella seconda forma è il modo per scoprire quali nomi di livello superiore un modulo espone effettivamente senza uscire dal REPL.
2.41.3.2. Consultarlo con help#
Una volta che dir ha fatto emergere un candidato, help lo descrive:
>>> help(str.split)
split(sep=None, maxsplit=-1)
Return a list of the words in the string, ...
Su MicroPython, help è più essenziale che su CPython – a volte solo la firma, a volte una docstring di una riga, a volte nulla per le funzioni C integrate. È comunque un rapido promemoria quando il tooltip dell’IDE non è a portata di mano.
2.41.4. Quando qualcosa si blocca#
Uno script che non restituisce il controllo è più difficile da diagnosticare di uno che solleva un’eccezione. Cause comuni:
Un ciclo
whilela cui condizione non diventa mai falsa. Aggiungi una print della variabile del ciclo a ogni iterazione; se il valore non cambia, il corpo del ciclo ha un bug.Una chiamata bloccante in attesa di un input che non arriva mai – una lettura da una coda vuota, una sleep senza fine. Circonda la chiamata con delle print per vedere su quale riga lo script è bloccato.
Una ricorsione infinita. Il traceback che alla fine si verifica (con
RecursionError) di solito la indica con precisione.
Il recupero più efficace per uno script bloccato è il pulsante stop dell’IDE, che invia un KeyboardInterrupt allo script tramite USB. L’interrupt emerge come un traceback alla riga attualmente in esecuzione – spesso esattamente la riga che non restituisce il controllo.
Nota
Se un blocco resiste a ogni diagnosi – lo script sembra corretto, il traceback dell’interrupt punta a una funzione integrata o al codice del firmware anziché al tuo script, oppure lo stesso codice funzionava su una build precedente del firmware – la causa potrebbe essere un bug del firmware anziché un bug dello script. Riduci lo script al più piccolo esempio riproducibile che continua a bloccarsi e apri una segnalazione sul forum di OpenMV. Includi la versione del firmware, la scheda su cui è stato eseguito e lo script ridotto.
2.41.5. Rimuovi le diagnostiche prima della distribuzione#
Le print strategiche durante lo sviluppo sono ottime; cento chiamate a print lasciate in uno script di produzione ingombrano l’output e usano heap che il lavoro reale potrebbe usare. Quando un bug è risolto, rimuovi le print (oppure proteggile dietro un flag di debug che puoi disattivare).
Per le diagnostiche che devono rimanere nel percorso di codice a lungo termine, passa da print() al modulo logging. Esso associa un livello a ogni messaggio (debug, info, warning, error) e consente a una singola impostazione di silenziare quelli meno importanti in produzione:
import logging
log = logging.getLogger("main")
log.info("starting up")
log.debug("loaded config: %s", config)
log.warning("falling back to defaults")
Impostare il livello del logger su logging.WARNING rende le chiamate info e debug praticamente a costo zero (la stringa del messaggio non viene mai costruita), senza dover commentare le righe. Questo rende logging lo strumento giusto per le diagnostiche permanenti; la print grezza va bene per quelle usa e getta.