2.41. Depuração

A maioria dos scripts que falham na câmera falha de uma de três formas: levantam uma exceção, produzem o valor errado ou travam. Cada caso tem um conjunto diferente de ferramentas.

2.41.1. Lendo um traceback

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

Um traceback é lido de baixo para cima:

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

  • Cada bloco File "...", line N, in <name> acima dela é um frame – uma chamada mais profunda conforme você sobe.

  • O frame bem no topo é onde o script começou; o frame bem na base é onde o erro disparou.

Leia primeiro a base para descobrir o que deu errado e, em seguida, suba para ver como o script chegou lá. Os números de linha apontam para os locais exatos no código-fonte do script.

2.41.3. Explorando um objeto

Duas funções embutidas respondem “o que posso fazer com esta coisa”:

  • dir() – retorna uma lista de todo nome definido em um 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 ele faz.

2.41.3.1. Encontrando 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 trecho da lista são métodos dunder, herdados de todo objeto; os nomes que valem a pena procurar geralmente vêm depois deles. dir funciona em qualquer coisa – uma classe, uma instância, um módulo, um tipo embutido:

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

Essa segunda forma é como descobrir quais nomes de nível superior um módulo realmente expõe sem sair do REPL.

2.41.3.2. Consultando com help

Depois que dir revelou um candidato, help o descreve:

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

No MicroPython, help é mais enxuto do que no CPython – às vezes apenas a assinatura, às vezes uma docstring de uma linha, às vezes nada para funções embutidas em C. Ainda assim, é um lembrete rápido quando a dica de ferramenta da IDE não está por perto.

2.41.4. Quando algo trava

Um script que não retorna é mais difícil de diagnosticar do que um que levanta exceção. Culpados comuns:

  • Um laço while cuja condição nunca se torna falsa. Adicione um print da variável do laço a cada iteração; se o valor não estiver mudando, o corpo do laço tem um bug.

  • Uma chamada bloqueante esperando por uma entrada que nunca chega – uma leitura de uma fila vazia, um sleep sem fim. Cerque a chamada com prints para ver em qual linha o script ficou preso.

  • Uma recursão infinita. O traceback quando ela finalmente dispara (com RecursionError) geralmente aponta direto para ela.

A recuperação mais eficaz para um script travado é o botão stop da IDE, que envia um KeyboardInterrupt ao script via USB. A interrupção aparece como um traceback na linha que está rodando no momento – frequentemente a linha exata que não está retornando.

Nota

Se um travamento resiste a todo diagnóstico – o script parece correto, o traceback da interrupção aponta para uma função embutida ou para o código do firmware em vez do seu script, ou o mesmo código funcionava em uma versão anterior do firmware – a causa pode ser um bug de firmware em vez de um bug de script. Reduza o script ao menor reprodutor que ainda trava e abra um relatório no fórum da OpenMV. Inclua a versão do firmware, a placa em que ele rodou e o script reduzido.

2.41.5. Remova os diagnósticos antes de publicar

Prints estratégicos durante o desenvolvimento são ótimos; uma centena de chamadas a print deixadas em um script de produção poluem a saída e usam heap que o trabalho real poderia estar usando. Quando um bug é corrigido, remova os prints (ou proteja-os atrás de uma flag de depuração que você possa desligar).

Para diagnósticos que devem permanecer no caminho de código a longo prazo, troque print() pelo módulo logging. Ele anexa um nível a cada mensagem (debug, info, warning, error) e permite que uma única configuração silencie as menos importantes 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 como logging.WARNING faz com que as chamadas info e debug praticamente não custem nada (a string da mensagem nunca é construída), sem precisar comentar linhas. Isso torna logging a ferramenta certa para diagnósticos permanentes; o print puro é adequado para os descartáveis.