Tämä kurssi on jo päättynyt.

Kurssin viimeisimmän version löydät täältä: O1: 2024

Debuggerin käyttö

Tästä sivusta:

Pääkysymyksiä: Miten tutkin ohjelman suoritusta vaihe vaiheelta? Mikä auttaisi ajonaikaisten virheiden etsimisessä?

Esitiedot: Tämä lisämateriaali olettaa esitietoja, joita on tarjolla kierroksilla 1–4. Sivu on parhaimmillaan, kun olet käynyt läpi varsinaisia lukuja ainakin lukuun 4.1 asti.

Pistearvo: Tämä on vapaaehtoista materiaalia, josta ei saa pisteitä. Yksi vapaaehtoinen harjoitus on kuitenkin tarjolla.

Oheisprojektit: Miscellaneous.

../_images/sound_icon5.png

Muuta: Sivulla on video, joka esittelee debuggerin käyttöä. Kuulokkeista tai kaiuttimista on siksi hyötyä. Ja on siellä lopussa vielä toinenkin video.

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.

Usein tällaisissa tilanteissa on tarpeen, että ohjelmoija käy läpi ohjelman suoritusta mielessään vaiheittain. Tätä 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.3 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)) this else another

}

Käytetään lisäksi tätä Experience-luokan metodeita kutsuvaa testiohjelmaa:

object ExperienceTest extends App {
  println("Aloitetaan ohjelman suoritus.")
  val wine1 = new Experience("Il Barco 2001", "ookoo", 6.69, 5)
  val wine2 = new 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 ExperienceTest-ohjelman suoritusta mielessäsi vaihe vaiheelta. Kiinnitä huomiosi siihen, mitä asioita sinun täytyy pitää mielessäsi missäkin vaiheessa.

Ohjelman suorituksen seuraamiseksi on eri vaiheissa muistettava muun muassa:

  • se, missä vaiheessa ohjelman suoritusta ollaan menossa (Mikä rivi ja mikä rivin kohta?),
  • kunkin oleellisen luokan/yksittäisolion määrittelyn oleelliset osat (jotka voi toki tarkistaa ohjelmakoodista),
  • muuttujien arvot juuri kyseistä riviä suoritettaessa (erityistä huolta vaativat var-muuttujat),
  • kuhunkin olioon liittyvät tiedot (ilmentymämuuttujien arvot), jotka määräytyvät aiempien olionluontikäskyjen ja mahdollisten tilaan vaikuttavien käskyjen perusteella,
  • metodia suoritettaessa: Mikä on this-olio tässä yhteydessä? Mihin olioon parametrimuuttuja viittaa tässä metodikutsussa?
  • metodista palatessa: Mistä juuri tämä metodikutsu tehtiin? Mihin kohtaan koodia palataan?
  • se, mitä oleellisia operaattoreita, funktioita ja luokkia on ja mitä ne tekevät, ja
  • moniosaisten lausekkeiden evaluoinnin yhteydessä syntyviä välituloksia.

Kaikki näistä asioista eivät näy suoraan ohjelmakoodista, vaikka koodia voikin käyttää niiden päättelemisessä apuna. Koska muistettavaa on paljon, on ohjelmoijan usein järkevää käyttää jotakin aputyökalua, 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.

Aputyökaluna voi käyttää vaikkapa paperilappusta, jolle kirjoitat tai piirrät muistiinpanot suorituksen vaiheista, olioista ja muuttujien arvoista. Usein on kuitenkin kätevämpää käyttää apuohjelmaa, joka osaa näyttää kuvauksen ohjelman suorituksesta. Monia yllä luetelluista asioista onkin kurssimateriaalissa näytetty animaatioina. Kuitenkaan tällaisia animaatioita ei ole aina tarjolla, joten mikä neuvoksi?

Debuggereista

Debuggerit (debugger) ovat ohjelman toiminnan vaiheittaiseen tarkasteluun tarkoitettuja apuohjelmia. Debuggerin avulla 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 samankaltaisia asioita kuin kurssimateriaaliin upotetut animaatiotkin. Kuitenkaan tyypillinen debuggeri ei esitä ohjelman etenemistä yhtä graafisesti eikä yksityiskohtaisesti. Debuggerit on tavallisesti suunniteltu ennen muuta kokeneiden ohjelmoijien eikä oppijoiden käyttöön, ja niillä on tarkoitus voida käsitellä hyvin laajojakin ohjelmakokonaisuuksia. Siksi esitystapakin on erilainen. Kuitenkin myös aloitteleva ohjelmoija voi hyötyä debuggerista.

Debuggeri voi olla erillinen työkalu tai sovelluskehittimen osa. Esimerkiksi Eclipsen Scala IDE tarjoaa Scala-ohjelmien tutkimiseen sopivan debuggerin.

Scala IDE:n debuggeri

Seuraavassa videossa on selitetty Scala IDE:n debuggerin käytön alkeita. Tutustu siihen huolella. Katsomisen jälkeen sinun on tarkoitus itse kokeilla debuggerin käyttöä.

Tärkeimpiä debuggeritoimintoja kootusti

Breakpointin eli keskeytyskohdan asettaminen
Valitse rivi. Sitten: Run ‣ Toggle Breakpoint tai Ctrl+Shift+B tai tuplaklikkaa harmaata palkkia rivinumeron vieressä. Samalla tavalla voit poistaa olemassa olevan keskeytyskohdan.
Ohjelman käynnistäminen debuggerissa
Valitse tiedosto. Sitten: Run ‣ Debug As ‣ Scala Application tai klikkaa tiedoston nimeä oikealla napilla ja valitse Debug As ‣ Scala Application tai etsi vastaava valinta koskettamalla ludetta lude yläreunan työkalupalkissa. Tämä käsky käynnistää ohjelman ja suorittaa sitä (ensimmäiseen) keskeytyskohtaan saakka (tai kokonaan, jos keskeytyskohtia ei ole).
Eteneminen suorittamalla yksi rivi
Run ‣ Step Over tai paina F6. Kone suorittaa koko rivin näyttämättä välivaiheita.
Eteneminen näyttäen myös välivaiheet
Run ‣ Step Into tai paina F5. Tämä käsky toimii kuten edellinen paitsi, että myös (tietynlaiset) välivaiheet näytetään ja koko rivin suorittaminen voi siis edellyttää lukuisia peräkkäisiä Step Into-komentoja. Erityisesti: jos valitulla rivillä tehdään metodikutsuja, niin Step Into "hyppää niiden sisään". Samaan tapaan debuggeri korostaa ne muuttujat, joista haetaan arvoja (vrt. kurssimateriaalin animaatioiden "muuttujien sisältä liukuvat arvot").
Palaaminen metodikutsun sisältä riville, josta sitä kutsuttiin
Run ‣ Step Return tai paina F7. Metodi, jossa oltiin, tulee samalla suoritetuksi loppuun.
Suorituksen jatkaminen ilman askellusta
Run ‣ Resume tai paina F8. Tämä suorittaa ohjelman seuraavaan keskeytykseen saakka tai loppuun, jos vastaan ei tule keskeytyskohtia.
Peruuttaminen
Ei onnistu, mutta voit lopettaa suorituksen ja aloittaa sen alusta.
Lopettaminen
Keskeytä valitsemalla Run ‣ Terminate tai painamalla Ctrl+F2 tai suorita ohjelma loppuun käyttäen yllä mainittuja etenemiskomentoja, kunnes Debug-välilehdellä lukee <terminated>.
Debuggerista poistuminen
Siirry takaisin tavalliseen Scala-perspektiiviin klikkaamalla Scala-sanaa Eclipsen oikeassa yläkulmassa. Yleensä tätä ennen kannattaa ajaa debuggauksen alla oleva ohjelma loppuun tai katkaista sen suoritus, ettei koneelle jää turhia ajoja pyörimään.

Monille mainituista debuggerikomennoista löytyy myös pikanäppäimet Debug-välilehden oikeasta yläkulmasta. Esimerkiksi Terminate-komennon voi antaa myös punaisella stop-nappulalla.

Kysyttyä: Miksei debuggeri osaa mennä takaperin?

Taaksepäin askelluksessa on joitakin teknisiä haasteita, eivätkä useimmat työkalut tue sitä. Mutta ei sekään mahdotonta ole, ja debuggerissa pakitus onkin ainakin 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 testiohjelman (käynnistysolion) ExcursionTest.

Lue ensin

Tutustu Excursion-luokan dokumentaatioon ja silmäile vähän ohjelmakoodiakin. Jatka vasta sitten.

Ja onhan asetustiedosto käytössä? Jos et ottanut luvussa 1.2 käyttöön kurssin Eclipse-asetustiedostoa tai et ole varma, otitko, niin tee se nyt. Ellei asetustiedosto ole käytössä, eivät kaikki seuraavista ohjeista päde.

Poraudu annetun ohjelman toimintaan nyt debuggerin avulla. Harjoituksen aikana tulee osoittautumaan, että Excursion-luokassa on pieni virhe.

Debuggerin käyttö voi olla aluksi hämmentävää, joten voi olla hyvä idea harjoitella sitä harjoitusryhmässä assistentin neuvomana!

Ekskursio-ohjelma debuggerissa

Alla olevat ohjeet ovat suuntaa antavia; saa soveltaa.

Aseta keskeytyskohta ExcursionTest-olion sisään riville, jossa on ensimmäinen tulostuskäsky. Käynnistä ohjelma debuggerissa. (Jos Eclipse kysyy Preferred Launcheria, valitse Scala JVM Launcher.) Etene ohjelman suoritusta vähitellen tutkien:

  • Käytä aluksi erityisesti Step Over-komentoa (F6).
  • Huomaa, miten tuloste muodostuu vähitellen Console-välilehdelle.
  • Koeta ymmärtää jokainen tapahtuva askel.
  • Tutki muuttujien arvoja Variables-välilehdellä.
  • Tarkkaile Debug-välilehdellä näkyvää kutsupinon kuvausta ja sen muuttumista metodikutsujen alkaessa ja päättyessä. (Kuten videossakin todettiin, lähinnä pinon yläosa on nyt kiinnostava.)
  • Kokeile myös Step Into-komentoa (F5).
  • Tutki ohjelman suoritusta, kunnes jossain vaiheessa Step Into- tai Step Over-komennon yhteydessä huomaat, että tapahtuu jotain outoa:

Ilmestyikö näkyviin aivan tuntemattoman näköistä koodia tiedostossa ResizableArray.scala? Niin pitikin:

  • Olet päätynyt Scalan valmiiden kirjastojen ohjelmakoodiin. ResizableArray on eräs luokka, jota on käytetty apuna Scalan puskuriluokan toteutuksessa.
  • Tänne päädyttiin, koska on syntynyt virhetilanne nimeltä IndexOutOfBoundsException. Silmäys kutsupinoon kertoo, että kyse on ekskursioluokan lastParticipant-metodista, joka on käyttänyt puskuria, joka on käyttänyt kyseistä ResizableArray-luokan koodia.
  • Nyt korostettu koodin rivi "heittää" (throw) virheen, mikä käytännössä tarkoittaa, että ohjelmamme tulee kaatumaan. Debuggeri on automaattisesti pysäyttänyt ohjelman suorituksen juuri hetkeä ennen ohjelman kaatumista.
  • Suorita korostettu rivi valitsemalla vaikkapa Step Over (F6). Näet, että tekstikonsoliin ilmestyy ajonaikaisesta ongelmasta kertova Stack trace -tuloste samaan tapaan kuin luvussa 4.1 kävi. Samalla ohjelman suoritus päättyy.

Esimerkiksi virheilmoituksesta voi poimia tiedon, että virhe aiheutui suoritettaessa sitä lastParticipant-metodin kutsua, joka tehtiin ExcursionTest-ohjelman rivillä 22. 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 22.
  • Anna Resume-komento (F8). Nyt jatkettiin juuri asettamaasi toiseen keskeytyskohtaan saakka. Tätä virhetilanteen aiheuttanutta riviä ei ole vielä suoritettu.
  • Valitse Step Into (F5) kolme kertaa. Ensimmäiset pari vain näyttävät, että testTrip-muuttujan arvo noudetaan. Kolmas vie sinut lastParticipant-metodin sisään.
  • Tutki Variables-välilehdeltä löytyviä Excursion-olion (eli this-olion, kun ollaan suorittamassa lastParticipant-metodia) tietoja. 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).

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.

Mikä kahden merkin (plus mielellään kahden välilyönnin) kokoinen lisäys korjaa virheen?

Keskeytyskohtien sijoittamisesta

Äsken asetimme keskeytyskohdat käynnistysolioon. Olisimme voineet myös asettaa keskeytyskohdan suoraan Excursion-luokan metodin lastParticipant koodiin. 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 tehdä keskeytyskohta, joissa suoritus katkeaa vain muuttujien arvojen ollessa tietynlaiset (ks. Breakpoint Properties Eclipsen valikosta).

Käytä debuggeria!

Tämän kurssin tehtävissä sinua ei yleensä erikseen käsketä käyttämään debuggeria. Toivottavasti kuitenkin 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ä. Kuitenkin jo se, että debuggeri auttaa ihmistä käymään läpi ohjelmakoodin suoritusta on usein 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ä sovelluskehittimessä.

Kiitokset videota suositelleelle aiemmalle kurssinkävijälle.

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 ja/tai monimutkainen.
  • Debuggeri on apuohjelma, jonka välityksellä ohjelmoija voi käydä läpi ohjelman suorituksen vaiheita ja tarkastella ohjelman tilaa.
  • Eclipsen Scala IDE tarjoaa debuggerin Scala-ohjelmien tutkimiseen. Siitä on hyötyä muun muassa tällä kurssilla.
  • Termejä sanastosivulla: debuggeri, keskeytyskohta.

Palaute

Tekijät

Tämän oppimateriaalin kehitystyössä on käytetty apuna tuhansilta opiskelijoilta kerättyä palautetta. Kiitos!

Kierrokset 1–13 ja niihin liittyvät tehtävät ja viikkokoosteet on laatinut Juha Sorva.

Kierrokset 14–20 on laatinut Otto Seppälä. Ne eivät ole julki syksyllä, mutta julkaistaan ennen kuin määräajat lähestyvät.

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 Riku Autio, Jaakko Kantojärvi, Teemu Lehtinen, Timi Seppälä, Teemu Sirkiä ja Aleksi Vartiainen.

Lukujen alkuja koristavat kuvat ja muut vastaavat kuvituskuvat on piirtänyt Christina Lassheikki.

Yksityiskohtaiset animaatiot Scala-ohjelmien suorituksen vaiheista ovat suunnitelleet Juha Sorva ja Teemu Sirkiä. Niiden teknisen toteutuksen ovat tehneet Teemu Sirkiä ja Riku Autio käyttäen Teemun toteuttamia Jsvee- ja Kelmu-työkaluja.

Muut diagrammit ja materiaaliin upotetut vuorovaikutteiset esitykset on laatinut Juha Sorva.

O1Library-ohjelmakirjaston ovat kehittäneet Aleksi Lukkarinen ja Juha Sorva. Useat sen keskeisistä osista tukeutuvat Aleksin SMCL-kirjastoon.

Opetustapa, jossa 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+ on luotu Aallon LeTech-tutkimusryhmässä pitkälti opiskelijavoimin. Pääkehittäjänä toimii tällä hetkellä Jaakko Kantojärvi, jonka lisäksi järjestelmää kehittävät useat tietotekniikan ja informaatioverkostojen opiskelijat.

Kurssin tämänhetkinen henkilökunta on kerrottu luvussa 1.1.

Lisäkiitokset tähän lukuun

Videon tekivät Markus Murhu ja Henri Niva. Käsikirjoittamiseen osallistui myös muuta Aallon Tietotekniikan laitoksen opetushenkilökuntaa.

../_images/imho15.png
Palautusta lähetetään...