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.2 asti.

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

Oheismoduulit: Miscellaneous.

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)) 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. 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 viittavat 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 ohjelman toiminnan vaiheittaiseen tarkasteluun tarkoitettuja apuohjelmia. 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 samankaltaisia asioita kuin kurssimateriaaliin upotetut animaatiotkin. Kuitenkaan tyypillinen debuggeri ei esitä ohjelman etenemistä yhtä graafisesti eikä yksityiskohtaisesti, koska debuggerit on tavallisesti suunniteltu ennen muuta kokeneiden ohjelmoijien eikä oppijoiden käyttöön ja suurten ohjelmien käsittelyyn tarvitaan erilainen esitystapa. Kuitenkin myös aloitteleva ohjelmoija voi hyötyä debuggerista.

Debuggeri voi olla erillinen työkalu tai sovelluskehittimen 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 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 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 lude käy. Ohjelma käynnistyy ja suorittuu (ensimmäiseen) keskeytyskohtaan saakka (tai kokonaan, jos keskeytyskohtia ei ole).
Eteneminen suorittamalla yksi rivi
Run → Debugging Actions → Step Over tai paina F8 (tai vastaava ikoni Debug-välilehdellä). Kone suorittaa koko rivin näyttämättä välivaiheita. Erityisesti: jos rivillä on funktiokutsu, sen suorituksen vaiheita ei esitellä.
Eteneminen näyttäen myös välivaiheet
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 edellyttää 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-välilehdellä, 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
Ei onnistu, mutta voit lopettaa suorituksen ja aloittaa sen alusta.
Lopettaminen
Keskeytä esimerkiksi valitsemalla Run → Stop tai painamalla Ctrl+F2 tai suorita ohjelma loppuun käyttäen yllä mainittuja etenemiskomentoja.

Monille mainituista debuggerikomennoista löytyy myös pikanäppäimet debuggerinäkymän reunoista. Esimerkiksi Stop-komennon voi antaa myös 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 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 testiohjelman (käynnistysolion) ExcursionTest.

Lue ensin

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

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

Seuraavat ohjeet ovat suuntaa antavia. Saa soveltaa; melkeinpä pitää.

Ekskursio-ohjelma debuggerissa

Aseta keskeytyskohta ExcursionTest-olion sisään val-alkuiselle riville, jolla määritellään testTrip-muuttuja.

Käynnistä ohjelma debuggerissa.

Suoritus pysähtyy kesketyskohtaan. Etene ohjelman suoritusta vähitellen tutkien:

  • Käytä aluksi Step Over-komentoa F8.
  • Katso, miten tuloste muodostuu vähitellen Console-osiossa, joka näkyy debuggauksen aikana osana Debug-välilehteä.
  • Koeta ymmärtää jokainen tapahtuva askel.
  • Tutki muuttujien arvoja Debug-välilehden Variables-osiossa.
  • Kokeile myös Step Into-komentoa F7.
    • Jos jossain vaiheessa päädyt oudosti Predef-nimisen olion toteutukseen tai muuhun scala-nimisen pakkauksen koodin , se tarkoittanee, että olet edennyt Step Intolla Scalan println-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.
  • Tarkkaile Debug-välilehden Frames-osiossa näkyvää kutsupinon kehysten→ luetteloa ja sen muuttumista metodikutsujen alkaessa ja päättyessä. (Lähinnä pinon yläosa on nyt kiinnostava.)
  • Tutki ohjelman suoritusta, kunnes jossain vaiheessa Step Into- tai Step Over-komennon yhteydessä huomaat, että tapahtuu jotakin erilaista:

Katosiko kutsupino Frames-kohdasta ja muuttujat arvoineen Variables-kohdasta? Niin pitikin:

  • 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 käyttänyt ArrayBuffer-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 ExcursionTest-ohjelman 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 aiheuttanutta riviä ei ole 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-välilehdeltä löytyviä Excursion-olion tietoja. (Variables-näkymässä 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.

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 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)

Äskeinen ExcursionTest-ohjelma oli rakennettu näin:

object ExcursionTest extends App {

  runFactoryScenario()

  def runFactoryScenario() = {
    // Kutsu eräitä Excursion-luokan metodeita täällä
    // (apufunktiossa) palaten tänne kutsujen välillä.
  }

}

Tuolle extends App -määreiselle käynnistysoliolle oli siis kirjattu funktio, jota kutsuttiin kertaalleen olion rungosta.

Ei tuossa mitään vikaa ole. On kuitenkin hyvä tietää, ettei ole sattumaa, että tämä esimerkkimme on rakennettu juuri noin.

Syynä on, että nykyisessä IntelliJ’n Scala-debuggerissa on rajoitus: jos haluat debugata käynnistysoliota, joka kutsuu metodeita useasti suoraan olion rungosta, niin debuggeri ei osaa näyttää sitä havainnollisella tavalla. Tämä siis ei näy debuggerissa kunnolla:

object ExcursionTest extends App {

  // Kutsu eräitä Excursion-luokan metodeita täällä (suoraan
  // App-olion rungossa) palaten tänne kutsujen välillä.

}

Onneksi tuo rajoitus on helppo kiertää: lisää apufunktio kuten runFactoryScenario yllä ja kutsu metodeita sieltä.

Rajoituksen sopii odottaa poistuvan tulevissa IntelliJ’n ja Scalan versioissa, mutta toistaiseksi näin.

Tästä 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 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 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ä sovelluskehittimessä.

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ä ihminen voi käydä läpi ohjelman suorituksen vaiheita ja tarkastella ohjelman tilaa.
  • IntelliJ tarjoaa debuggerin. 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!

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, Joonatan Honkamaa, Jaakko Kantojärvi, Niklas Kröger, Teemu Lehtinen, Strasdosky Otewa, 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 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 ja Juha Sorva. 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. Pääkehittäjänä on tällä hetkellä Markku Riekkinen, jonka lisäksi A+:aa 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 ovat luoneet Nikolai Denissov, Olli Kiljunen, Nikolas Drosdek, Styliani Tsovou, Jaakko Närhi ja Paweł Stróżański yhteistyössä Juha Sorvan, Otto Seppälän, Arto Hellaksen ja muiden kanssa.

Kurssin tämänhetkinen henkilökunta löytyy luvusta 1.1.

Lisäkiitokset tähän lukuun

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

Kiitokset Jobs-videota suositelleelle aiemmalle kurssinkävijälle.

a drop of ink
Palautusta lähetetään...