2.41. Ladění

Většina skriptů, které na kameře selžou, selže jedním ze tří způsobů: vyvolají výjimku, vyprodukují nesprávnou hodnotu, nebo se zaseknou. Každý z nich vyžaduje jinou sadu nástrojů.

2.41.1. Čtení tracebacku

Když skript vyvolá výjimku a nic ji neobslouží, REPL nebo IDE vypíše traceback – záznam řetězce volání od nejvzdálenějšího skriptu až po řádek, který výjimku vyvolal.

Traceback se čte zdola nahoru:

  • Spodní řádek pojmenovává třídu výjimky a její zprávu (ValueError: invalid literal for int()...).

  • Každý blok File "...", line N, in <name> nad ním je snímek (frame) – o jedno volání hlouběji, jak postupujete nahoru.

  • Úplně nejvyšší snímek je místo, kde skript začal; úplně nejnižší snímek je místo, kde chyba nastala.

Nejprve si přečtěte spodní část, abyste zjistili, co se pokazilo, a poté postupujte nahoru, abyste viděli, jak se skript do daného místa dostal. Čísla řádků ukazují na přesná místa ve zdrojovém kódu skriptu.

2.41.3. Zkoumání objektu

Dvě vestavěné funkce odpovídají na otázku „co s touto věcí mohu dělat“:

  • dir() – vrací seznam každého jména definovaného na objektu: metody, atributy, dunder metody, vše.

  • help() – vypíše dokumentační řetězec (a na CPythonu i signaturu) funkce, metody nebo třídy.

Používejte je společně: dir najde jméno, help vysvětlí, co dělá.

2.41.3.1. Nalezení jména pomocí dir

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

První část seznamu tvoří dunder metody zděděné z každého objektu; jména, která stojí za prohlédnutí, jsou obvykle až za nimi. dir funguje na čemkoli – na třídě, instanci, modulu, vestavěném typu:

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

Tato druhá forma je způsob, jak zjistit, která jména nejvyšší úrovně modul skutečně zpřístupňuje, aniž byste opustili REPL.

2.41.3.2. Vyhledání pomocí help

Jakmile dir odhalí kandidáta, help jej popíše:

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

Na MicroPythonu je help úspornější než na CPythonu – někdy jen signatura, někdy jednořádkový dokumentační řetězec, někdy pro vestavěné funkce v C nic. Přesto je to rychlá připomínka, když není po ruce nápověda v IDE.

2.41.4. Když se něco zasekne

Skript, který se nevrací, se diagnostikuje obtížněji než ten, který vyvolá výjimku. Časté příčiny:

  • Cyklus while, jehož podmínka se nikdy nestane nepravdivou. Přidejte výpis řídicí proměnné cyklu při každé iteraci; pokud se hodnota nemění, tělo cyklu obsahuje chybu.

  • Blokující volání čekající na vstup, který nikdy nedorazí – čtení z prázdné fronty, spánek bez konce. Obklopte volání výpisy, abyste viděli, na kterém řádku skript uvázl.

  • Nekonečná rekurze. Traceback, který se nakonec objeví (s RecursionError), na ni obvykle ukazuje přímo.

Nejúčinnějším způsobem obnovy zaseknutého skriptu je tlačítko stop v IDE, které skriptu pošle přes USB KeyboardInterrupt. Přerušení se objeví jako traceback na právě běžícím řádku – často právě na tom řádku, který se nevrací.

Poznámka

Pokud se zaseknutí brání každé diagnostice – skript se zdá správný, traceback přerušení ukazuje do vestavěné funkce nebo do kódu firmwaru, a ne do vašeho skriptu, nebo tentýž kód fungoval na předchozím sestavení firmwaru – příčinou může být chyba firmwaru spíše než chyba skriptu. Zredukujte skript na nejmenší reprodukovatelný příklad, který se stále zasekává, a založte hlášení na fóru OpenMV. Uveďte verzi firmwaru, desku, na které běžel, a zredukovaný skript.

2.41.5. Před nasazením diagnostiku odstraňte

Strategické výpisy během vývoje jsou skvělé; sto volání print ponechaných v produkčním skriptu zahlcuje výstup a spotřebovává haldu (heap), kterou by mohla využívat skutečná práce. Když je chyba opravena, výpisy odstraňte (nebo je ohraničte ladicím příznakem, který lze vypnout).

Pro diagnostiku, která by měla zůstat v cestě kódu dlouhodobě, přejděte z print() na modul logging. Ten ke každé zprávě připojí úroveň (debug, info, warning, error) a umožňuje jediným nastavením v produkci umlčet ty méně důležité:

import logging

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

Nastavení úrovně loggeru na logging.WARNING způsobí, že volání info a debug nestojí prakticky nic (řetězec zprávy se nikdy nesestaví), aniž byste museli zakomentovávat řádky. To dělá z logging správný nástroj pro trvalou diagnostiku; surový print je v pořádku pro tu jednorázovou.