2.41. Depuración

La mayoría de los scripts que fallan en la cámara lo hacen de una de estas tres formas: lanzan una excepción, producen un valor incorrecto o se quedan colgados. Cada caso cuenta con un conjunto de herramientas distinto.

2.41.1. Cómo leer una traza de error (traceback)

Cuando un script lanza una excepción y nada la gestiona, el REPL o el IDE imprimen una traza de error (traceback): un registro de la cadena de llamadas desde el script más externo hasta la línea que provocó el error.

Una traza de error se lee de abajo hacia arriba:

  • La línea inferior indica la clase de la excepción y su mensaje (ValueError: invalid literal for int()...).

  • Cada bloque File "...", line N, in <name> que aparece encima es un fotograma: una llamada más profunda a medida que se asciende.

  • El fotograma superior es donde comenzó el script; el fotograma inferior es donde se disparó el error.

Lee primero la parte inferior para saber qué salió mal y luego asciende para ver cómo llegó el script hasta allí. Los números de línea apuntan a las ubicaciones exactas del código fuente del script.

2.41.3. Explorar un objeto

Dos funciones integradas responden a «qué puedo hacer con esto»:

  • dir(): devuelve una lista con todos los nombres definidos en un objeto: métodos, atributos, métodos dunder, todo.

  • help(): imprime la cadena de documentación (y, en CPython, la firma) de una función, método o clase.

Úsalas juntas: dir encuentra el nombre y help explica qué hace.

2.41.3.1. Encontrar un nombre con dir

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

El primer tramo de la lista son métodos dunder, heredados de todos los objetos; los nombres que merece la pena buscar suelen estar después de ellos. dir funciona con cualquier cosa: una clase, una instancia, un módulo, un tipo integrado:

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

Esa segunda forma es la manera de averiguar qué nombres de nivel superior expone realmente un módulo sin salir del REPL.

2.41.3.2. Consultarlo con help

Una vez que dir ha sacado a la luz un candidato, help lo describe:

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

En MicroPython, help es más escueto que en CPython: a veces solo la firma, a veces una cadena de documentación de una línea, a veces nada en el caso de las funciones integradas en C. Aun así, es un recordatorio rápido cuando no se tiene a mano la información emergente del IDE.

2.41.4. Cuando algo se queda colgado

Un script que no termina es más difícil de diagnosticar que uno que lanza una excepción. Culpables habituales:

  • Un bucle while cuya condición nunca se vuelve falsa. Añade un print de la variable del bucle en cada iteración; si el valor no cambia, el cuerpo del bucle tiene un error.

  • Una llamada bloqueante que espera una entrada que nunca llega: una lectura de una cola vacía, una espera sin fin. Rodea la llamada con prints para ver en qué línea se ha quedado atascado el script.

  • Una recursión infinita. La traza de error que aparece cuando finalmente se dispara (con RecursionError) suele apuntar directamente a ella.

La recuperación más eficaz para un script colgado es el botón stop del IDE, que envía un KeyboardInterrupt al script a través de USB. La interrupción aparece como una traza de error en la línea que se está ejecutando en ese momento, a menudo la línea exacta que no termina.

Nota

Si un cuelgue resiste todos los diagnósticos (el script parece correcto, la traza de error de la interrupción apunta a una función integrada o al código del firmware en lugar de a tu script, o el mismo código funcionaba en una compilación de firmware anterior), la causa puede ser un error del firmware en lugar de un error del script. Reduce el script al reproductor más pequeño posible que siga quedándose colgado y abre un informe en el foro de OpenMV. Incluye la versión del firmware, la placa en la que se ejecutó y el script reducido.

2.41.5. Quita los diagnósticos antes de publicar

Los prints estratégicos durante el desarrollo son estupendos; un centenar de llamadas a print dejadas en un script de producción saturan la salida y consumen memoria dinámica (heap) que el trabajo real podría estar usando. Cuando se corrige un error, quita los prints (o protégelos detrás de una marca de depuración que puedas desactivar).

Para los diagnósticos que deban permanecer en la ruta de código a largo plazo, cambia de print() al módulo logging. Este asocia un nivel a cada mensaje (debug, info, warning, error) y permite que un único ajuste silencie los menos importantes en producción:

import logging

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

Establecer el nivel del logger en logging.WARNING hace que las llamadas info y debug no cuesten prácticamente nada (la cadena del mensaje nunca se construye), sin tener que comentar líneas. Eso convierte a logging en la herramienta adecuada para los diagnósticos permanentes; el simple print está bien para los desechables.