Virhetilanteet rajapinnoissa

Erilaisiin virhetilanteisiin varautuminen on mutkikas ohjelmoinnin elementti, joka saattaa vaatia jopa enemmän koodia kuin itse ongelman ratkaisu. Virhetilanteisiin reagoiminen onkin usein vaikeaa. Erilaisten suoritusreittien seurauksena koodista tulee helposti sekavaa. Virheen sattuessa saatetaan joutua tekemään monenlaista siivoustyötä, mikä helposti jää tekemättä. Virheestä toipuminen siten, että ohjelman suoritusta voitaisiin vielä jatkaa, vaatii, että kesken jääneet työt voidaan jotenkin peruuttaa, mikä ei aina ole edes mahdollista.

Ohjelmoinnissa on perusteltua lähtökohtana varautua siihen, että muut voivat toimia väärin. Kun virhe havaitaan ajoissa, siihen on mahdollista sopeutua ja parhaassa tapauksessa jopa jatkaa ohjelman suoritusta niin, ettei virhe näy ohjelman ulkoisessa käytöksessä. Kun ohjelmakomponentissa tapahtuu virhe, on useita erilaisia tapoja, joilla virheeseen voidaan reagoida. Riippuu tilanteesta, mikä milloinkin on sopivin.

  • Suorituksen keskeytys (engl. abrupt termination) on äärimmäisin tapa reagoida ohjelmassa tapahtuvaan virheeseen. Keskeytyksessä ohjelman suorittaminen yksikertaisesti keskeytetään välittömästi virheeseen. Keskeytystä tulisi välttää. Samalla se on varsin tyypillisesti ajoympäristöjen oletustoiminta.

  • Hallittu lopetus (engl. abort, exit) pyrkii siivoamaan ohjelmiston varaamat resurssit ja ilmoittamaan virheestä käyttäjälle. Lisäksi virhetilanne kirjataan johonkin ennen suorituksen lopettamista.

  • Jatkaminen (engl. continuation) jättää virheen huomiotta. Tämä on varsin harvinainen tilanne, koska virhe luonteensa mukaisesti on ohjelmassa jotain, joka vaatii käsittelyä.

  • Peruuttaminen (engl. rollback) palauttaa ohjelmiston tilan siihen, missä se oli ennen virheen aiheuttaneen operaation suorittamista. Peruuttamisen toteuttaminen on haastavaa tai jopa mahdotonta.

  • Toipuminen (engl. recovery) on jonkin ohjelman paikallisen osan toteutus hallitusta lopettamisesta. Jokin ohjelman osa ei itse pysty käsittelemään virhettä, mutta pyrkii siivoamaan oman tilansa eli vapauttamaan varaamansa resurssit ja ilmoittamaan virheestä jollekin toiselle ohjelman osalle.

Kaikista virhetilanteista ei aina voi toipua tai virhettä peruuttaa, koska tilannetta ei voida yrityksistä huolimatta mitenkään korjata. Esimerkkinä tällaisesta on muistin loppuminen: jos muistia ei ole enää saatavilla, on hyvin vaikea tehdä mitään toipumisoperaatioitakaan.

Virhe

On myös hyvä määritellä, mitä virheellä ohjelmassa ylipäätään tarkoitetaan. Ohjelmassa esiintyvä virhe voi olla ulkoinen tai sisäinen. Ulkoisessa virheessä on kyse siitä, että ohjelmaa pyydetään tekemään jotain, mitä se ei osaa tai mihin se ei pysty. Sisäisessä virheessä toteutus itsessään ajautuu tilanteeseen, jossa jotain menee pieleen. Itse virhetilanteen havaitseminen on usein virheen sattuessa helpoin vaihe. Tiedon kätkentä ja toteutuksen kapselointi rajapinnan taakse aiheuttavat myös sen, ettei virheen tapahtumapaikka ole palvelun kutsujan tiedossa. Tällöin tarvitaan mahdollisuus kertoa palvelun käyttäjälle virheestä ja mahdollisuus reagoida virheeseen sopivalla tavalla. Tähän poikkeukset tarjoavat sopivan mekanismin.

Poikkeusturvallisuus

Olio-ohjelmoinnissa kapselointi piilottaa metodien käyttäjältä, miten palvelu on toteutettu. Tämä koskee myös mahdollisia poikkeustilanteita. Toteutusta voidaan myös muuttaa ilman, että muutos näkyy käyttäjälle. Polymorfismi aiheuttaa lisäksi sen, ettei käyttäjä edes tiedä, minkä luokan oliota milloinkin käyttää. Periytyminen myös siirtää palveluiden toteutus on hajallaan usealla eri luokkahierarkian tasolla. Rajapinnan dokumentoinnin tulee siis kuvata myös käyttäjälle näkyvät virhetilanteet ja poikkeukset, joihin käyttäjä saattaa joutua reagoimaan.

Periytymisessä aliluokat eivät saa rikkoa kantaluokan lupauksia eli ne eivät saa aiheuttaa sellaisia virhetilanteita ja poikkeuksia, joita ei ole dokumentoitu kantaluokalle. Kantaluokka ei siten saa luvata liikoja virheistä tai niiden puuttumisesta. Kantaluokkahan ei voi tietää, millaisia aliluokkia siitä periytetään. Virhetilanteiden dokumentointi on helpompaa, jos voidaan käyttää yleisiä termejä eri tilanteille.

Sopimussuunnittelussa virhetilanteet ovat osa ehtoja. Niiden dokumentoimiseen voidaan käyttää ns. poikkeustakuita, jotka poikkeuksen lisäksi kuvaavat, millaiseen tilaan luokan olio poikkeuksen sattuessa jää.

Poikkeustakuut

Poikkeustakuut (engl. exception quarantees) ovat alunperin C++sta lähtöisin, mutta ovat yleiskäyttöisiä muissakin ohjelmointikielissä. Niiden ideana on jaotella luokan virhetilanteisiin reagoiminen yksinkertaisiin perustapauksiin. Takuut kuvaavat sitä, mitä käyttäjä voi odottaa oliosta siinä tilanteessa, että rajapinnan palvelu heittää poikkeuksen rajapinnasta ulos.

  • Minimitakuu (engl. minimal guarantee) takaa, ettei poikkeuksen sattuessa olio hukkaa resursseja ja että olio on ohjelmassa tuhottavissa tai resetoitavissa, mutta ei muuten käyttökelpoinen. Luokkainvariantti ei välttämättä ole voimassa poikkeuksen jälkeen.

  • Perustakuu (engl. basic guarantee) takaa, että poikkeuksen lentäessä ulos rajapinnan palvelusta olion tila jää ei-ennakoitavaksi, mutta sinänsä järjelliseksi. Luokkainvariantti on edelleen voimassa ja olio on sinänsä edelleen käyttökelpoinen, vaikkakin sen tila ei ole ennustettavissa.

  • Vahva takuu (engl. strong guarantee) takaa, että oliolle suoritettava operaatio joko tulee suoritetuksi kokonaan ilman virheitä tai poikkeuksen sattuessa olion tila pysyy sellaisena kuin se oli ennen operaation suorittamista. Vahvan takuu sanotaankin olevan ns. kaikki tai ei mitään (engl. commit or rollback) operaatio. Vaikka vahva takuu on käyttäjälle mukava, se on usein hankala toteuttaa.

  • Nothrow-takuu (engl. nothrow guarantee) on ns. ohjelmoijan taivaaksi kutsuttu palvelu, joka takaa, ettei poikkeusta koskaan vuoda ulos rajapinnasta. Operaatio onnistuu aina ja jos toteutuksessa tapahtuu poikkeus, metodi käsittelee sen itsenäisesti.

Lisäksi näistä erillään on käsite poikkeusneutraalius (engl. exception neutrality), joka tarkoittaa sitä, että ohjelmakomponentti vuotaa sisällään olevien komponenttien poikkeukset ulos sellaisenaan muuttumattomina. Poikkeus voidaan tällöinkin toki ottaa väliaikaisesti kiinni, se vaan heitetään eteenpäin uudelleen. Esimerkiksi ArrayListin forEach() on tällainen palvelu, joka välittää suoritettavan operaation heittämät poikkeukset kutsujalle.

Javassa finally-lohko auttaa perustakuun ja vahvan takuun toteuttamisessa, koska sen avulla voidaan paikallisesti tehdä tarvittavia siivousoperaatioita.

Poikkeusturvallisuus (kesto 23:27)

Virhetilanteisiin varautuminen ohjelmoinnissa

Poikkeusturvallisessa ohjelmoinnissa

Poikkeustakuut