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.2. Depuração com print¶
A maneira mais rápida de descobrir o que um script está fazendo é imprimir os valores suspeitos. Três funções embutidas tornam os prints mais úteis:
repr()– retorna a representação em string no estilo de desenvolvedor de um valor.print(repr(value))distingue"5"de5eNonede"None", o que umprint()simples não consegue.type()– retorna a classe de um valor.print(type(value))é a forma de descobrir se a variável que “deveria ser um int” é secretamente uma string.len()– o comprimento de uma sequência ou coleção. Uma fração surpreendentemente grande de bugs são problemas de erro por um (off-by-one) ou de incompatibilidade de tamanho.
print("got:", repr(value), "type:", type(value), "len:", len(value))
Coloque um print dentro de cada ramo que lhe interessa – ambos os braços de um if, cada bloco except, o corpo de um laço que você suspeita estar rodando zero vezes. O custo é uma linha de saída; o valor é descobrir se o caminho de código que você pensa estar rodando é o que realmente está rodando.
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
whilecuja 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.