lauantai 21. maaliskuuta 2009

C:tä C++ tyyliin.








Hi deee Ho!

Jaa-a. Tälläkertaa kirjoitan aiheesta, josta en ole koskaan aiemmin kirjoittanut. Enkä lukenut. Aiheesta, josta minulla on varsin rajalliset kokemukset. Aiheesta jota on hyvin vaikeaa rajata, aiheesta johon en voi sanoa "Näin se on. Tämä on oikein. Tämä taas väärin". Myönnetään, kysymyksenne "Kannattaako sellaisesta aiheesta kirjoittaa?" on aiheellinen. Mutta vastausta kysymykseenne etta saa muuten, kuin lukemalla tämän kirjoituksen - minä en voi sitä kertoa, sillä en itsekään sitä tiedä.

Viime aikoina olen useammassa kuin yhdessä C:llä koodaamassani komponentissa käyttänyt hieman tavallisuudesta poikkeavaa lähestymistapaa. Oikeastaan tämä tapa saattaa olla tutumpi C++ kuin C koodareille. Ja tämä tapa (en tiedä onko se yleisesti käytetty/tunnettu, enkä tiedä onko sillä nimeä) sanotaan sitä vaikka otus C:eeksi, minun on nyt tarkoitus esitellä.

.
.
.

Miettivä hiljaisuus. En tiedä miten etenisin. Taidan heittää tähän väliin esimerkin.


Viimeisin suuri koodaamani kokonaisuus on testisofta useille eri ajastimille. Enkä nyt tarkoita mitään rannekelloja, vaan ohjelmistoalustan applikaatiokoodarin käyttöön tarjoamia ajastimia. Mainitaan vielä sen verran, että systeemi tukee useamman eri käyttöjärjestelmän päälle rakennettuja, erilaisella rajapinnalla varustettuja ajastimia.

Kas joitain aikoja sitten olin tilanteessa, jossa katsoin että useiden erilaisten ajastimien testaus olisi hyväksi. Tuijotin jossain määrin toivottomana noin viittätoista erilaista ajastimen luontifunktiota, joille kaikille pystyi vielä antamaan varsin erilaisia toimintamoodeja parametreilla. Lisäksi ajastimet käyttivät kahta täysin erilaisia ajanlaskusysteemiä, toisessa tiksuteltiin numeroa aina vain isommaksi, tietyn ajan välein, toinen taas muistutti seinäkelloa, (joka siis koostuu sekunneista, minuuteista ja tunneista, jotka kaikki pyörähtävät ympäri kasvattaen suurempaa yksikköä, kunnes vuorokauden vaihtuessa kello pyörähtää ympäri alkutilaansa) tosin kolmen erilaisen aikayksikön sijasta tässä oli viisi erilaista yksikköä, ja "kellon ympäri" mentiin hieman alle minuutissa.

Mietin miten ihmeessä saisin kaikki ajastimet testattua suunnilleen samalla tavalla.

Mietittyäni tovin huomasin kaikkien ajastimien testauksen lopulta vaativan tietyt samankaltaiset askeleet.

1. ajastimen luonti.
2. ajastimen "expiroitumisen" oikeanaikaisuuden tarkistaminen.
3. mahdollisen jaksoittaisen ajastimen peruuttaminen.

Hieman tarkennettuna lista näytti tältä:

1. ajastimen parametrien generointi
2. ajastimen parametrien oikeellisuuden tarkistus. (ajastimen toiminta virheellisiä parametreja käytettäessä on toki myös tarkistettava, mutta testisysteemin tulee osata odottaa oikeaa käytöstä), ja ajastimen jaksollisuuden tarkistus.
3. Ajastimen luonti
4. oletetun laukeamisajan laskeminen
5 laukeamisen odottaminen ja laukeamisajan tarkistus
6 uuden laukeamisajan laskenta jaksolliselle ajastimelle
7.jaksollisen ajastimen (ja laukeamatta jääneen) ajastimen peruuttaminen.
8. virhetilanteen raportointi, ja paluuarvon generointi.

Mekaaninen älämietivaantee C tyylin lähestymistapa olisi ollut kirjoittaa tällainen runko, kopioida se 15 eri tiedostoon, ja lisätä joka runkoon eri ajastintyypille sopivat funktiot + kirjoittaa mahdollisesti vielä sekakäyttöstressitesti.

Totesin, että ei millään, noin tappavan tylsään urakkaan en lähde!

Mieleeni tuli C++:n virtuaaliluokat, joiden avulla C++ koodari saisi kaiken menemään yhteen runkoon. Ja voila...

Aloitin ensin viidestä samankaltaisesta ajastimesta, ja kirjoitin C structin, jota käytin C++:n luokan tyylisesti, eli sijoitin struktiin sekä toiminnallisuuden, että datan. Toisin sanoen, aloitin toteuttamalla yllälistatut funktiot, ja sijoitin struktiin osoittimet ylläminitun tyyppisiin funktioihin. Parametrina kaikille funktioille taas annettiin osoitin tähän struktiin. Lisäksi sijoitin struktiin funktiopointterit struktin "init ja uninit" funktioihin. Kaikki funktiosta toiseen välitettävät datamuuttujat sitten sijoitin myös kyseiseen struktiin.

Ehh.. Nyt kun luen tuon tekstin, en ymmärrä itsekkään mitä oikein tarkoitan :D Eli kirjoitan hyyyvin yksinkertaisen esimerkin:

typedef void (*lampunSytytin)(lamppuesimerkki *_this);
typedef void (*lampunSammutin)(lamppuesimerkki *_this);
typedef void (*lampunAlustin)(lamppuesimerkki *_this);
typedef void (*lampunPoistin)(lamppuesimerkki *_this);

typedef struct lamppuesimerkki
{
Slamppu *lamppuStrukti;
lampunSytytin sytyta;
lampunSammutin sammuta;
lampunAlustin alusta;
lampunPoistin poista;
}lamppuesimerkki;

jossa siis sytyta, sammuta, alusta ja poista ovat funktio-osoittimia.

Esim:

void sytyta(lamppuesimerkki* _this)
{
_this->paalla=1;
}
void sammuta(lamppuesimerkki* _this)
{
_this->paalla=0;
}
void alusta(lamppuesimerkki *_this)
{
_this->lamppuStrukti=getLamppu();
_this->sytyta=&sytyta;
_this->sammuta=&sammuta;
_this->alusta=&alusta;
_this->poista=&poista;
]
void poista(lamppuesimerkki *_this)
{
releaseLamppu(_this->lamppuStrukti);
}

Ja samanlaiset funktiot energiansäästölampulle.

Sitten tein rungon, jossa ensin kutsutaan alustusfunktiota, ja sitten kutakin testifunktiota, ja lopuksi palautetaan tulos. Tai itse asiassa, erotin jo tässä vaiheessa parametrien generoinnin omaksi pieneksi for-looppi-häkkyräksi, joka ei ollut osa struktia. Tästä for looppi häkkyrästä sitten välitettiin ajastimen tyyppi ja parametrit "testienginelle", joka osasi sitten kutsua oikeaa alustusfunktiota, ja antaa parametrit sille. (sijoitin parametrit Em struktiin data-alueelle, ja parametrien generoinnin jälkeen, kun "testi-instanssi" tehtiin (strukti luotiin), siihen sijoitettiin parametristrukti jo ennen init funktion kutsumista. Huomautettavahan se on tässävaiheessa, että kaikille ajastimille minun ei suinkaan tarvinnut toteuttaa kaikkia funktioita erikseen. Osa funktioista sopi useammalle kuin yhdelle ajastimelle.

Lisäsin tässä vaiheessa testirunkoon vielä uuden säikeen generoinnin / instanssi, jolloin for-loopista voitiin laukaista useita yhdenaikaisia testi-instansseja pyörimään.

Seuraavaksi huomasin, että lisäämällä struktin data-alueelle muutaman uuden muuttujan, sain parilla uudella funktiolla tehtyä toimivan testin vielä viidelle muulle ajastimelle.

Ja sitten vielä muutama uusi funktio, ja loputkin ajastimet voitiin testata käyttämällä samaa testirunkoa, hieman erilaisilla parametrien generointi loopeilla. Samoin pelkkää parametrigeneraattoria muuttamalla pystyi testaamaan erilaisten ajastimien yhteiskäyttöä.


Kun testit olivat valmiit, huomasin tässä struktitoteutuksessa vielä erään edun. Ajastinstrukti sisälsi oikeastaan kaiken (missä tahansa testin vaiheessa) tarvittavan datan, ja osoitin struktiin meni jokaisen funktion argumentiksi. Nyt siis pysäyhdyttäessä debuggerilla mihin tahansa testin kohtaan, on oikeastaan lähes kaikki info myös testin aikaisemmasta suorituksesta tallessa ja näkyvissä. Lisäksi erilaisten debuggi-infojen keräily on helppoa, koska kiinnostavissa kohdissa testin tarvitsee vain välittää kopio structista infoja keräilevälle ja tallettavalle prosessille. Niinpä testin kylkeen tuli koodailtua "event" systeemi, eli kiinnostavissa kohdissa testi dumppaa struktin kopion rengaspuskuriin, jota matalampi prioriteettinen säie purkaa. Purkavalle säikeelle on mahdollista rekisteröidä callback-fuinktioita kiinnostavista kohdista tulevan datan käsittelyyn, ja näin voidaan testistä saada ulos mm. erilaisia statistiikkoja, testin suorituksen juurikaan kärsimättä.

Ehkä suuriimat hyödyt tämänkaltaisesta suunnittelusta saadaan silloin, kun toteutettavana on lukuisia toisistaan poikkeavia kikkuloita, jotka kuitenkin käyttäytyvät saman formaatin mukaan. Ja toisaalta, tällaisella toteutuksella/suunnittelulla saadaan systeemeistä sellaisia, että niihin voidaan helposti muuttaa / lisätä toteutusta. Vielä jos varsinaisessa "rungossa" käytetään sopivasti void osoitinta, sen sijaan että käytetään osoitinta structiin , ja castataan se oikeaksi tyypiksi jonkin ulkoisen syötteen perusteella, voidaan systeemiä muuntaa melkein loputtomiin - joskin sen ymmärtäminen ja sitä mukaa ylläpitäminen saattaa muuttua varsin hankalaksi... (Ts. tämä mahdollistaisi useiden erityyppisten structien käytön saman "enginen" sisällä).

Ei kommentteja: