Debuggerin käyttö
Johdanto: ohjelman suorituksen seuraamisesta
Ohjelmoijan ja ohjelmoinnin opiskelijan on usein tarpeen selvittää, mitä jokin olemassa oleva ohjelma tekee. Esimerkiksi:
Pitää jatkokehittää aiemmin kehitettyä ohjelmaa.
Pitää kirjoittaa dokumentaatio toisen tekemästä ohjelmasta.
Pitää etsiä virhe väärin toimivasta ohjelmasta (itse kirjoitetusta tai toisen tekemästä).
Pitää tutustua opettajan antamaan esimerkkiohjelmaan.
Tällaisissa tilanteissa ohjelmoija voi käydä läpi ohjelman suoritusta mielessään vaiheittain. Vastaavaa pohdintaa tarvitaan myös uutta ohjelmaa kirjoittaessa: jotta ohjelmoija voisi arvioida, toimiiko hänen parhaillaan kirjoittamansa ohjelma, hänen täytyy kyetä "suorittamaan ohjelmaa päässään".
Lähestytään aihetta esimerkin kautta. Käytetään luvun 3.4 esittelemää esimerkkiä,
Experience
-luokkaa, joka on määritelty näin:
class Experience(val name: String, val description: String, val price: Double, val rating: Int):
def valueForMoney = this.rating / this.price
def isBetterThan(another: Experience) = this.rating > another.rating
def chooseBetter(another: Experience) = if this.isBetterThan(another) then this else another
end Experience
Käytetään lisäksi tätä Experience
-luokan metodeita kutsuvaa testiohjelmaa:
@main def testExperiences() =
println("Aloitetaan ohjelman suoritus.")
val wine1 = Experience("Il Barco 2001", "ookoo", 6.69, 5)
val wine2 = Experience("Tollo Rosso", "turhahko", 6.19, 3)
val better = wine1.chooseBetter(wine2)
var result = better.name
println(result)
result = "Parempi kuin wine1? " + better.isBetterThan(wine1)
println(result)
Käy läpi testExperiences
-ohjelman suoritusta mielessäsi vaihe vaiheelta. Jos teet sen
huolellisesti, huomaat, että kussakin vaiheessa on pidettävä mielessä monta asiaa:
Missä vaiheessa ohjelman suoritusta ollaan menossa? (Mikä rivi ja mikä rivin kohta?)
Mitkä ovat kunkin oleellisen luokan/yksittäisolion määrittelyn oleelliset osat? (Ne voi toki tarkistaa ohjelmakoodistakin.)
Mitkä ovat muuttujien arvot juuri kyseistä riviä suoritettaessa? Erityistä huolta vaativat
var
-muuttujat.Mitkä ovat kuhunkin olioon liittyvät tiedot (ilmentymämuuttujien arvot)? Ne määräytyvät aiempien olionluontikäskyjen ja mahdollisten tilaan vaikuttavien käskyjen perusteella.
(metodia suoritettaessa:) Mikä on
this
-olio tässä yhteydessä? Mitkä ovat parametrimuuttujien arvot tässä metodikutsussa?(metodista palatessa:) Mistä juuri tämä metodikutsu tehtiin? Mihin kohtaan koodia palataan?
Mitä oleellisia operaattoreita, funktioita ja luokkia on, ja mitä ne tekevät?
Mitä välituloksia on syntynyt moniosaisia lausekkeita evaluoidessa?
Kaikki näistä asioista eivät näy suoraan ohjelmakoodista. Koska muistettavaa on paljon, ohjelmoijat käyttävät monesti aputyökaluja, joka helpottaa yksityiskohtien hallintaa. Näin aivokapasiteettia vapautuu, ja ohjelmoija voi paremmin keskittyä miettimään esimerkiksi etsittävän virheen sijaintia tai tutkittavan ohjelman yleisiä tavoitteita ja toimintaperiaatteita.
Paperilappunenkin on varsin hyvä aputyökalu: voit kirjoittaa muistiinpanoja suorituksen vaiheista ja hahmotella kaavioita olioista ja muuttujien arvoista.
Paperin ja kynän lisäksi tai sijaan voi käyttää apuohjelmaa, joka kuvaa ohjelman suorituksen. Monia yllä luetelluista asioista onkin kurssimateriaalissa näytetty animaatioina. Kuitenkaan tällaisia animaatioita ei ole aina tarjolla, joten mikä neuvoksi?
Debuggereista
Debuggerit (debugger) ovat apuohjelmia, joilla voi tarkastella ohjelman toimintaa vaiheittain. Debuggerilla voi ajaa tallennettua ohjelmaa rivi riviltä ja tutkia samalla ohjelman tilaa. Erityisen hyödyllisiä debuggerit ovat virheiden eli "bugien" etsimisessä, mistä työkalun nimikin juontuu.
Debuggeri näyttää ohjelmasta pitkälti vastaavia asioita kuin kurssimateriaaliin upotetut animaatiotkin. Kuitenkaan tyypillinen debuggeri ei esitä ohjelman etenemistä yhtä graafisesti eikä yksityiskohtaisesti, koska debuggerit on tavallisesti suunniteltu kokeneiden ohjelmoijien eikä oppijoiden käyttöön ja koska suurten ohjelmien käsittelyyn tarvitaan erilainen esitystapa. Silti myös aloitteleva ohjelmoija voi hyötyä debuggerista.
Debuggeri voi olla erillinen työkalu tai integroidun työkaluston osa. Esimerkiksi IntelliJ tarjoaa debuggerin.
IntelliJ’n debuggeri
Debuggerin sujuva käyttö vaatii treeniä. Tässä vinkkejä alkuun pääsemiseen:
Silmäile läpi luettelo yleisimmistä IntelliJ’n debuggerin toiminnoista alla.
Tee pieni harjoitus alempana tällä sivulla. Hyödynnä mainittuja toimintoja.
Kokeile debuggeria omin päin: kirjoita pieni testiohjelma ja käsittele sitä debuggerissa tai tutki kurssin esimerkkiohjelmia.
Etsi lisämateriaalia netistä. Esimerkiksi tässä YouTube-videossa esitellään lyhyesti debuggerin ominaisuuksia (enemmänkin kuin kurssilla tarvitset) ja IntelliJ’n sivustolla selitetään debuggerin käyttöä (esimerkkinä Java-koodia käyttäen, mutta samat toiminnot toimivat Scalaankin).
Pyydä assareilta apua harjoitusryhmissä.
Luettelo tärkeimmistä debuggeritoiminnoista
- Breakpointin eli keskeytyskohdan asettaminen
Ennen kuin aloitat ohjelman tutkimisen debuggerissa, on yleensä syytä asettaa ainakin yksi keskeytyskohta (breakpoint). Keskeytyskohta on ohjelman kohta, jossa debuggeri pysäyttää suorituksen ja antaa sinun tutkia ohjelman tilaa. Mieti ensin, mille riville haluat keskeytyskohdan. (Voit valita itse, mutta jos emmit, valitse kokeeksi vaikka ensimmäinen käsky käynnistysolion tai käynnistysfunktion sisällä.) Merkitse keskeytyskohta siirtämällä kursori tuolle riville ja valitsemalla Run → Toggle Breakpoint tai painamalla Ctrl+F8. Vielä yksinkertaisemmin tuo käy, kun klikkaat palkkia rivin ja rivinumeron välissä. Samalla tavalla voit poistaa olemassa olevan keskeytyskohdan. Punainen pallura koodin marginaalissa kertoo siinä olevan keskeytyskohdan.
- Ohjelman käynnistäminen debuggerissa
Aja käyttäen Run-komennon sijaan Debug-komentoa. Se löytyy esimerkiksi valitsemalla käynnistystiedosto ja sitten joko sen oheisvalikko tai yläpalkin Run-valikko, joka sisältää myös Debug-käskyn; työkalupalkin ludekin käy. Ohjelma käynnistyy ja suorittuu (ensimmäiseen) keskeytyskohtaan saakka (tai kokonaan, jos keskeytyskohtia ei ole).
- Eteneminen suorittamalla rivi kokonaan
Run → Debugging Actions → Step Over tai paina F8 (tai vastaava ikoni Debug-osiosta). Kone suorittaa koko rivin näyttämättä välivaiheita. Erityisesti: jos rivillä on funktiokutsu, sen suorituksen vaiheita ei esitellä.
- Eteneminen näyttäen välivaiheetkin
Run → Debugging Actions → Step Into tai paina F7. Tämä käsky toimii kuten edellinen paitsi, että myös (tietynlaiset) välivaiheet näytetään ja koko rivin suorittaminen voi siis vaatia lukuisia peräkkäisiä Step Into-komentoja. Erityisesti: jos valitulla rivillä on funktiokutsu, niin Step Into "hyppää sen sisään". Jos rivillä on useita funktiokutsuja, IntelliJ pyytää sinua valitsemaan nuolinäppäimillä, minkä sisään askellat. (Se saattaa tarjota vaihtoehdoksi myös kirjastofunktioita kuten
println
, joiden sisään askeltaminen ei kuitenkaan yleensä ole hyödyllistä.)- Ohjelman tilan tutkiminen
Näet tietoja muuttujista Debug-osiosta, joka tulee näkyviin IntelliJ’n alareunaan kun käynnistät debuggerin.
- Palaaminen funktiokutsun sisältä riville, josta sitä kutsuttiin
Run → Debugging Actions → Step Out tai paina Shift+F8. Funktio, jossa oltiin, tulee samalla suoritetuksi loppuun.
- Suorituksen jatkaminen ilman askellusta
Run → Debugging Actions → Resume tai paina F9. Tämä suorittaa ohjelman seuraavaan keskeytykseen saakka tai loppuun, jos vastaan ei tule keskeytyskohtia.
- Peruuttaminen
Tälle ei ole komentoa, mutta voit lopettaa suorituksen ja aloittaa sen alusta.
- Lopettaminen
Keskeytä esimerkiksi valitsemalla Run → Stop tai painamalla Ctrl+F2 tai suorita ohjelma loppuun yllä mainituilla etenemiskomennoilla.
Monille mainituista debuggerikomennoista löytyy myös kuvakkeet Debug-näkymän reunoista. Esimerkiksi Stop-komennon voi antaa myös -nappulalla.
Kysyttyä: Miksei debuggeri osaa mennä takaperin?
Taaksepäin askellus on teknisesti vaativampaa, eivätkä useimmat työkalut tue sitä. Mutta ei sekään mahdotonta ole, ja taitaa olla vähän yleistymään päin.
Debuggeriharjoitus
Nouda käyttöösi projekti Miscellaneous. Se sisältää luokan nimeltä o1.excursion.Excursion
ja sitä käyttävän ohjelman testExcursion
eri tiedostossa.
Lue ensin
Tutustu Excursion
-luokan dokumentaatioon ja silmäile vähän
ohjelmakoodiakin. Jatka vasta sitten.
Poraudu annetun ohjelman toimintaan debuggerilla. Harjoituksen aikana tulee osoittautumaan,
että Excursion
-luokassa on pieni virhe.
Koska debuggerin käyttö voi olla aluksi hämmentävää, saatat haluta kokeilla tätä harjoitusryhmässä, jossa voit pyytää assistentilta neuvoa!
Seuraavat ohjeet ovat suuntaa antavia. Saa soveltaa; melkeinpä pitää.
Ekskursio-ohjelma debuggerissa
Aseta keskeytyskohta testExcursion
-metodin riville, jolla on ensimmäinen println
-käsky.
Käynnistä ohjelma debuggerissa.
Suoritus pysähtyy keskeytyskohtaan. Korostettuna on tulostuskäskyn sisältävä rivi. Etene ohjelman suoritusta vähitellen tutkien:
Paina Step Over eli F8 suorittaaksesi koko korostetun rivin. Korostus siirtyy seuraavalle riville.
Ota alhaalta Debug-osiosta esiin Console-välilehti. Siellä näkyy nyt ohjelman osittainen tuloste: ensimmäinen tulostuskäsky on suoritettu.
Seuravaaksi suoritettavana on runFactoryScenario()
-funktiokutsu. Älä kuitenkaan tällä
kertaa suorita koko funktiota kerralla painamalla Step Over. Tutkitaan sen sijaan
tuon funktion sisältöä vaiheittain:
Paina Step Into F7. Suoritus siirtyy kutsutun funktion alkuun.
Huomaa: Debug-osion Debugger-kohdassa näkyy kutsupinon kehysten→ luettelo. Nyt olet
runFactoryScenario
-funktiossa, jota on kutsuttutestExcursion
ista.
Etene funktiossa vähitellen omaan tahtiisi:
Käytä muutamalle ensimmäiselle riville Step Over-komentoa F8.
Koeta ymmärtää jokainen tapahtuva askel. Katso, miten tuloste muodostuu vähitellen Console-välilehdelle.
Pidä silmällä muuttujien arvoja Debugger-osion luettelossa. Erityisesti:
testTrip
viittaaExcursion
-olioon, jolla on puolestaan omat muuttujansa.Kokeile myös Step Into-komentoa F7 askeltaaksesi esimerkiksi
registerInterest
-metodin sisään.Jos jossain vaiheessa päädyt oudosti
Predef
-nimisen olion toteutukseen tai muuhunscala
-nimisen pakkauksen koodin, se tarkoittanee, että olet edennyt Step Intolla Scalanprintln
-käskyn toteutuksen sisään, mikä tuskin on se, mitä halusit. Ei hätää. Voit joko palata takaisin Step Out -käskyllä Shift+F8 tai vaikka aloittaa alustakin.
Pidä silmällä Debugger-osion kutsupinoa ja sen muuttumista metodikutsujen alkaessa ja päättyessä.
Tutki ohjelman suoritusta, kunnes jossain vaiheessa Step Into- tai Step Over-komennon yhteydessä huomaat, että tapahtuu jotakin erilaista:
Katosiko kutsupino Debugger-kohdasta ja muuttujat arvoineen siitä vierestä? Niin pitikin. Tutkitaan:
Ohjelma-ajo katkesi. Silmäys Console-osioon kertoo, että on syntynyt virhetilanne nimeltä
IndexOutOfBoundsException
.Virheen nimen alla näkyy, että kyse on ekskursioluokan
lastParticipant
-metodista, joka on kutsunutArrayBuffer
-luokan koodia. (ArrayBuffer
on luokka jota on käytetty Scalan puskurien toteutuksessa.)Äsken suoritettu koodirivi "heitti" (
throw
) virheen, joka kaatoi ohjelmamme.
Olet jo ehkä huomannut — ja virheilmoituksesta voit varmistaa — että virhe
aiheutui, kun suoritettiin sitä lastParticipant
-metodin kutsua, joka tehtiin
test.scala
-tiedoston rivillä 26.
Tutki tätä metodikutsua tarkemmin debuggerissa. (Tee se kokeeksi, vaikka olisitkin jo keksinyt, missä vika piilee.) Voit toimia vaikkapa seuraavasti.
Virhetilanteen tutkiskelua
Käynnistä taas debuggeri. Suoritus keskeytyy aiemmin asettamaasi keskeytyskohtaan.
Lisää uusi keskeytyskohta riville 26.
Anna Resume-komento F9. Nyt jatkettiin juuri asettamaasi toiseen keskeytyskohtaan saakka. Tätä virhetilanteen aiheuttavaa riviä ei vielä suoritettu.
Valitse Step Into F7 ja vaihtoehdoista lastParticipant
(eikä
println
). Päädyt valitun metodin sisään.
Ei välitetä tällä kertaa numberOfInterested
- ja numberOfParticipants
-metodien
sisäisestä toteutuksesta vaan askelletaan niiden yli. Paina Step Over F8
pari kertaa ohittaaksesi if
-rivin ja val
-rivin yksityiskohdat. Nuo rivit eivät
vielä kaada ohjelmaa.
Tutki Debug-näkymästä löytyviä Excursion
-olion tietoja. (Muuttujaluettelossa
this
viittaa tuohon olioon sinä aikana, kun olemme suorittamassa tuon olion
lastParticipant
-metodia.) Löydät sieltä esimerkiksi interestedStudents
-muuttujan
osoittaman puskurin ja sen sisältä neljä ilmoittautuneen henkilön nimeä sekä merkinnän
puskurin tämänhetkisestä koosta (4).
Huomaa myös paikallinen muuttuja numberOfLast
.
Palauta mieleen: virheen tyyppi on IndexOutOfBoundsException
eli "indeksi on rajojen
ulkopuolella". Huomaatko virheen lastParticipant
-metodissa? Päättele koodin perusteella
ja tutki tilannetta halutessasi lisää debuggerissa.
Jos suoritat vielä seuraavankin Some
-alkuisen rivin, ohjelma kaatuu.
Miten sijoittaa keskeytyskohdat?
Äsken asetimme keskeytyskohdat käynnistysolioon. Olisimme voineet myös asettaa
keskeytyskohdan suoraan Excursion
-luokan metodiin lastParticipant
. Tällöin
keskeytettäisiin aina, kun tuota metodia kutsutaan.
Vastaavasti graafisella käyttöliittymällä varustetussa ohjelmassa voit asettaa keskeytyskohdan vaikkapa tapahtumankäsittelijämetodiin tai johonkin niistä mallin metodeista, joiden metodeja tapahtumankäsittelijä kutsuu. Näin suoritus keskeytyy kyseisen metodin aktivoituessa.
On myös mahdollista asettaa keskeytyskohta, joissa suoritus katkeaa vain muuttujien arvojen ollessa tietynlaiset. Kokeile napsauttaa punaista keskeytyskohtapalleroa hiiren oikealla napilla ja tutki. Ja sellaisenkin keskeytyskohdan voi asettaa, joka katkaisee ohjelman suorituksen, kun suorituksen aikana tapahtuu virhe (Run → View Breakpoints → Java Exception Breakpoints).
Valitettava rajoitus (IntelliJ’n Scala-debuggerissa)
Nykyisessä IntelliJ’n Scala-debuggerissa on rajoitus: se ei toimi kunnolla kaikille
käynnistysolioille (App
). Esimerkkikoodi:
object TroubleWithIJDebugger extends App:
println("Starting")
myMethod()
println("Called myMethod")
myMethod()
println("Done")
def myMethod() =
println("Hello from myMethod")
println("Now let's go back to the main code")
end TroubleWithIJDebugger
Debuggeri reistailee, jos seuraavat kolme ehtoa täyttyvät:
tuosta koodista kutsutaan metodeita, joiden sisään haluat askeltaa; ja
haluat palata kutsutuista metodeista takaisin suoraan App
-olion
sisällä olevaan koodiin ja jatkaa sen parissa.
Onneksi tuo rajoitus on helppo kiertää järjestämällä koodi toisin. Kumpi vain seuraavista toimii debuggerissa nätisti:
object HelperMethod extends App: actuallyDoStuff() def actuallyDoStuff() = // debugattava toiminta on metodissa println("Starting") myMethod() println("Called myMethod") myMethod() println("Done") def myMethod() = println("Hello from myMethod") println("Now let's go back to the main code") end HelperMethod@main def mainFunctionInsteadOfApp() = println("Starting") myMethod() println("Called myMethod") myMethod() println("Done") def myMethod() = println("Hello from myMethod") println("Now let's go back to the main code") end mainFunctionInsteadOfApp
Mainittu rajoitus poistunee tulevissa IntelliJ’n versioissa, mutta toistaiseksi näin. Rajoituksesta ei tarvitse piitata, kun debuggaat muunlaisia ohjelmia, joissa ei ole tarpeen tehdä useita metodikutsuja käynnistysoliosta sinne aina välillä palaten.
Lopuksi: debuggereista ja debuggaamisesta
Tämän kurssin tehtävissä sinua ei yleensä erikseen käsketä käyttämään debuggeria. Toivottavasti silti omaksut tämä työkalun arsenaaliisi vähitellen. Älä unohda, että sinulla on debuggeri käytettävissäsi, kun huomaat laatimasi ohjelman toimivan väärin.
Kuten olet nähnyt, niin nimestään huolimatta debuggeri ei ole taikakalu, joka korjaa ohjelmista virheitä. Se ei "edes" etsi niitä. Näihin tehtäviin tarvitaan ihmistä. Silti jo se, että debuggeri auttaa ihmistä käymään läpi ohjelmakoodin suoritusta, on joskus kullanarvoinen apu.
Ihmistä tarvitsevan debuggaustyön väistämättömyyteen liittyy myös tämä vajaan minuutin video, jossa Steve Jobs mainostaa ohjelmoijan arkea helpottavaa ominaisuutta eräässä IDE:ssä.
Yhteenvetoa
Ohjelmoijan on kyettävä "suorittamaan ohjelmaa päässään" vaihe vaiheelta.
Tekniset apuvälineet tukevat ohjelman suoritusvaiheiden selvittämistä erityisesti silloin, kun ohjelma on tuntematon, virheellinen tai monimutkainen.
Debuggeri on apuohjelma, jonka välityksellä ihminen voi käydä läpi ohjelman suorituksen vaiheita ja tarkastella ohjelman tilaa.
IntelliJ tarjoaa debuggerin. Siitä voi olla hyötyä tällä kurssilla ja muutenkin.
Termejä sanastosivulla: debuggeri, keskeytyskohta.
Palaute
Tekijät
Tämän oppimateriaalin kehitystyössä on käytetty apuna tuhansilta opiskelijoilta kerättyä palautetta. Kiitos!
Materiaalin luvut tehtävineen ja viikkokoosteineen on laatinut Juha Sorva.
Liitesivut (sanasto, Scala-kooste, usein kysytyt kysymykset jne.) on kirjoittanut Juha Sorva sikäli kuin sivulla ei ole toisin mainittu.
Tehtävien automaattisen arvioinnin ovat toteuttaneet: (aakkosjärjestyksessä) Riku Autio, Nikolas Drosdek, Kaisa Ek, Joonatan Honkamaa, Antti Immonen, Jaakko Kantojärvi, Onni Komulainen, Niklas Kröger, Kalle Laitinen, Teemu Lehtinen, Mikael Lenander, Ilona Ma, Jaakko Nakaza, Strasdosky Otewa, Timi Seppälä, Teemu Sirkiä, Joel Toppinen, Anna Valldeoriola Cardó ja Aleksi Vartiainen.
Lukujen alkuja koristavat kuvat ja muut vastaavat kuvituskuvat on piirtänyt Christina Lassheikki.
Yksityiskohtaiset animaatiot Scala-ohjelmien suorituksen vaiheista suunnittelivat Juha Sorva ja Teemu Sirkiä. Teemu Sirkiä ja Riku Autio toteuttivat ne apunaan Teemun aiemmin rakentamat työkalut Jsvee ja Kelmu.
Muut diagrammit ja materiaaliin upotetut vuorovaikutteiset esitykset laati Juha Sorva.
O1Library-ohjelmakirjaston ovat kehittäneet Aleksi Lukkarinen, Juha Sorva ja Jaakko Nakaza. Useat sen keskeisistä osista tukeutuvat Aleksin SMCL-kirjastoon.
Tapa, jolla käytämme O1Libraryn työkaluja (kuten Pic
) yksinkertaiseen graafiseen
ohjelmointiin, on saanut vaikutteita tekijöiden Flatt, Felleisen, Findler ja Krishnamurthi
oppikirjasta How to Design Programs sekä Stephen Blochin oppikirjasta Picturing Programs.
Oppimisalusta A+ luotiin alun perin Aallon LeTech-tutkimusryhmässä pitkälti opiskelijavoimin. Nykyään tätä avoimen lähdekoodin projektia kehittää Tietotekniikan laitoksen opetusteknologiatiimi ja tarjoaa palveluna laitoksen IT-tuki; sitä ovat kehittäneet kymmenet Aallon opiskelijat ja muut.
A+ Courses -lisäosa, joka tukee A+:aa ja O1-kurssia IntelliJ-ohjelmointiympäristössä, on toinen avoin projekti. Sen suunnitteluun ja toteutukseen on osallistunut useita opiskelijoita yhteistyössä O1-kurssin opettajien kanssa.
Kurssin tämänhetkinen henkilökunta löytyy luvusta 1.1.
Lisäkiitokset tähän lukuun
Kiitokset Jobs-videota suositelleelle aiemmalle kurssinkävijälle.
Haluat tutkia vaihe vaiheelta koodia, joka on kirjoitettu suoraan käynnistysolion (
App
) sisään (siis ei metodiin);