Virheiden etsiminen ja korjaaminen


Virheiden löytäminen ja käsittely kuuluu jokaisen ohjelmoijan perustaitoihin ja on välttämätöntä suuria ohjelmia kirjoittaessa. Tällä kurssilla ei luoda ohjelmia, jotka vaativat huomattavaa virheiden käsittelyä tai debuggaamista. Edettäessä suurempiin ohjelmiin, erityisesti graafisen käyttöliittymän sisältäviin ohjelmiin ja sekä vaativampiin (”pikkutarkkoihin”) kieliin (C, C++), on virheiden käsittely ja debuggaaminen erittäin oleellista.

Virheen löytäminen alkaa traceback-viestistä. Traceback on punainen virheilmoitus, joka kertoo syyn ohjelman kaatumiseen. Aluksi viesti voi näyttää heprealta, mutta kun sitä oppii lukemaan, se on erittäin hyödyllinen apuväline virheiden löytämisessä.

Esimerkki 1


Katsotaan ensin yksinkertaista tracebackiä, joka syntyy koodista

print(x)

Traceback on:

Traceback (most recent call last):
File "C:/Users/Omistaja/Desktop/ErrorExample1.py", line 1, in <module>
print(x)
NameError: name 'x' is not defined

Tracebackin ensimmäinen rivi ilmoittaa meille virheen sijainnin. Tässä tapauksessa virhe tapahtui tiedostossa ErrorExample1.py rivillä 1. Huomaa miten virheviestissä lukee polku, jossa tiedosto sijaitsee, pelkän nimen sijaan.

Seuraava rivi kertoo meille, mitä kyseisellä rivillä lukee. Tässä tapauksessa koodin pätkä, mikä aiheuttaa virheen on print(x).

Viimeinen rivi tracebackissä kertoo meille mikä virhe on kyseessä. Kyseinen virhe on siis NameError, joka johtuu siitä, että muuttujaa x ei ole määritelty.

Esimerkki 2

Seuraava esimerkki on hieman monimutkaisempi traceback, joka on syntynyt seuraavasta koodista:

def palautaAlkio(lista, alkio):
    return lista[alkio]
lista = [1, 2, 3]
print(palautaAlkio(lista,3))

Selkeyden vuoksi jaotellaan traceback osiin:

Traceback (most recent call last):
File "<ipython-input-38-055a27710ada>", line 1, in <module><
    runfile('C:/Users/User/Desktop/ErrorExample.py', wdir='C:/Users/Sammako/Desktop')
File "C:\Users\User\Anaconda3\lib\site-packages\spyder\utils\site\sitecustomize.py", line 705, in runfile
    execfile(filename, namespace)
File "C:\Users\User\Anaconda3\lib\site-packages\spyder\utils\site\sitecustomize.py", line 102, in execfile
    exec(compile(f.read(), filename, 'exec'), namespace)

Yllä olevat viestit käsittelevät ohjelman kääntämistä, eikä niihin syvennytä tällä kurssilla.

File "C:/Users/User/Desktop/ErrorExample.py", line 5, in <module>
    print(palautaAlkio(lista, 3))

Jälleen kerran tracebackistä selviää ohjelman polku, rivi, sekä rivin sisältö

File "C:/Users/User/Desktop/ErrorExample.py", line 2, in palautaAlkio

    return lista[alkio]

Koska rivi, joka aiheuttaa virheen on funktiokutsu, ilmoittaa traceback vielä erikseen virheen sijaitsevan funktiossa nimeltä palautaAlkio, sekä rivin, jossa virhe sijaitsee sekä, sisällön.

Tässä esimerkissä funktio, jossa virhe on, sijaitsee samassa tiedostossa kuin sen kutsu. Tapauksissa, jossa ohjelma kutsuu useita eri funktiota, useista eri tiedostoista, tämän kaltainen viesti on erittäin hyödyllinen.

IndexError: list index out of range

Virhe on IndexError eli ohjelma yrittää kutsua listan alkiota, jota ei ole olemassa.

On mahdollista, että virhe aiheutuu kirjastossa kuten numpy. Tällöin traceback saattaa ilmoittaa virheen sijaitsevan esim. rivillä 1000, jossain satunnaisessa tiedostossa. Tällöin täytyy etsiä viimeisin rivi tracback:ssä, joka sijaitsee itse luomassasi tiedostossa.

Esimerkki 3

Tracebackistä ei kuitenkaan aina ole apua. Otetaan esimerkiksi koodi

def kerro_kymmenella(numero):
    return numero * 0
numero = 5
jako = 1 / kerro_kymmenella(numero)
print(jako)

Käyttäjä on luonut funktion, jonka pitäisi kertoa numero kymmenellä, mutta teki kirjoitusvirheen, minkä seurauksesta funktio palauttaa aina nollan. Kun tarkastelemme saatua tracebackiä, huomaamme ongelman olevan rivillä 6, sekä ongelman johtuvan nollalla jakamisesta.

Koska tehty virhe ei suoranaisesti aiheuta virhettä, vaan antaa väärän tuloksen, joka myöhemmin aiheuttaa virheen, ei traceback ole yhtä hyödyllinen tässä tilanteessa. On myös tilanteita, kuten graafiset käyttöliittymät, jotka eivät aina kaatuessaan luo tracebackiä. Miten siis löytää virhe, kun sen sijaintia ei tiedetä?

Virheen löytäminen

print-funktiot ovat yksinkertainen tapa löytää mahdollisia virheitä koodista. Alla oleva koodi kaatuu, mutta koska virhe on ikuinen silmukka ei traceback-viestiä synny.

numero = 5
kertoma = 1
print("testi")
while numero > 1:
    kertoma *= numero
print("testi")

Kun koodi ajetaan print-funktioiden kanssa, huomataan ensimmäisen tulostuvan, mutta toisen ei. Tästä on helppo päätellä, että virhe on while-silmukassa. Kun virheen sijainti tiedetään, on helppo huomata virheen johtuvan siitä, että muuttujan numero arvoa ei vähennetä silmukassa.

Virheiden käsittelystä kerrotaan lisää muualla oppimateriaalissa.