2.41. Hibakeresés

A kamerán hibára futó szkriptek többsége háromféleképpen hibázik el: kivételt váltanak ki, rossz értéket állítanak elő, vagy lefagynak. Mindegyikhez más-más eszközkészlet tartozik.

2.41.1. A visszakövetés (traceback) olvasása

Amikor egy szkript kivételt vált ki, és semmi sem kezeli azt, a REPL vagy az IDE kiír egy visszakövetést (traceback) – a hívási lánc feljegyzését a legkülső szkripttől egészen addig a sorig, amely a kivételt kiváltotta.

A visszakövetést alulról felfelé kell olvasni:

  • A legalsó sor megnevezi a kivétel osztályát és annak üzenetét (ValueError: invalid literal for int()...).

  • Minden File "...", line N, in <name> blokk fölötte egy képkocka (frame) – felfelé haladva egy-egy hívással mélyebbre jutunk.

  • A legfelső képkocka az, ahol a szkript elindult; a legalsó képkocka pedig az, ahol a hiba bekövetkezett.

Először a legalsó sort olvasd el, hogy megtudd, mi romlott el, majd haladj felfelé, hogy lásd, hogyan jutott el a szkript odáig. A sorszámok pontos forráshelyekre mutatnak a szkriptben.

2.41.3. Egy objektum felderítése

Két beépített függvény válaszol arra a kérdésre, hogy „mit tudok kezdeni ezzel a dologgal”:

  • dir() – visszaadja egy objektumon definiált összes név listáját: metódusokat, attribútumokat, dundereket, mindent.

  • help() – kiírja egy függvény, metódus vagy osztály docstringjét (CPython-on a szignatúrát is).

Használd őket együtt: a dir megtalálja a nevet, a help pedig elmagyarázza, mit csinál.

2.41.3.1. Név keresése a dir segítségével

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

A lista első része a minden objektumtól örökölt dunder metódusokból áll; az átvizsgálásra érdemes nevek általában ezek után jönnek. A dir bármin működik – osztályon, példányon, modulon, beépített típuson:

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

Ezzel a második alakkal deríthető ki, hogy egy modul valójában mely legfelső szintű neveket teszi elérhetővé, anélkül, hogy el kellene hagyni a REPL-t.

2.41.3.2. Utánanézés a help segítségével

Miután a dir felszínre hozott egy jelöltet, a help leírja azt:

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

MicroPython-on a help szűkszavúbb, mint CPython-on – néha csak a szignatúra, néha egy egysoros docstring, néha pedig semmi a beépített C függvények esetén. Mégis gyors emlékeztető, amikor az IDE eszköztippje nincs kéznél.

2.41.4. Amikor valami lefagy

Egy olyan szkriptet, amely nem tér vissza, nehezebb diagnosztizálni, mint egyet, amely kivételt vált ki. Gyakori okok:

  • Egy while ciklus, amelynek feltétele sosem válik hamissá. Adj hozzá egy nyomtatást a ciklusváltozóról minden iterációban; ha az érték nem változik, akkor a ciklus törzsében van a hiba.

  • Egy blokkoló hívás, amely olyan bemenetre vár, ami sosem érkezik meg – olvasás egy üres sorból, egy vég nélküli sleep. Vedd közre a hívást nyomtatásokkal, hogy lásd, melyik soron akadt el a szkript.

  • Egy végtelen rekurzió. A visszakövetés, amikor végül bekövetkezik (egy RecursionError kivétellel), általában egyenesen rámutat.

Egy lefagyott szkript leghatékonyabb helyreállítása az IDE stop gombja, amely egy KeyboardInterrupt kivételt küld a szkriptnek USB-n keresztül. A megszakítás visszakövetésként jelenik meg az éppen futó sornál – gyakran pontosan annál a sornál, amely nem tér vissza.

Megjegyzés

Ha egy lefagyás minden diagnosztikának ellenáll – a szkript helyesnek tűnik, a megszakítás visszakövetése egy beépített elemre vagy firmware-kódra mutat a saját szkripted helyett, vagy ugyanaz a kód egy korábbi firmware-verzión működött – akkor az ok inkább firmware-hiba lehet, mint szkripthiba. Csökkentsd a szkriptet a legkisebb olyan reprodukáló példára, amely még mindig lefagy, és nyiss egy bejelentést az OpenMV fórumon. Add meg a firmware verzióját, az alaplapot, amelyen futott, valamint a lecsökkentett szkriptet.

2.41.5. Távolítsd el a diagnosztikát szállítás előtt

A fejlesztés során elhelyezett stratégiai nyomtatások remekek; de száz print hívás egy éles szkriptben hagyva összezavarja a kimenetet, és olyan heap-et használ, amelyet a valódi munka is használhatna helyette. Amikor egy hibát kijavítasz, vedd ki a nyomtatásokat (vagy bújtasd el őket egy hibakeresési jelző mögé, amit ki tudsz kapcsolni).

Az olyan diagnosztikához, amelynek hosszú távon a kódútvonalon kellene maradnia, válts a print() függvényről a logging modulra. Ez egy szintet (level) csatol minden üzenethez (debug, info, warning, error), és lehetővé teszi, hogy egyetlen beállítással elnémítsd a csendeseket éles környezetben:

import logging

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

Ha a naplózó szintjét logging.WARNING értékre állítod, akkor az info és debug hívások lényegében semmibe nem kerülnek (az üzenet karakterlánc soha nem épül fel), anélkül, hogy ki kellene kommentezni a sorokat. Ezzel a logging lesz a megfelelő eszköz a tartós diagnosztikához; a nyers print az eldobható diagnosztikához jó.