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.2. Tulostusvirheenkorjaus¶
Nopein tapa selvittää, mitä skripti tekee, on tulostaa epäilyttävät arvot. Kolme sisäänrakennettua funktiota tekevät tulostuksista hyödyllisempiä:
repr()– palauttaa arvon kehittäjätyylisen merkkijonon.print(repr(value))erottaa arvon"5"arvosta5ja arvonNonearvosta"None", mihin tavallinenprint()ei pysty.type()– palauttaa arvon luokan.print(type(value))on tapa selvittää, onko muuttuja, jonka ”pitäisi olla int”, salaa merkkijono.len()– jonon tai kokoelman pituus. Yllättävän suuri osa bugeista on yhden yli- tai kokoepäsuhtaongelmia.
print("got:", repr(value), "type:", type(value), "len:", len(value))
Pudota tulostus jokaiseen haaraan, josta välität – molempiin if-lauseen haaroihin, jokaiseen except-lohkoon, sen silmukan runkoon, jonka epäilet suoritettavan nolla kertaa. Hinta on yksi tulostusrivi; arvo on sen selvittäminen, onko se koodipolku, jonka luulet suoritettavan, todella se polku, joka suoritetaan.
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.