2.41. Virheenkorjaus

Useimmat kameralla epäonnistuvat skriptit menevät pieleen yhdellä kolmesta tavasta: ne nostavat poikkeuksen, ne tuottavat väärän arvon tai ne jäävät jumiin. Jokaiseen liittyy oma työkaluvalikoimansa.

2.41.1. Jäljityksen lukeminen

Kun skripti nostaa poikkeuksen eikä mikään käsittele sitä, REPL tai IDE tulostaa jäljityksen (traceback) – tallenteen kutsuketjusta uloimmasta skriptistä alas siihen riviin, joka nosti poikkeuksen.

Jäljitys luetaan alhaalta ylöspäin:

  • Alin rivi nimeää poikkeusluokan ja sen viestin (ValueError: invalid literal for int()...).

  • Jokainen sen yläpuolella oleva File "...", line N, in <name> -lohko on kehys (frame) – yhtä kutsua syvemmällä mitä ylemmäs mennään.

  • Aivan ylin kehys on se, mistä skripti alkoi; aivan alin kehys on se, missä virhe laukesi.

Lue ensin alin selvittääksesi, mikä meni pieleen, ja kävele sitten ylöspäin nähdäksesi, miten skripti päätyi sinne. Rivinumerot osoittavat tarkat lähdekoodin sijainnit skriptissä.

2.41.3. Olion tutkiminen

Kaksi sisäänrakennettua funktiota vastaa kysymykseen ”mitä tällä voin tehdä”:

  • dir() – palauttaa luettelon jokaisesta olioon määritellystä nimestä: metodit, attribuutit, dunderit, kaikki.

  • help() – tulostaa funktion, metodin tai luokan docstringin (ja CPythonissa allekirjoituksen).

Käytä niitä yhdessä: dir löytää nimen, help selittää, mitä se tekee.

2.41.3.1. Nimen löytäminen dir-funktiolla

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

Luettelon ensimmäinen osa on dunder-metodeja, jotka periytyvät jokaiselta oliolta; tarkasteltavat nimet ovat yleensä niiden jälkeen. dir toimii mihin tahansa – luokkaan, instanssiin, moduuliin, sisäänrakennettuun tyyppiin:

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

Tuo jälkimmäinen muoto on tapa selvittää, mitkä ylätason nimet moduuli todella paljastaa poistumatta REPListä.

2.41.3.2. Sen hakeminen help-funktiolla

Kun dir on nostanut esiin ehdokkaan, help kuvaa sen:

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

MicroPythonissa help on niukempi kuin CPythonissa – joskus vain allekirjoitus, joskus yksirivinen docstring, joskus ei mitään sisäänrakennetuille C-funktioille. Se on silti nopea muistutus, kun IDE:n työkaluvihje ei ole lähellä.

2.41.4. Kun jokin jää jumiin

Skripti, joka ei palaa, on vaikeampi diagnosoida kuin sellainen, joka nostaa poikkeuksen. Yleisiä syyllisiä:

  • while-silmukka, jonka ehto ei koskaan muutu epätodeksi. Lisää silmukkamuuttujan tulostus jokaiseen iteraatioon; jos arvo ei muutu, silmukan rungossa on bugi.

  • Estävä kutsu, joka odottaa syötettä, joka ei koskaan saavu – lukeminen tyhjästä jonosta, päättymätön sleep. Ympäröi kutsu tulostuksilla nähdäksesi, mihin riviin skripti on juuttunut.

  • Ääretön rekursio. Jäljitys, kun se lopulta laukeaa (poikkeuksella RecursionError), osoittaa yleensä suoraan siihen.

Tehokkain elpyminen jumiutuneelle skriptille on IDE:n stop-painike, joka lähettää skriptille poikkeuksen KeyboardInterrupt USB:n yli. Keskeytys nousee esiin jäljityksenä sillä rivillä, joka on parhaillaan suorituksessa – usein juuri se rivi, joka ei palaa.

Muista

Jos jumiutuminen vastustaa jokaista diagnostiikkaa – skripti vaikuttaa oikealta, keskeytyksen jäljitys osoittaa sisäänrakennettuun tai laiteohjelmistokoodiin skriptisi sijaan, tai sama koodi toimi aiemmassa laiteohjelmistoversiossa – syy voi olla laiteohjelmiston bugi eikä skriptin bugi. Pienennä skripti pienimmäksi mahdolliseksi toistettavaksi tapaukseksi, joka edelleen jää jumiin, ja avaa raportti OpenMV-foorumilla. Sisällytä laiteohjelmiston versio, levy, jolla se ajettiin, ja pienennetty skripti.

2.41.5. Poista diagnostiikka ennen julkaisua

Strategiset tulostukset kehityksen aikana ovat erinomaisia; sata tuotantoskriptiin jätettyä print-kutsua sotkevat tulosteen ja käyttävät kekoa, jota varsinainen työ voisi käyttää. Kun bugi on korjattu, poista tulostukset (tai suojaa ne virheenkorjauslipun taakse, jonka voit kytkeä pois).

Diagnostiikalle, jonka tulisi pysyä koodipolulla pitkäaikaisesti, vaihda funktiosta print() moduuliin logging. Se liittää kuhunkin viestiin tason (debug, info, warning, error) ja antaa yhden asetuksen vaientaa hiljaiset tuotannossa:

import logging

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

Loggerin tason asettaminen arvoon logging.WARNING tekee info- ja debug-kutsuista käytännössä ilmaisia (viestimerkkijonoa ei koskaan rakenneta), ilman että rivejä tarvitsee kommentoida pois. Tämä tekee moduulista logging oikean työkalun pysyvälle diagnostiikalle; raaka print on hyvä kertakäyttöiselle.