Assert makron käyttäminen

Ohjelmoija usein olettaa aika paljon. Esimerkiksi tällaisessa silmukassa:

 for (int a=0; a != ylaRaja; a++) x += a; 

..oletetaan, että ylaRaja tulee joskus vastaan. Jos ylaRaja on int-muuttuja, jolla on arvo -5, niin silmukka ei lopu vasta kun a on pyörähtänyt ympäri. Vähän huonohermoisempi käyttäjä voi jo kyllästyä odottelemaan, eikä se varmaan olekaan ollut tarkoitus. Varsinkin jos ohjelmoijia on monta, niin vain Heimo Huima uskaltaa olettaa tällaisia asioita - muut ohjelmoijat kun tunnetusti ovat vastuuttomia poropeukaloita. Toisaalta eihän siitä mitään tulisi, jos jokaisiin kohtaan pistettäisiin varmistuksia eri muuttujien arvoista. Ohjelmasta tulisi vain valtava kasa virheentarkistuksia. Ja mitäpä käyttäjä tekee sillä tiedolla, että muuttuja xTerminalVelocity on saanut epäkelvon arvon - avaa ohjelman binäärikoodin heksaeditoriin ja korjaa virheen, vai?

Ohjelmoijalle kuitenkin tämmoinen tieto voi olla melko arvokas. Hym, hym, hankala tilanne… Tällaisen ajatuskulun lopputuloksena ovat suuret mielet kehittäneet assert-makron. Se varmistaa, että sille annettu ehto on tosi - siis esimerkiksi että muuttujalla on sallittu arvo. assert tulee mukaan ohjelmaan vain jos sana DEBUG on määritelty. Siis julkaistavaan versioon assertit eivät tule eikä valmiin ohjelman koko kasva. Koska assert on esikääntäjän makro, se voi antaa melko tarkkaa tietoa virheestä, käyttäen hyväksi esikääntäjän määrittämiä vakioita (muotoa VAKIO). Kääntäjien mukana yleensä seuraa assert.h -tiedosto, josta makron määrittely löytyy. Jos sellaista ei ole, niin voidaan assert tehdä itse.

Tiedosto assert.hpp:

#ifndef _assert_hpp
#define _assert_hpp

#ifndef DEBUG
#define assert(x) // jos DEBUG ei määritelty, assert on tyhjä

#else
#define assert(x) if (! (x)) { \
        cout << "VIRHEELLINEN TILANNE!" << endl; \
        cout << "Assert epäonnistui: " << #x << ", tiedostossa " << __FILE__ << " rivillä "\
        << __LINE__ << "." << endl; }
#endif

#endif

Näin sitä sitten käytettäisiin:

#define DEBUG
#include "assert.hpp"

int main()
{
	int ika = -5;
	assert(ika >= 0);
	
	return 0;
}

Kannattaa muuten varoa, ettei kirjoita assertia näin:

assert(status = OK); 

Sehän ei vertaile statuksen arvoa, vaan sijoittaa siihen OK:n. Siis mahdollinen virhetilanne häviää, mutta kun kun julkaisuversiossa assertit poistetaan, niin virhe tulee takaisin. O' mama, en haluaisi joutua tuollaista virhettä korjaamaan… Assertia ei pitäisi käyttää kuin ohjelmointivirheiden etsimiseen. Esimerkiksi tiedoston avaamisen onnistumista ei saa varmistaa assertilla, vaan sille pitää rakentaa järjestelmä, joka ilmoittaa käyttäjälle tilanteesta. Tiedoston puuttiminenhan ei ole virhe ohjelmassa, vaan lähinnäkin käyttäjässä. Mutta toisaalta ohjelman kehityksen alkuvaiheessa, kun virheiden käsittelyyn tarvittavia osia ei vielä ole ohjelmoitu - mutta ohjelmoija kuitenkin haluaisi varmistaa esimerkiksi tiedostojen avaamisen ja muistin varaamisen onnistumisen - voi vastaavaa makroa käyttää. Itse määrittelen makron tassert (temporary assert), jolla ohjelman alkuvaiheessa varmistan tällaiset tilanteet. Siinä vaiheessa kun tulee aika rakentaa virheidenkäsittelyn osat, on mahdolliset vaarapaikat helppo etsiä - siis kaikki tassert-makrot pitää korvata.

Lähteet