2.41. Débogage¶
La plupart des scripts qui échouent sur la caméra le font de l’une de trois façons : ils lèvent une exception, ils produisent une valeur erronée, ou ils se bloquent. Chacune dispose d’un ensemble d’outils différent.
2.41.1. Lire une trace d’appels¶
Lorsqu’un script lève une exception et que rien ne la gère, le REPL ou l’IDE affiche une trace d’appels (traceback) – un enregistrement de la chaîne d’appels, depuis le script le plus externe jusqu’à la ligne qui a déclenché l’erreur.
Une trace d’appels se lit de bas en haut :
La ligne du bas indique la classe de l’exception et son message (
ValueError: invalid literal for int()...).Chaque bloc
File "...", line N, in <name>au-dessus est une trame (frame) – un appel plus profond à mesure que l’on monte.La trame tout en haut est l’endroit où le script a démarré ; la trame tout en bas est l’endroit où l’erreur s’est déclenchée.
Lisez d’abord le bas pour comprendre ce qui s’est mal passé, puis remontez pour voir comment le script en est arrivé là. Les numéros de ligne pointent vers les emplacements exacts dans le code source du script.
2.41.2. Débogage par affichage¶
Le moyen le plus rapide de découvrir ce que fait un script est d’afficher les valeurs suspectes. Trois fonctions intégrées rendent les affichages plus utiles :
repr()– renvoie la chaîne de style développeur pour une valeur.print(repr(value))distingue"5"de5etNonede"None", ce qu’un simpleprint()ne peut pas faire.type()– renvoie la classe d’une valeur.print(type(value))permet de découvrir si la variable qui « devrait être un entier » est secrètement une chaîne de caractères.len()– la longueur d’une séquence ou d’une collection. Une part étonnamment importante des bogues sont des erreurs de décalage d’un (off-by-one) ou des problèmes de non-correspondance de taille.
print("got:", repr(value), "type:", type(value), "len:", len(value))
Insérez un affichage dans chaque branche qui vous intéresse – les deux branches d’un if, chaque bloc except, le corps d’une boucle que vous soupçonnez de ne jamais s’exécuter. Le coût est une ligne de sortie ; l’intérêt est de découvrir si le chemin de code que vous pensez exécuté est bien celui qui s’exécute réellement.
2.41.3. Explorer un objet¶
Deux fonctions intégrées répondent à la question « que puis-je faire avec cet objet » :
dir()– renvoie une liste de tous les noms définis sur un objet : méthodes, attributs, méthodes spéciales (dunders), absolument tout.help()– affiche la docstring (et sur CPython, la signature) d’une fonction, d’une méthode ou d’une classe.
Utilisez-les ensemble : dir trouve le nom, help explique ce qu’il fait.
2.41.3.1. Trouver un nom avec dir¶
>>> dir([1, 2, 3])
['__add__', '__class__', '__contains__', '__delitem__',
'__eq__', '__ge__', ..., 'append', 'clear', 'copy',
'count', 'extend', 'index', 'insert', 'pop', 'remove',
'reverse', 'sort']
La première partie de la liste correspond aux méthodes spéciales (dunders), héritées de tout objet ; les noms qui méritent d’être recherchés se trouvent généralement après. dir fonctionne sur n’importe quoi – une classe, une instance, un module, un type intégré :
>>> import json
>>> dir(json)
['__name__', 'dump', 'dumps', 'load', 'loads']
Cette seconde forme permet de découvrir quels noms de premier niveau un module expose réellement sans quitter le REPL.
2.41.3.2. Le rechercher avec help¶
Une fois que dir a fait ressortir un candidat, help le décrit :
>>> help(str.split)
split(sep=None, maxsplit=-1)
Return a list of the words in the string, ...
Sur MicroPython, help est plus succinct que sur CPython – parfois juste la signature, parfois une docstring d’une ligne, parfois rien pour les fonctions C intégrées. Cela reste un rappel rapide quand l’info-bulle de l’IDE n’est pas à portée de main.
2.41.4. Quand quelque chose se bloque¶
Un script qui ne rend jamais la main est plus difficile à diagnostiquer qu’un script qui lève une exception. Les coupables courants :
Une boucle
whiledont la condition ne devient jamais fausse. Ajoutez un affichage de la variable de boucle à chaque itération ; si la valeur ne change pas, le corps de la boucle comporte un bogue.Un appel bloquant en attente d’une entrée qui n’arrive jamais – une lecture sur une file vide, une mise en veille sans fin. Encadrez l’appel d’affichages pour voir sur quelle ligne le script est bloqué.
Une récursion infinie. La trace d’appels qui finit par se déclencher (avec
RecursionError) pointe généralement directement dessus.
Le moyen de récupération le plus efficace pour un script bloqué est le bouton stop de l’IDE, qui envoie une KeyboardInterrupt au script via USB. L’interruption apparaît sous forme de trace d’appels à la ligne en cours d’exécution – souvent la ligne exacte qui ne rend pas la main.
Note
Si un blocage résiste à tous les diagnostics – le script semble correct, la trace d’appels de l’interruption pointe vers une fonction intégrée ou du code du micrologiciel plutôt que vers votre script, ou le même code fonctionnait sur une version antérieure du micrologiciel – la cause peut être un bogue du micrologiciel plutôt qu’un bogue du script. Réduisez le script au plus petit cas reproducteur qui se bloque encore et ouvrez un rapport sur le forum OpenMV. Incluez la version du micrologiciel, la carte sur laquelle il s’est exécuté et le script réduit.
2.41.5. Retirez les diagnostics avant de livrer¶
Des affichages stratégiques pendant le développement sont très utiles ; une centaine d’appels print laissés dans un script de production encombrent la sortie et consomment de la mémoire du tas (heap) que le travail réel pourrait utiliser à la place. Une fois un bogue corrigé, retirez les affichages (ou protégez-les derrière un indicateur de débogage que vous pouvez désactiver).
Pour les diagnostics qui doivent rester dans le chemin de code à long terme, passez de print() au module logging. Il attache un niveau à chaque message (debug, info, warning, error) et permet, via un seul réglage, de faire taire les messages discrets en production :
import logging
log = logging.getLogger("main")
log.info("starting up")
log.debug("loaded config: %s", config)
log.warning("falling back to defaults")
Régler le niveau du logger sur logging.WARNING rend les appels info et debug pratiquement gratuits (la chaîne du message n’est jamais construite), sans avoir à commenter de lignes. C’est ce qui fait de logging l’outil adapté aux diagnostics permanents ; un print brut convient pour les diagnostics jetables.