Tieto vs kontrolli (struct- ja enum-tyypit)

Tässä kohdassa on tarkoitus puhua tieto-ohjatusta ohjelmoinnista. Sitä ennen esittelemme kuitenkin kaksi C++:n tietotyyppiä, joita asian selvittämiseksi tarvitaan.

Tietuetyyppi

Monissa ohjelmointikielissä ohjelmoijan on mahdollista määritellä omia tietotyyppejään, joiden avulla useita keskenään erityyppisiä tietoalkioita voidaan koota yhteen. Tavallaan kyseessä on eräänlainen alkeellisempi versio luokkatyypeistä: Mitään funktiorajapintaa ei muodostu, vaan osatietoja (kenttiä) käsitellään suoraan. Tällaista tietotyyppiä kutsutaan usein tietueeksi (record) tai C++:ssa struct:ksi.

C++:ssa tietue määritellään varatun sanan struct avulla:

struct Tuote {
    string tuotenimi;
    double hinta;
};

Edellisen määrittelyn jälkeen uutta tyyppiä Tuote voidaan käyttää hyvin pitkälle samoin kuin muitakin tietotyyppejä:

Tuote tavara = {"saippua", 1.23};

Tuote-tyyppisen muuttujan yksittäisiin osatietoihin päästään käsiksi .-operaattorilla:

tavara.hinta = 0.9 * tavara.hinta;  // Alennus 10 %
cout << tavara.tuotenimi << ": " << tavara.hinta << endl;
tavara = { "appelsiini", 0.45 };

Tietuetta voi luonnollisesti käyttää myös STL-säiliöön talletettavana arvona, funktion parametrina (arvo- tai viiteparametrina) ja paluuarvona.

Tietue voi kuvata esimerkiksi yksittäisen henkilön:

struct Henkilo {
    string etunimi;
    string sukunimi;
    string osoite;
    string sahkoposti;
};

Yllä olevan tietueen kaikki kentät ovat samaa tyyppiä, joten yllä olevat tiedot olisi voitu kirjoittaa 4-alkioiseen vektoriin. Tietue on kuitenkin tässä parempi (ja se oikea) ratkaisu, koska nyt voidaan käyttää nimettyjä kenttiä indeksien sijasta. Vektoriin ei pidä tallettaa merkitykseltään erilaisia tietoja, vaikka niiden tyyppi olisi sama.

Luettelotyyppi

Joskus tulee tarve muuttujalle, jolla voi olla vain tiettyjä arvoja, esim. kirjaston ohjelmistossa kirjan tila voi olla hyllyssä, lainassa, varattu tai vaikka hukassa. Olisi tietenkin mahdollista käyttää tähän tarkoitukseen string- tai vaikka int-tyyppejä, mutta ne sallisivat paljon muitakin vaihtoehtoja. Siksi ne eivät ole parhaita mahdollisia kirjan tilan tallentamiseen.

Luettelotyyppi on tietotyyppi, jonka kaikki mahdolliset arvot (alkiot) ovat ohjelmoijan määrittelemiä.

C++:ssa luettelotyyppi määritellään varatulla sanalla enum:

enum Tyypin_nimi { alkio_1, ... , alkio_n };

Yllä määriteltiin uusi tyyppi Tyypin_nimi, johon kuuluvat alkiot alkio_1, …, ja alkio_n. Konkreettisemmin:

enum Kirjantila { HYLLYSSA, LAINASSA, VARATTU, HUKASSA };

Luettelotyypin alkiot ovat kokonaislukuvakioita, joiden arvot C++-kääntäjä määrittelee automaattisesti: alkio_1 = 0, alkio_2 = 1, jne. Halutessaan ohjelmoija voi määrätä alkioiden arvot:

enum Tyypin_nimi { alkio_1 = arvo_1, ... , alkio_n = arvo_n };

Joskus on kätevää, että luettelotyyppi osaa kertoa oman kokonsa eli arvojensa lukumäärän:

enum Kuukausi { TAMMI, HELMI, MAALIS, HUHTI, TOUKO, KESA, HEINA,
                ELO, SYYS, LOKA, MARRAS, JOULU, KUUKAUSIA_VUODESSA
};

Tieto-ohjattu ohjelmointi

Tietokoneohjelmat koostuvat tiedosta (data) ja niihin kohdistuvista käskyistä. Todellisuudessa tämä kahtiajako ei ole selvärajainen, vaan joissakin tapauksissa tieto voi korvata osan käskyistä tai päinvastoin. Tätä käytetään hyväksi nk. tieto-ohjatussa (data-driven / data-directed) suunnittelussa ja ohjelmoinnissa.

Tieto-ohjatun ohjelmoinnin idea on suunnitella ja toteuttaa ohjelmaan tietorakenteita, joiden avulla tietoa käsittelevien käskyjen määrä saadaan pienemmäksi, eli korvataan käskyjä tiedolla. Tämä tulee paremmin esiin esimerkissä, jossa käytetään edellä esiteltyjä struct- ja enum-tyyppejä. (Esimerkin koodi löytyy kokonaisuudessan Gitistä: examples/04/datadrivenprogramming).

Ajatellaan aluksi koodia, jossa ei ole hyödynnetty tieto-ohjatun ohjelmoinnin filosofiaa:

#include <string>

enum PostalAbbreviation {AL, AK, AZ, AR, CA, CO, ERROR_CODE};  // Excluded the rest 44 elements

// Version 1: First idea
PostalAbbreviation name_to_abbreviation(const std::string& name)
{
    if(name == "Alabama"){
        return AL;
    } else if (name == "Alaska"){
        return AK;
    } else if (name == "Arizona"){
        return AZ;
    } else if (name == "Arkansas"){
        return AR;
    } else if (name == "California"){
        return CA;
    } else if (name == "Colorado"){
        return CO;
    } else {   // Excluded 44 "else if" blocks
        return ERROR_CODE;
    }
}

Yllä oleva if-lause olisi todellisuudessa paljon pidempi, siis jos myös loppuja 44 osavaltiota käsittelevä koodi olisi kirjoitettu näkyviin. Jos sama koodi kirjoitetaan soveltamalla tieto-ohjatun ohjelmoinnin periaatteita, päästään pitkästä if-lauseesta eroon seuraavasti:

#include <string>
#include <vector>

enum PostalAbbreviation {AL, AK, AZ, AR, CA, CO, ERROR_CODE};  // Excluded the rest 44 elements

struct StateInfo {
    std::string name;
    PostalAbbreviation abbreviation;
};

const std::vector<StateInfo> STATES = {
    { "Alabama", AL },
    { "Alaska", AK },
    { "Arizona", AZ },
    { "Arkansas", AR },
    { "California", CA },
    { "Colorado", CO }  // Excluded 44 lines
};

// Version 2: Better solution
PostalAbbreviation name_to_abbreviation(const std::string& name)
{
    for(auto s : STATES) {
        if(name == s.name){
            return s.abbreviation;
        }
    }
    return ERROR_CODE;
}

Näiden kahden esimerkin ero on selvä: Kun nähtiin hiukan vaivaa sopivan tietorakenteen suunnitelussa ja alustamisessa, niin varsinainen name_to_abbreviation-funktio supistui minimaaliseksi. Tieto-ohjatussa ohjelmoinnissa toteutustekniikkana on aina (tavalla tai toisella) etukäteen prosessoitu informaatio, joka muodostuu lähtöarvoista ja niistä seuraavasta lopputuloksesta.

Tieto-ohjatun ohjelmoinnin hyötyjä ovat:

  1. Syntyvät ohjelmat ovat yleensä lyhyempiä, mutta niiden selkeys ei silti kärsi.
  2. Virheiden mahdollisuus pienenee.
  3. Ohjelmien laajennettavuus ja ylläpidettävyys on helpompaa.

Varma merkki tieto-ohjatun uudelleensuunnittelun tarpeesta ohjelmassa on liian pitkäksi kasvava if-rakenne (tai switch-rakenne, jota ei ole tähän mennessä käsitelty).