Funktionaalisen ohjelmoinnin väärinkäyttö

Funktionaalinen ohjelmointi itsessään on ihan hieno asia. Olen kuitenkin sitä mieltä, että tiettyjä asioita ei kannata toteuttaa sillä. Ainakin yksi hyvä esimerkki on laskukaavojen laskenta, kuten tarkistussummien varmistus. Törmäsin töissä niin hienoon siivuun koodia, että minun on nyt pakko jauhaa se läpi perinpohjaisesti. En todellakaan tee tätä aina, koska muuten kirjoittaisin blogipostauksia pahimmillaan muutaman tunnin välein ja kaikki aika menisi siihen.

Ongelma ja kritiikki

Alkuperäinen funktionaaliseen paradigmaan ihastuneen konsultin tuotos oli tämännäköinen:

var tarkistusSumma = _.last(merkkijono);
var merkkijononMerkitYksittäinTaulukossa = ['0'].concat(_.take(merkkijono, merkkijono.length - 1));
var kertoimet = [1, 2, 1, 2, 1, 2, 1, 2, 1, 2];
var välisumma = kertoimet.reduce(function(muisti, kertoin, kertoimenIndeksi) {
    return muisti + merkkijononMerkitYksittäinTaulukossa[kertoimenIndeksi] * kertoin;
}, 0);

Muuttujat eivät sitten olleet alunperin suomeksi, pistin ne suomeksi tähän vain pienen tason sensurointitoimenpiteenä.

Ensimmäinen pielessä oleva asia on luettavuus. Myös alkuperäiset muuttujanimet olivat pitkiä. Tämä on sinänsä ihan ookoo asia ja olen pidentänyt omien muuttujieni nimiä, koska siinä nyt vaan on järkeä kuvata asioita kuvaavasti. Tosin taulukkomuuttujan nimi ei ollut läheskään yhtä pitkä kuin ylle laittamani versio, koska halusin korostaa millaista typeryyttä tuossa on tehty.

Toinen asia, joka nostattaa kulmakarvoja, on apukirjaston käyttö. Tässä tapauksessa kyseessä on lodash. Alussa otetaan talteen tarkistusSumma, eli toisin sanoen viimeinen numero. Tämä homma hoidetaan apukirjastolla. No, onhan tuo ihan luettavaa sinänsä. Kovin tehokasta se ei ole.

var merkkijononMerkitYksittäinTaulukossa = ['0'].concat(_.take(merkkijono, merkkijono.length - 1));

Seuraava rivi aiheuttaa kuitenkin jo ihmettelyä: se luo taulukon, jonka ensimmäinen arvo on nolla merkkijonossa ja siihen sitten liitetään edeltävät numerot. Tiedättekö miksi tämä nolla lisätään? En minäkään tiedä varmasti, mutta epäilen sen johtuvan siitä, että seuraavalle riville saadaan nätisti vuorotteleva ykkösten ja kakkosten sarja. Hetken aikaa mietin, että sillä saatetaan kiertää virhetilanne, joka syntyisi jos taulukolla ei ole pituutta, mutta sellaista virhettä ei pysty syntymään.

Tämä touhu voidaan kuitenkin tehdä ihan ilman apukirjastoa. Esimerkiksi vaikka näin:

var merkkijononMerkitYksittäinTaulukossa = [].slice.call(merkkijono, 0, merkkijono.length - 1);

Me ei oikeasti tarvita tuota nollamerkkiä ensimmäiseksi, mikä selviää lukemalla koodia edemmäs.

var välisumma = kertoimet.reduce(function(muisti, kertoin, kertoimenIndeksi) {
    return muisti + merkkijononMerkitYksittäinTaulukossa[kertoimenIndeksi] * kertoin;
}, 0);

Ynnääminen tässä perustuu kertolaskusta saatuun tulokseen. Eli nollasta lopputuloksena on aina nolla. Eli ylimääräinen nolla vain tekee koodista hämäävämpää tulkita ja tuhlaa prosessoria ei-minkään tekemiseen. Jätän lukijan arvioitavaksi, miten typerä kertoimet-taulukko on.

Korjaus

Ratkaisin luettavuusongelman ja kyseenalaisen suorituskyvyn (vaikkei se ollut varsinainen ongelma) käyttämällä perinteistä imperatiivista tyyliä.

var indeksi,
    välisumma = 0;

for(indeksi = 0; indeksi < 9; indeksi++) {
    välisumma += ~~merkkijono[indeksi] * (indeksi & 1 || 2);
}

var tarkistusSumma = ~~merkkijono[indeksi];

Joo. Kyllä. Tuo funktionaalinen häkkyrä tekee periaatteessa tämän saman työn. Mahdollisten luettavuusongelmien välttämiseksi selvennetään algoritmin kenties epäselvimmät kohdat:

  • Tupla-not (eli ~~) on yksi nopeimpia tapoja ja lyhyimpiä syntakseja pakottaa arvo kokonaisluvuksi (oli saatu sisältö mitä hyvänsä).
  • (indeksi & 1 || 2) taas palauttaa 2 jos indeksin alin bitti ei ole aktiivinen ja muutoin 1. Tämä korvaa kertoimet-taulukon.
  • Jos asia ei ole ennestään tuttu, niin indeksi on for-silmukan jälkeen 9.

Ennen koodia on lisätty varmistukset, että merkkijono on todellakin merkkijono, ja että se on numeerinen. Vanha funktionaalinen toteutus ei tehnyt minkäänlaista tyyppitarkistusta tai edes pituustarkistusta, minkä takia funktiota kutsunut koodi teki ylimääräisiä temppuja varmistaakseen, että sinne menee kelpoa kamaa.

En väitä että ratkaisuni tässä on absoluuttisesti paras ratkaisu, mutta on se kaikin tavoin parempi kuin alkuperäinen pätkä.