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.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 while la 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.