2.41. Depuração

A maioria dos scripts que falham na câmara falha de uma de três formas: lançam uma exceção, produzem o valor errado ou ficam suspensos. Cada situação tem um conjunto diferente de ferramentas.

2.41.1. Ler um traceback

Quando um script lança uma exceção e nada a trata, o REPL ou o IDE imprime um traceback – um registo da cadeia de chamadas desde o script mais externo até à linha que lançou a exceção.

Um traceback lê-se de baixo para cima:

  • A linha inferior nomeia a classe da exceção e a sua mensagem (ValueError: invalid literal for int()...).

  • Cada bloco File "...", line N, in <name> acima é um fotograma – uma chamada mais profunda à medida que se sobe.

  • O fotograma mais acima é onde o script começou; o fotograma mais abaixo é onde o erro ocorreu.

Leia primeiro a parte inferior para perceber o que correu mal, e depois percorra para cima para ver como o script chegou lá. Os números de linha apontam para localizações exatas no código fonte do script.

2.41.3. Explorar um objeto

Duas funções integradas respondem à questão «o que posso fazer com isto»:

  • dir() – devolve uma lista de todos os nomes definidos num objeto: métodos, atributos, dunders, tudo.

  • help() – imprime a docstring (e no CPython, a assinatura) de uma função, método ou classe.

Use-as em conjunto: dir encontra o nome, help explica o que faz.

2.41.3.1. Encontrar um nome com dir

>>> dir([1, 2, 3])
['__add__', '__class__', '__contains__', '__delitem__',
 '__eq__', '__ge__', ..., 'append', 'clear', 'copy',
 'count', 'extend', 'index', 'insert', 'pop', 'remove',
 'reverse', 'sort']

O primeiro bloco da lista são os métodos dunder, herdados de todos os objetos; os nomes que vale a pena procurar encontram-se geralmente depois deles. dir funciona com qualquer coisa – uma classe, uma instância, um módulo, um tipo integrado:

>>> import json
>>> dir(json)
['__name__', 'dump', 'dumps', 'load', 'loads']

Esta segunda forma é a maneira de descobrir quais os nomes de nível superior que um módulo realmente expõe sem sair do REPL.

2.41.3.2. Consultar com help

Assim que dir encontrar um candidato, help descreve-o:

>>> help(str.split)
split(sep=None, maxsplit=-1)
    Return a list of the words in the string, ...

No MicroPython, help é mais simples do que no CPython – por vezes apenas a assinatura, por vezes uma docstring de uma linha, por vezes nada para funções integradas em C. Ainda assim é um lembrete rápido quando o tooltip do IDE não está à mão.

2.41.4. Quando algo fica suspenso

Um script que não retorna é mais difícil de diagnosticar do que um que lança uma exceção. Causas comuns:

  • Um ciclo while cuja condição nunca se torna falsa. Adicione uma impressão da variável do ciclo em cada iteração; se o valor não mudar, o corpo do ciclo tem um erro.

  • Uma chamada bloqueante à espera de entrada que nunca chega – uma leitura de uma fila vazia, um sleep sem fim. Delimite a chamada com impressões para ver em que linha o script está bloqueado.

  • Uma recursão infinita. O traceback quando finalmente disparar (com RecursionError) normalmente aponta diretamente para ela.

A recuperação mais eficaz para um script suspenso é o botão stop do IDE, que envia um KeyboardInterrupt ao script via USB. A interrupção aparece como um traceback na linha atualmente em execução – muitas vezes é exatamente essa a linha que não está a retornar.

Nota

Se uma suspensão resiste a todos os diagnósticos – o script parece correto, o traceback da interrupção aponta para um integrado ou para código de firmware em vez do seu script, ou o mesmo código funcionava numa versão de firmware anterior – a causa pode ser um erro de firmware e não de script. Reduza o script ao menor reprodutor que ainda fique suspenso e abra um relatório no fórum OpenMV. Inclua a versão do firmware, a placa onde foi executado e o script reduzido.

2.41.5. Remover os diagnósticos antes de distribuir

As impressões estratégicas durante o desenvolvimento são ótimas; uma centena de chamadas a print deixadas num script de produção enchem a saída e consomem heap que o trabalho real poderia estar a usar. Quando um erro estiver corrigido, remova as impressões (ou coloque-as por detrás de um indicador de depuração que pode desativar).

Para diagnósticos que devem permanecer no caminho de código a longo prazo, substitua print() pelo módulo logging. Este associa um nível a cada mensagem (debug, info, warning, error) e permite que uma única definição silencie as mais silenciosas em produção:

import logging

log = logging.getLogger("main")
log.info("starting up")
log.debug("loaded config: %s", config)
log.warning("falling back to defaults")

Definir o nível do logger para logging.WARNING faz com que as chamadas info e debug não custem praticamente nada (a string da mensagem nunca é construída), sem ter de comentar linhas. Isto torna o logging a ferramenta certa para diagnósticos permanentes; um print simples é suficiente para os temporários.