Razhroščevanje

Razhroščevanje

Avtor: Urška Pikl

Definicija

Razhroščevanje je umetnost odkrivanja programskih napak v programski kodi in ugotavljanja, kako jih popraviti.

Tako imenovani "hrošči" - napake v programu - se nahajajo v različnih oblikah, kot na primer

  • pri napakah pri kodi programa,
  • napakah pri načrtovanju programske kode,
  • slabo načrtovanem uporabniškem vmesniku,
  • sistemskih napakah,
  • ...

Če želimo uspešno odpravljati napake v računalniških programih, potem moramo najprej vedeti, za katero vrsto napak gre. Šele nato jo lahko popravimo z ustreznimi metodami.

Napake lahko odkrivamo skozi vse "življenje" programa. Odkrije jo lahko programer medtem ko testira program, ali pa napako odkrije končni uporabnik, ko dobi nepričakovan rezultat.

Sestavni del uspešnega iskanja napak v programu je uporaba primernih metod, s katerimi pridobivamo ustrezne informacije od različnih virov, ki javijo napako.

Najbolj pogosti napaki pri programiranju sta:

  1. Programiranje brez premisleka/načrtovanja
  2. Pisanje programske kode na "grd" način

Izvor besede

Obstajajo različne razlage o izvoru besed "hrošč" in "razhroščevanje". ("bug", "debugging")

Oba izraza, hrošč (bug) in razhroščevanje (debugging) pogosto pripisujejo Grace Murray Hopper v 40. letih prejšnjega stoletja. Bila je ameriška računalniška znanstvenica in častnica ameriške vojske. Med delom na računalniku na Univerzi na Harvardu, je njen sodelavec našel v računalniku veščo, ki je ovirala delovanje računalnika. Hopperjeva je kasneje izjavila, da sta "debugging the system" (razhroščevala sistem).

Vendar beseda hrošč s pomenom napake tehničnega značaja sega nazaj vsaj v leto 1878 k Thomasu Edisonu. Beseda razhroščevanje naj bi se takrat uporabljala kot izraz v letalstvu, še pred vstopom v svet računalnikov.

Obstaja še nekaj drugih različic o izvoru besed "bug" in "debuging".

Po letu 1963 je bil ta izraz že precej splošno znan in ga računalniškim priročnikom ni bilo potrebno več definirati na začetnih straneh.

Vrste hroščev

Pri napakah v programski kodi je izvor napak oseba, ki je kodo napisala. Primeri takih napak so

  • Klicanje napačne funkcije (namesto poiščiNajvečjo poiščiNajmanjšo)
  • Uporaba napačnih spremenljivk na napačnih mestih (namesto razdalja(y,x) kličemo razdalja(x,y))
  • Uporaba še nedoločenih spremenljivk
  • Izpuščanje pogojev preden vrnemo rezultat
  • ...

Uporabniki programov nekatere napake v zasnovi programa opazijo takoj, druge pa so precej težje za zaznavo. Te je tudi težje odpraviti.

Očitne napake se pogosto pokažejo pri programih, ko naletijo na omejitev z računalnikom. Na primer velikost spomina na voljo, prostor na trdem tisku, hitrost procesorja,...

Slabši uporabniški vmesnik pogosto prisili uporabnike v uporabo programa, kjer dobijo za rezultat nekaj, kar ni bilo predvideno. Na primer, iskalnik po spletni strani je lahko občutljiv na velike in male črke. Če uporabnik tega ne opazi, potem mu lahko iskalnik vrne sporočilo, da na spletni strani ni na voljo nobenega mesta s ključnimi besedami, ker iskanega gesla iskalnik ni našel v načinu občutljivem na velike in male črke.

Včasih se zgodi, da strojna oprema sporoči napako. Ponavadi se to zgodi nepričakovano. Težko se je prepričati, ali napaka izhaja iz našega programa ali je napaka v strojni opremi. To pa zato, ker lahko da oseba, ki operira s programom, nima dostopa do strojne opreme, ki dela težave.

Preprečevanje napak

Nobena razprava o razhroščevanju programov ne bi bila popolna brez razprave o tem, kako najprej preprečiti hrošče.

Ne glede na to, kako dobro napišete programsko kodo, če napišete napačno kodo, ne bo pomagalo nikomur. Če napišete pravilno kodo, pa uporabnik ne bo znal uporabljati programskega vmesnika, potem je to tudi tako, kot če kode ne bi napisali.

Na kratko, dober programer/razhroščevalec mora imeti ves čas v mislih, kje bi lahko prišlo do težav.

Če želimo napisati učinkovit program, se moramo postaviti v vlogo uporabnika. Nato moramo premisliti, kako bi se uporabnik lotil nekega problema s tem programom. Ta premislek potem uporabimo pri programiranju.

Uporabniki seveda ne bodo mislili algoritmično, kot recimo mislimo, ko programiramo. Uporabnik bo o svojem problemu mislil na način, s katerim misli o svojih problemih - na svoj način.

Za zapis kode učinkovitega programa, je potrebno tudi vedeti, kaj od nas želi naročnik. Uporabniki pogosto zahtevajo od programa več, kot bi mogoče sam program omogočal. Ali pa imajo nasprotujoče vizije programa, na primer bi radi da program naredi nekaj več, ampak ne zahteva novega znanja od uporabnika.

Na kratko, potrebno je povprašati po naročnikovih zahtevah. Če zahtev ne bomo dobili od naročnika, nam lahko v prihodnje uporabniki javljajo napake, ki ne bodo tvorili smiselne celote.

Osnovni koraki razhroščevanja

Razhroščevanje nekega programa je izkušnja zase. Ampak obstajajo nekateri glavni principi odpravljanja napak.

Osnovni koraki razhroščevanja so :

Prepoznavanje hrošča

Izkušen programer ponavadi ve, kje se pogosteje pojavljajo napake. Te so odvisne od kompleksnosti dela programa ali pa od možnosti poškodovanih podatkov.

Na primer: vse podatke, ki smo jih dobili od uporabnika, moramo obravnavati malce sumničavo. Velik del programa moramo nameniti preverjanju dobljenih podatkov. Preveriti moramo tako obliko pridobljenih podatkov, kot tudi vsebino.

Podatke pridobljene s prenosi moramo preveriti, če smo dobili vse podatke, ki bi jih morali (mogoče smo kaj izgubili s prenosom).

Podatki, ki smo jih morali zaradi zapletenosti razdeliti ali nekoliko spremeniti, lahko vsebujejo nepričakovane vrednosti. Teh vrednosti potem ne moremo obravnavati pravilno.

Z vstavljanjem kontrole (preverjanjem) za pogoste napake, lahko program hitro ugotovi, če so podatki napačni.

Če je napaka v programu tako obširna, da se nam program obnaša nenavadno / nepričakovano, potem hitro ugotovimo, da imamo hrošča. Ob manjši napaki nam program sporoči o njej, če v programu preverjamo in vračamo sporočila o tovrstnih napakah.

Lahko pa je napaka manjša in povzroča le napačne rezultate. V tem primeru jo je težje odkriti. Še posebej težko jo odkrijemo, če je težko oziroma nemogoče preveriti, če so rezultati pravilni.

Cilj tega koraka je najti simptome hrošča. Pod katerimi pogoji se da hrošča najti in kdaj (če sploh) se ga da obiti. To bo zelo pomagalo pri naslednjih korakih razhroščevanja.

Naslednji korak

Primeri napačnih podatkov

  • Imamo funkcijo, ki vsakemu elementu v danem seznamu odšteje poljubno število x. Nato to število izpiše.
(primer1.png)
Funkcija
  • Ob vnosu napačne vrste podatkov, se nam program sesuje.
(primer2.png)
Napačen tip elementa v seznamu
  • Sesuje se tudi, če pozabimo vnesti kak podatek.
(primer3.png)
Manjka začetni parameter

Izolacija izvora hrošča

Ta korak je pogosto najtežji, pa tudi najbolj koristen. Ideja je, da najdemo del programa, ki nam povzroča napako. Na žalost izvor problema ni vedno isti, kot izvor simptomov.

Na primer, če je vnešena vrednost napačna, potem lahko pride do napake šele, ko s to vrednostjo začnemo operirati, ali pa smo z njo že operirali. Zato niti ni nujno, da dobimo napako ob prebiranju vnešene vrednosti.

Ta korak pogosto vključuje ponavljajoče testiranje. Programer naj bi najprej preveril, če so podatki pravilno vnešeni. Nato če se pravilno berejo, pravilno obdelujejo, itd.

Če je bil vnos pravilen, rezultat pa ne, potem je hrošč v metodi / funkciji / programu.

S ponavljajočim preverjanjem vnosov in rezultatov lahko oseba, ki razhroščuje, najde hrošča že v nekaj vrsticah programske kode, kjer naj bi se hrošč nahajal.

Izkušeni razhroščevalci lahko pogosto predvidijo, kje bi se lahko hrošč nahajal. Tega so se naučili s programiranjem podobnih programov v preteklosti. Tako lahko sproti testirajo podatke (vnešene parametre in vrnjene rezultate) v sumljivih delih programske kode.

Manj izkušeni programerji pa pogosteje iščejo napake po korakih. V programu postopoma iščejo v programski kodi, kjer je obnašanje programa drugačno od pričakovanega. To je iskanje hroščev s poskušanjem. Programer mora v tem primeru določiti katere spremenljivke bo preverjal, ko bo iskal nenavadno obnašanje programa.

Drug način iskanja hrošča, je po metodi binarnega iskanja mesta v kodi, kjer se hrošč nahaja. S testiranjem programa nekje na sredini določimo, ali se hrošč nahaja pred tem mestom ali za tem. S tem območje omejimo na manjše območje in lahko ta postopek ponavljamo.

Naslednji korak

Identifikacija povzročitelja hrošča

Ko smo našli položaj hrošča, je naš naslednji korak najti dejanskega povzročitelja napake. Povzročitelj pa so lahko tudi drugi deli programske kode.

Na primer, če ugotovimo, da se program obnaša čudno na nekem vnosu, moramo potem najti razlog, zakaj je vnos napačen. To je dejanski izvor napake. Nekateri bi trdili, da je hrošč sam po sebi že to, če program ne preverja slabih vnosov.

Dobro razumevanje programa je ključno za uspešno iskanje virov hrošča. Usposobljeni razhroščevalec lahko izolira mesto v kodi, kje se nahaja hrošč. Nekdo, ki pa program razume, lahko točneje določi dejanskega hrošča.

Lahko da je razlog v napaki v napačnem vnosu podatkov, ki jih program prejme. V drugih primerih pa je napaka logična (t.j. da jo povzroči hrošč v programu, vendar se program zaradi te napake ne sesuje). To je , ko so podatki vnešeni pravilno, vendar jih nismo pravilno obdelali.

(primer4.png)
Primer logične napake: funkcija naj bi vrnila vsoto, vendar podatka med seboj zmnoži

Logic error

Druge možnosti so, da je vnešenih preveč/premalo podatkov ali pa nepričakovane kombinacije podatkov. Lahko je tudi možnost, da smo uporabili napačne podatke, ko smo klicali neko drugo funkcijo,...

Ko smo določili vir hrošča, je dobro pregledati še druge dele programa, ki so podobni. Iste napake bi se lahko pojavile tudi tam. Če je bil hrošč neke vrste tipkarski škrat, potem tega v drugih delih najverjetneje ne bomo našli.

Če pa je prvotni programer napačno razumel začetni načrt programa in/ali zahteve naročnika, potem se lahko napake ponavljajo tudi drugje.

Naslednji korak

Določitev popravka hrošču

Sedaj imamo že vir hrošča v programu. Naslednja naloga pa je ugotoviti, kako se lahko hrošča rešimo. Dobro poznavanje programa je pri tem ključno, tudi pri odpravljanju najenostavnejših napak. To pa zato, ker bi lahko prilagajanje programa povzročilo nepričakovane rezultate. Še več, popravljanje obstoječe napake v programu lahko povzroči novo napako/hrošča, ali pa celo odkrije novih hroščev v programu, ki jih prej nismo odkrili zaradi prvotnega hrošča.

Te težave pogosto povzročajo deli programa (posamezne funkcije), ki niso bili testirani, ali pa pod predhodno netestiranimi pogoji.

Včasih je popravek enostaven in očiten. To je še posebej pogosto v logičnih napakah, ko je bilo sicer v originalnem načrtu pravilno določeno, a smo ga nepravilno vnesli v programsko kodo.

(primer4.1.png)
Logična napaka, namesto znaka + je znak *, ki pomeni v pythonu množenje in ne seštevanje

Lahko pa se zgodi, da je bil že načrt programa napačen. V tem primeru pa je treba večji del programa ponovno napisati. Ali pa je potrebno napisati kodo celotne aplikacije še enkrat.

V nekaterih primerih je zaželjeno izvesti najprej hitri popravek, kateremu sledi trajen popravek. Za to odločitev se odločimo glede na resnost/težo problema, prepoznavnost, pogostost in stranskih učinkih problema. Kot tudi narave popravka.

Naslednji/zadnji korak

Popravek in testiranje

Ko smo popravili programsko kodo, je pomembno še enkrat stestirati program in ugotoviti, če program obravnava popravke pravilno (da dobimo pravilne rezultate).

Testiranje je potrebno iz dveh razlogov:

  1. preverimo, če popravek obravnava prejšnje podatke/problem pravilno
  2. preverimo, da popravek ne pusti za sabo kakšnih nezaželjenih stranskih učinkov (drugih hroščev v kodi)

Za večje sisteme programov je dobro narediti serijo testov, ki jih poženemo po večjih spremembah v programski kodi. Po vsakem popravku in spremembi kode lahko s temi testi preverimo, če se naš program še vedno obnaša po pričakovanih merilih. Tudi ko dodajamo nove funkcije v program, lahko teste dodamo v testirni program.

Tehnike razhroščevanja 1/2

Tehnika razhroščevanja se razlikuje od enega programskega jezika do drugega.

Glavni člen razhroščevanja je razhroščevalnik. To je program, ki poteka sočasno z našim na novo napisanim programom in nam omogoča prekiniti program. Ko program začasno prekinemo, lahko preberemo pomnilniške naslove in druge ponavadi nam nevidne vrednosti spremenljivk našega programa.

(primer5.png)
Razhroščevalnik v okolju IDLE

Za iskanje mesta, kjer se nam program sesuje je dobro uporabljati razhroščevalnik.

Tehnike razhroščevanja 2/2

Drug način razhroščevanja je beleženje nekakšnega dnevnika. Shranjevanje/izpisovanje določenih spremenljivk (npr. v neko dstoteko, okno,...) nam lahko da dobro informacijo o tem, kako se naš program obnaša. Izpisovanje imen funkcij med izvajanjem programa je lahko koristno, ko ugotavljamo pri katerih delih se nam pojavi hrošč.

(primer6.png)
Beleženje spremenljivk z ukazom //print//

Obsežnejši programi ali sistemi programov so težji za razhroščevanje, medtem ko so manjši/krajši relativno enostavni. Pri večjih programih je koristno če jih razdelimo na manjše podprograme, ki jih potem rezhroščujemo. Temu pravimo Unit testi in vključujejo v vaš program dodatno kodo, s katero potem preverjamo le dele programa.

Razhroščevanje v pythonu

Hrošči v pythonu so lahko različne napake, ki nam jih program javi (Syntax error, Runtime error,...) ali pa jih vidimo v nepričakovanih rezultatih.

V tem primeru moramo ugotoviti, katera vrsta napake nam dela težave.

Ko se nam program v pythonu sesuje, nam tudi sporoči v katerem delu kode je naletel na težave in neko sporočilo o vrsti napake. Dostikrat iz sporočila ni dobro razvidno, na kakšno napako je program naletel. Tudi vrstica, ki nam jo sporoči, ni vedno pravilna. Torej je nek drug del kode problematičen. Sedaj ga moramo le še najti. Sliši se enostavno, vendar je včasih to kar precej trdovratno delo.

V pythonu pogosto razhroščujemo sami, brez programske pomoči. Ponavadi so to dodatni print stavki v programu, assert testi, ali pa na nekih delih sprožimo izjeme, da vidimo, če program sploh pride do tega dela kode.


Pri programiranju v pythonu obstaja v okolju IDLE tudi razhroščevalnik. Nahaja se v meniju Debug v Python Shellu.

(primer8.png)
IDLE Razhroščevalnik

Uporaba razhroščevalnika v IDLE-u

IDLE razhroščevalnik

Zagon programa v načinu razhroščevanja

  • Ko smo zagnali razhroščevalnik (Debug Control), se nam pojavi naslednje okno.

    (primer9.png)
    Debug control
  • V Shell-u pa se pojavi napis:

[DEBUG ON]
>>>

  • Odpremo kodo programa iz Shell-a: file-->open-->datoteka.py
  • Zaženemo to datoteko s pritiskom na gumb F5 ali pa kliknemo v menijski vrstici Run-->Run module.
  • Sedaj lahko poženemo program v načinu razhroščevanja.

IDLE razhroščevalnik

Ponavadi želimo določiti tako imenovani Break point - prekinitveno točko v programski kodi. To je točka/mesto v kodi, kjer se program v načinu razhroščevanja ustavi tik preden vstopi v funkcijo ali ukaz, kjer se ta točka nahaja.

V IDLU lahko določimo prekinitveno točko v kodi tako, da z desnim klikom na vrstici, kjer želimo da se program ustavi, izberemo Set Breakpoint. Vrstica, na kateri smo nastavili prekinitveno točko je sedaj označena z rumeno.

Program zaženemo s klikom na gumb Go v oknu Debug control. Program teče toliko časa, dokler ne pride do točke, kjer smo nastavili prekinitveno točko in na tem mestu počaka na naše nadaljnje ukaze. Ali pa nas počaka na mestu, kjer smo določili vnos nekega podatka. V tem primeru nas počaka da vnesemo ustrezen podatek.

Ko se program ustavi, lahko nadaljujemo po programu na različne načine tako, da klikamo na ustrezne gumbe.

Razhroščevanje v C#

V jeziku C# programiramo v programu Microsoft Visual Studio (Microsoft Visual C# Express). Ta program ima vgrajen razhroščevalnik, ki nam pomaga pri iskanju napak v naši programski kodi.

Naš program lahko zaženemo v debug načinu. To pomeni, da nam bo program, ko bo prišel do mesta s hroščem, napisal sporočilo o napaki (se bo sesul).

Na mesto, kjer se program sesuje, lahko dodamo t.i. Break Point. Dodamo jo z desnim klikom v vrstici, kjer se nahaja hrošč in izberemo Breakpoint -> Insert Breakpoint. Mesto, kjer smo označili s prekinitveno točko se obarva rdeče in ima na levi strani narisan rdeč krog.

Breakpoint - prekinitvena točka je točka v programski kodi, kjer se v debug načinu program ustavi, in lahko potem nadaljujemo po korakih pregled programa.

Če imamo v programu več prekinitvenih točk, potem se po vrsti od prvega do zadnjega lahko premikamo s pritiskanjem na gumb F5.

Po programu se lahko nato premikamo po vsaki vrstici posebej, kot jo "aktivira" program. Pomikamo se s pritiskom na gumb F10.

Če v funkciji kličemo katero drugo funkcijo, lahko na tem mestu pritisnemo F10. Ta bo sicer izvedel novo funkcijo, vendar ne bomo šli po njej po korakih. Če želimo vstopiti v to funkcijo, potem na mestu, kjer bi se morala izvesti pritisnemo F11 (namesto F10). S tem vstopimo v novo funkcijo, in se po njej sprehajamo po korakih, kot smo se po njej "nadrejeni". Ko pridemo do konca funkcije, nas program vrne na mesto v nadrejeni funkciji, kjer smo bili nazadnje.

Če ne želimo več biti v ugnezdeni funkciji, potem iz nje "izstopimo" s pritiskom kombinacije Shift + F11.

Primer uporabe razhroščevalnika v C#

Razhroščevanje v C#

Razhroščevalnik v Visual Studiu ima še veliko možnosti. Tu je nekaj trikov:

  • Razhroščevanje zna biti včasih velik izziv. Spremljanje vrednosti posameznih spremenljivk ali izrazov je lahko precej zamudno. Stvari se precej poenostavijo, če miško nastavimo na spremenljivko katere vrednost nas zanima. V tem primeru se nem pokaže trenutna vrednost te spremenljivke. Še več. Razni razredi in podatkovne strukture lahko s klikom na plus razširimo in pogledamo njihove posamezne vrednosti.
(cSpr6.png)
Razred ulomek, pregled vrednosti

Razhroščevanje v C#

  • Če nas še posebej zanima obnašanje določene spremenljivke med izvajanjem funkcije, potem jo lahko dodamo na neko listo, kjer spremljamo njeno vrednost. Najprej moramo odpreti okno Watch tako, da v izberemo v meniju Debug-->Windows-->Watch (Ctrl+D, W). Nato pa dodamo spremenljivko na to listo z desnim klikom na spremenljivko izberemo Add Watch, ali pa kar natipkamo njeno vrednost v tabeli v oknu Watch.

Razhroščevanje v C#

  • Če imamo na Watch listi spremenljivko, v kateri hranimo tabelo, nam izpiše pod njeno vrednostjo le njen naslov. Ko pa želimo videti vse vrednosti te tabele, potem kliknemo na plus, ki se nahaja na levi strani imena te spremenljivke v oknu Watch (glej sliko).

Razhroščevanje v C#

  • Včasih nas zanima, kako bi se funkcija obnašala, če bi kateri od spremenljivk spremenili vrednost. Da bi program prekinili, v kodi spremenili vrednost, nato pa še enkrat ponovili ves postopek razhroščevanja, se sliši nekoliko zamudno. Zato imamo možnost spreminjanja vrednosti kar medtem, ko imamo vklopljen način razhroščevanja. Vrednost spremenljivke lahko spremenimo tako, da se z miško postavimo nanjo in se nam prikaže njena trenutna vrednost. To vrednost lahko potem spremenimo sklikom nanjo in vnesemo željeno novo vrednost. Če smo vrednost te spremenljivke že uporabili v funkciji, potem se rezultat, ki ga izračunamo z uporabo te spremenjene spremenljivke ne bo poračunal še enkrat.
(cSpr3.png)
Na koncu spremenjena vrednost spremenljivke a

Razhroščevanje v C#

  • Včasih se zgodi, da ko preverjamo kodo po korakih, opazimo napako, ko kliče prvotna funkcija neko njej ugnezdeno funkcijo. V tem primeru nam tudi ni potrebno končati razhroščevanja in začeti ponovno, ampak lahko kliknemo na rumeno puščico na levi strani, ki nam pove kje se trenutno nahajamo. To puščico potem povlečemo do mesta, kjer naša prvotna funkcija kliče ugnezdeno. Na tem mestu lahko potem pritisnemo F11 in vstopino v to funkcijo. Rumeno puščico lahko premikamo samo znotraj posamezne funkcije. Ne moremo je premakniti iz neke funkcije v drugo.

    (cSpr7.png)
    Prestavljanje trenutne pozicije

Razhroščevanje v C#

  • Če poskušamo ustvariti nek redek dogodek in se nam program v načinu razhroščevanja pogosto ustavlja na prekinitveni točki, potem lahko le-temu dodamo nek pogoj. Prekinitveni točki moramo le nastaviti določen logičen pogoj in razhroščevalnik bo prekinitveno točko ignoriral, če ne bo ustrezala temu pogoju.

    • V Visual C# 2010 Express-u žal prekinitvenim točkam ne moremo nastavljati pogojev

Zaključek

Če potegnemo črto, je razhroščevanje pomemben del programiranja. Program v osnovi je hitro napisan. Največ časa nam pa vzame prav iskanje napak / hroščev v programski kodi.

Glavno je, da dobro pretestiramo kodo, na ogromno primerih. Posebej je treba biti pazljiv pri robnih primerih, saj so tam hrošči najpogostejši.

Velja, da če nismo našli hrošča v programu, še ne pomeni, da ga ni. Mogoče ga sami ne bomo nikoli našli, nekdo drug, ki pa bo uporabljal naš program, pa ga bo našel takoj.

Viri

0%
0%