• Ei tuloksia

2.3 Määrittelyjen kirjoittaminen

2.3.2 Loppuehto

Ohjelmistosysteemin toteutusta varten tehdään ensin suunnitelma, jossa eri oh-jelmistokomponenttien roolit tulevat esille. Suunnitelma dokumentoidaan ja sen pohjalta toteuttajat aloittavat työskentelynsä. Ohjelmoijalla saattaa olla siis mel-ko hyvä kuva rutiinien loppuehdoista tai ne on ainakin johdettavissa mel-kohtuullisella vaivalla ko. dokumentista. Rutiinien nimet, jotka selvästi liittyvät loppuehtoihin, on myös voitu antaa jo aiemmin. Mikäli näin ei ole, ohjelmoijan tehtävänä on mää-rätä itse luokan julkiseen liitäntään tulevat piirteet ja niiden rajaukset. Erittäin tärkeää on jakaa toiminnotperustehtäviin niin, että kukin rutiini tekee vain yhden tarkasti rajatun toimenpiteen.6

Esimerkki 2.10 Tili-luokkaan ei ole syytä kirjoittaa rutiiniatilinkäsittely, jolla hoi-detaan sekä otot että panot. Ongelman tunnistaa siitäkin, että rutiinille pitää välittää kontrollitietoa, joka kertoo kummasta tapahtumasta on kyse. Tämän tiedon perusteella rutiinin logiikka jakautuu kahteen erilliseen haaraan. Parempi on tietysti tehdä erilliset rutiinitottojapano, jolloin kutsustakin näkee suoraan kummasta tapahtumasta on kyse.

Vältä siis tilanteita, joissa rutiinille välitetään tämäntyyppistä kontrollitietoa.

Loppuehto kuvaa miten rutiini käyttäytyy laillisten kutsujen yhteydessä. Se kertoo tulostietojen merkityksen sekä viittaustyyppisten argumenttien kautta tapahtuvat muutokset (perustyyppiä oleviin argumentteihin ei kutsujalle näkyviä muutok-sia voi tehdäkään). Jos rutiinissa voi tapahtua virhetilanteita, joiden seurauksena nostetaan esimerkiksi poikkeus, on kaikki erilaiset lopetustilanteet kerrottava lop-puehdossa. Jos rutiini haluaa muuttaa argumenttina annettua oliota eikä se ole kutsujan kannalta mielekästä, on mietittävä kannattaisiko olio kopioida ja päättää sitten annetaanko kopiointi asiakkaan vai toimittajan huoleksi. Tämäkin jo osoit-taa sen, että paitsi tapahtuvista muutoksista, usein on yhtä tärkeää kertoa myös mitä tietoja ei muuteta. Tätä varten jatkossa otetaan käyttöön seuraava sääntö:

tunnisteisiin, joita ei ole mainittu loppuehdossa, ei ole tehty muutoksia.

Rutiinin asiakkaan on syytä lukea huolella, mitä loppuehdossa sanotaan ja miettiä pilkun tarkasti sen merkitystä, eikä tehdä mitään ylimääräisiä, loppueh-dossa mainitsemattomia johtopäätelmiä rutiinin toiminnasta esimerkiksi piirteen nimen perusteella. Erityisen hyvä testi on asettautua implementoijan asemaan ja

6Jos rutiinin nimen keksimisessä on ongelmia, se saattaa olla merkki siitä, että rutiini yrittää tehdä useita perustoimintoja (tai osia niistä).

2.3. MÄÄRITTELYJEN KIRJOITTAMINEN 31 ajatella minkälaisella toteutuksella pääsisi vähimmällä työllä, sillä niin implemen-toija vääjäämättä toimii.

Alkuehtojen tavoin myös loppuehtojen vahvuuden merkitystä on syytä tarkas-tella kriittisesti. Luonnollista kieltä käytettäessä määrittelystä tulee helposti toi-minnallinen (operational), jolloin loppuehto kertoo suoraan rutiinin toteutustavan:

/**

* @.pre Tieto alkio esiintyy taulukossa taulu ainakin kerran.

* @.post Rutiini tutkii paikkoja taulu[0], taulu[1],...

* ja palauttaa ensimmäisen indeksin i, jolle

* alkio.equals(taulu[i]).

*/

public static int paikka(Object[] taulu, Object alkio)

Kokeneen ohjelmoijan on yleensä helppo tehdä toiminnallinen määritys, mutta on-gelmana on, että tällainen määrittely on (a) yleensä melko pitkä (teepä lyhyt ja selkeä toiminnallinen kuvaus neliöjuuren laskevalle ohjelmalle!), ja (b) liian vahva sitoen toteuttajan kädet lähes täysin sekä (c) siihen voi jäädä — luonnollisesta kie-lestä johtuen — tulkinnanvaraisia ilmauksia. Toiminnallisia kuvauksia kannattaa siis yrittää välttää. Yleensäkin, jos loppuehtoa joudutaan vahvistamaan, se on teh-tävä harkiten, koska ehdon vahveneminen on tavallisesti merkki siitä, että rutiinin toteutusvaihtoehtojen määrä pienenee (tuloksena helposti ns. ylimäärittely, engl.

overspecification). Tämän takia on tärkeää tarkkailla, ettei vahventaminen ole joh-tanut siihen, että rutiinin ratkaisumenetelmä tulee tarkasti kiinnitettyä, vaan että määrittely antaa edelleen mahdollisuuden tehokkaampaan ja/tai elegantimpaan totetukseen. Loppuehdon tiukentaminen tarkoittaa myös lisätyötä toteuttajalle, koska rutiini lupaa tehdä enemmän kuin aiemmin. Jos esimerkiksi lajittelurutiinil-le annetaan lisävaatimus tehdä hommansa stabiilisti (yhtä suuret alkiot eivät saa muuttaa keskinäisiä paikkojaan alkuperäiseen tilanteeseen verrattuna), on selvää, että työmäärä lisääntyy.

Keskitie on kultainen loppuehdonkin tapauksessa, sillä myös liian heikko (eli vajaamääritelty, engl. underdetermined) loppuehto on ongelmallinen. Jos rutiinin käyttäytymiseen liittyvistä yksityiskohdista jätetään osa määrittelemättä, jotkin alkuehdon täyttävät kutsut saattavat johtaa useaan valinnaiseen (mutta oikeaan) tulostietoon. Implementointi takaa tällöin vain sen, että palautettava arvo kuuluu kyseiseen tulostietojoukkoon. Esimerkiksi hakurutiini

/**

* @.pre taulu != null && EXISTS(a : taulu; a.equals(alkio))

* @.post taulu[RESULT].equals(alkio)

*/

public static int paikka(Object[] taulu, Object alkio)

voi olla asiakkaan kannalta liian heikko, koska se ei kerro, mikä indeksiarvo palau-tetaan, jos haettava tieto esiintyy taulukossa useammin kuin kerran. Toisaalta on muistettava, että tarpeetonta loppuehdon tiukentamista on vältettävä: määrittelyn tulee olla riittävän yleinen — onhan hakurutiinissa esimerkiksi riippumattomuus taulukon koosta tärkeää — mutta vain, jos se lisää rutiinin käytettävyyttä.

Loppuehtoon kirjoitetaan vain normaaliin toimintaan liittyviä kuvauksia. Eri-koistapauksien hoidosta kirjoitetaan oma kuvauksensa, sillä tällöin on kyse jollakin tavoin poikkeuksellisesta tilanteesta:

/**

* @.pre taulu != null

* @.post taulu[RESULT].equals(alkio) &

* FORALL(j : 0 <= j < RESULT; !taulu[i].equals(alkio))

* @throws AlkioEiLöydyPoikkeus

* Nostetaan jos !EXISTS(a : taulu; a.equals(alkio)).

*/

public static int paikka(Object[] taulu, Object alkio)

throws AlkioEiLöydyException AlkioEiLöydyPoikkeus on tarkistettava poikkeus ja kutsujan on valmistauduttava käsittelemään se, jos haettua tietoa ei löydy taulukosta.

Esimerkki 2.11 Hieman erikoisempi loppuehto on TaulukkoPino-luokan luontioperaa-tiolla (oletetaan että luokka perii abstraktin luokanRajoitettuPino, ks. listaus 5.1, s. 134):

/**

* @.pre maksimikoko >= 0

* @.post annaKoko() == 0 &

* annaKapasiteetti() == maksimikoko

* @.postPrivate pino != null

*/

public TaulukkoPino(int maksimikoko) {

super(maksimikoko);

pino = new Object[maksimikoko];

}

Loppuehto on jaettu kahteen osaan: Ensimmäinen on suunnattu asiakkaalle ja kertoo ab-straktin käsitteen ”pino” ominaisuuksista. Toinen ehto on privaatti ja se on tarkoitettu luokan toteuttajalle ja ylläpitäjille. Tämä loppuehto liittyy pinon sisäiseen toteutukseen eikä näin muodoin ole mitenkään oleellinen asiakkaalle (tällaisessa väittämässä on tyypil-lisesti private-piirteitä, joten siinäkin mielessä se on asiakkaalle irrelevantti). Väittämä on kuitenkin hyvä tarkistaa, jotta pino-olion tiedettäisiin olevan luonnin jälkeen sisäisesti eheä.

Loppuehdossa on tyypillisesti siis kahdentyyppisiä väittämiä:

2.3. MÄÄRITTELYJEN KIRJOITTAMINEN 33

• Rutiinin toiminnan vaikutukset asiakkaalle. Tämä kertoo mihin tarkoituk-seen rutiini on tehty ja miten lopputulos näkyy asiakkaalle.7 Tässä yhtey-dessä on kerrottava myös mahdollisten erikoistilanteiden syntymis- ja hoito-tavoista. Haluttaessa korostaa jonkin tiedon — esimerkiksi viittaustyyppisen argumentin — mutatoitumattomuutta, mainitaan tämä osana loppuehtoa.

• Olion sisäiseen esitysmuotoon liittyvät ehdot. Loppuehto voi sisältää osia, joilla ei ole asiakkaalle mitään merkitystä, koska ne liittyvät luokan sisäi-seen toteutuksisäi-seen. Yleensä tällaiset ehdot kirjoitetaan luokkainvarianttiin (ks. kohta 3.2.2), mutta jotkin yksittäiseen rutiiniin liittyvät osat on kirjoi-tettava myös loppuehtoon. Tästä ei ole asiakkaalle haittaa, muttei sillä ole toisaalta mitään informaatioarvoakaan.

Ja vielä lyhyesti: hyvän määrittelyn tunnistaa siitä, että rutiinin merkitys on il-maistu yksinkertaisesti ja lyhyesti ja että se kuvaa tarkasti rajatun ja hyvin määri-tellyn toimenpiteen, jonka ymmärtäminen ei riipu rutiinin käyttöyhteydestä. Mer-kityksen kuvaamisessa on luonnollista käyttää apuna luokan muita julkiseen liitän-tään kuuluvia piirteitä, koska luokka määrittelee abstraktin käsitteen ja väittämät liittyvät ko. abstraktin käsitteen ominaisuuksiin. Kytkemällä julkisia piirteitä näin toisiinsa asiakas ymmärtää paremmin rutiinin nivoutumisen muuhun kokonaisuu-teen.