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.