Saturday 1 June 2013

Mallintamista ja PHP:tä

Tämä teksti alkoi mallinnuksen pohdintana, mutta päätyi abstraktiksi suunnitelmaksi Pröngin tulevaisuudesta. Nauttikaa kohtuudella, ja muistakaa olla olettamatta että tämän tekstin olevan faktaa. Tämä on hakemista; minulla on abstrakti aavistus tekstistäni, jonka muotoa yritän hakea paperille, ja joskus onnistun siinä hyvin, joskus huonosti. Tämä vaikuttaa, ainakin vielä Emacsista katsottuna, ensimmäiseen kategoriaan kuuluvalta.

Mallintaminen on uskomattoman vaikeaa. Harjaantumattomalle silmälle se voi näyttää helpolta, mutta ei. Turha toivo. Osa mallintamisen haasteista perustuu siihen, että kaikki ohjelmoidessa kohdattavat ongelmat voidaan jalostaa pienemmiksi ja pienemmiksi, kunnes omataan enää mallinnusongelmia. Yritätkö kirjoittaa N-ulotteista peliä? Noh, algoritmisi riippuvat siitä miten mallinnat pelihahmot, niiden liikkeet, niiden piirtämiset ja sellaiset. Yritätkö kirjoittaa nettisivua, johon tallennat blogitekstejä, kuvia, tai, noh, luonnoksia? Silkkaa mallintamista! En ole kohdannut Pröngin kanssa ongelmaa, joka ei olisi ratkennut muuttamalla mallinnuksessa käytettyä lähestymistapaa.

Tämän tekstin lukijoille suositellaan Universaaliin Designpatterniin tutustumista, ja Helloworldiä jollain Lispillä. Käytännössä kuitenkin riittää että ymmärtää Javan ja PHP:n oliomallit riittävän syvällä tasolla.

Miltä näyttää mallinnusongelma?

Tällaisen tunnistaa jokainen, joka joskus on luokkapohjaisella kielellä ohjelmoinut. Itse asiassa olen ilmeisesti lähestynyt aihetta aiemminkin. Teksti on luonnollisesti roskaa, mutta kun oikein siristelee silmiään, teksti puhuu ihan asiaa. Toteuttaakko (suorituskyvyltään ja laajennettavuudeltaan naurettavan \o/) skriptimoottorin suorittava osa iteroimalla rivien ensimmäisiä avainsanoja tuhannen rivin switch-hirviötä vasten, vai rakentaakko jokaista avainsanaa varten oma luokkansa, ja hakeakko vaikka reflektiolla avainsanan perusteella oikean luokan instanssi, jota vasten kutsutaan polymorfista execute() - metodia? Vai käyttääkkö kieltä, joka

  1. Tuntee Closure-käsitteen
  2. Omaa siistin syntaksin closureille
, ja laittaakko näitä hashmappiin keyword-string => closure - järjestykseen, jolloin "polymorfisen execute() - metodin" haku on vakio-mittainen operaatio (en kyllä tiedä mitään Javan ja C#:n reflektioiden big-O - laadusta, joten en tiedä onko tämä suorituskykyisempää kuin luokkien hakeminen niiden kautta...), ja saadaan yhä kaikki luokkien edut (toisinsanoen saadaan funktion ympärille tila) ilman kankeaa syntaksia.

Tältä, hyvät ystävät, näyttää mallinnusongelma. Kerroin kolma tapaa mallintaa skriptimoottorin runtimeä: proseduaalinen switch-helvetti (enpä tiedä ratkaisisivatko edes C:n ja Pascalin kaltaisiin juuttuneet tätä ongelmaa näin), luokkaperäinen toteutus (jota voisi odottaa mm. Java-, C#-, ja ehkä PHP-ohjelmoijiltakin?), ja funktionaalisuutta havitteleva closure-ratkaisu, jota Lisp-väki soveltaisi oikopäätä.

Aivan yksinkertaista

Onko? Otetaan esimerkki: Pröng. Pröngissä on luonnoksia, joilla on paljon erilaisia ominaisuuksia:

  • Teksti
  • Lähettäjä
  • Lähetysaika
  • Kommentit
  • Tagit
Tämä on jo monimutkainen rakenne. Oletetaan ettei kukaan tekisi omaa taulukkoaan jokaiselle näistä ominaisuuksista, ja määrittäisi yksittäistä oliota indeksiksi näihin viiteen taulukkoon, vaikka se voisikin tehdä relaatimalliin mappauksesta ehkä jopa helppoa. Ei, nykyaikainen koodari, joka on opiskellut ketteriä OO-metodologioita koko pienen ikänsä, mallintaa tämän hetkessä java-luokkana:

public class Luonnos
{
    private String Teksti;
    public String getTeksti()
    {
 return Teksti;
    }

    public void setTeksti(String value)
    {
 Teksti = value;
    }

    private String Lähettäjä;
    public String getLähettäjä()
    {
 return Lähettäjä;
    }

    public void setLähettäjä(String value)
    {
 Lähettäjä = value;
    }

    private String Lähetysaika;
    public String getLähetysaika()
    {
 return Lähetysaika;
    }

    public void setLähetysaika(String value)
    {
 Lähetysaika = value;
    }

    private List<String> Kommentit;
    public List<String> getKommentit()
    {
 return Kommentit;
    }

    private List<String> Tagit;
    public List<String> getTagit()
    {
 return Tagit;
    }
}

Jätän vielä huomiotta tietokannan. Oikeassa elämässä tämä perisi jonkin DAO-luokan, joka generoi SQL:n tallentamiseen, päivittämiseen ja hakemiseen. Tämä on perinteinen, enkapsulointia suojeleva tapa mallintaa dataolioita. C#ssä get- ja set-metodit korvattaisiin {get;set;} - loitsuilla julkisten kenttien perässä. PHPllä luokka mallinnettaisiin täysin samoin kuin Javalla. Ilmeisesti PHP 5.4n myötä PHP:n setterit ja getterit voisi korvata C#n kaltaisilla {get;set;} - kaksikoille.

Miten tämä mallinnettaisiin Clojurella? Hyvä että kysyitte!

  (defn new-luonnos [Teksti Lähettäjä Lähetysaika Kommentit Tagit]
     {:teksti Teksti :lähettäjä Lähettäjä :lähetysaika :kommentit Kommentit :tagit Tagit})

Sanokaa minua höpsöksi, mutta Clojuren tulkinta, josta näytin varsin verboosin esimerkin, on paljon mukavampi (toisinsanoen lyhyempi) kirjoitettava. Toki se rikkoo enkapsuloinnin, mutta Clojuren kanssa sillä ei ole väliä, koska mapin arvoja ei voi lisätä eikä muuttaa. Vaikka Clojuren java.util.Map - toteutuksen .put() käyttäytyisikin samoin kuin java.util.HashMap.put(), ei enkapsuloinnin puuttumisella välttämättä olisi väliä. Dokumentaatio on tärkeää.

Pröng

Otetaan tuo rakkain sivustoni lähempään tarkasteluun. Mainitsin aiemmin että PHPllä dataluokat voidaan mallintaa Javan tavoin, mutta oikeasti, miksi ihmeessä kukaan haluaisi tehdä näin? Yritä tehdä SQL:ää generoiva kerros javamaisille luokille, joissa luokkaskeemoissa on listattu jokainen property, ja niille erikseen setterit ja getterit. Joku reflektio-apina löytyy myös PHP:stä, mutta Java jätti ainakin minulle niin kovat traumat, etten edes tutkinut tätä mahdollisuutta. Sen sijaan, PHP tarjoaa joukon taikametodeja, joista tämän keskustelun kontekstissa kiintoisat ovat __get ja __set.

Mutta eivätkö nämä metodit hajota tyyppiturvallisuuden? Totta kai! Ne myös tarjoavat todella yksinkertaisen do-it-yourself - reflektion, jos __set()in asettaa asettamaan arvonsa mappiin, ja __getin asettaisi palauttamaan arvonsa tästä mapista. Tästä voi sitten generoida helposti SQL:t kaikkiin tarpeisiin... Jos niin haluat. Persistenssin datan mallinnus nimittäin monimutkaistaa yleisiä mallinnusongelmia todella pahasti. Yleensä persistenssillä datalla tarkoitetaan relaatiokantaan tallennettavaa dataa, mutta on olemassa myös ratkaisuja, jotka ottavat sisäänsä JSONää, XMLää ja jotain täysin omia ratkaisuja. Koska esimerkkinämme on Pröng, jonka persistenssimetodit ovat MySQL ja tiedostojärjestelmä, oletettakoon tämän teksin ajan ettei NoSQLää ole olemassa, ja etten ole siirtymässä viuhkasta pois lähiaikoina.

Tärkeä fakta, jonka jokainen harrastelija ja ammattilainen oppii, yleensä vaikeimman kautta: O/R-mappaus on vaikeaa. Alkuun kokeileva koodari kirjoittaa haku- ja syöttöfunktiot käsin (hauskaa! ensimmäisen viuhka-pröngin käsinkirjoitettu datakerros vei suhteelliselta rivimäärältään eniten tilaa koodipohjasta, ja oli täynnä SQL-reikiä, koska kukaan ei ollut kertonut hra koodarille PDOsta...), myöhemmin hän kuulee ORM-kirjastoista (Hibernate, EF, "ne mitä PHP:lle onkaan kirjoitettu"), ja kokeiltuaan niitä, hän toteaa näiden olevan vaikeita käyttää: kirjoitettakoon oma! Ei liene vaikea päätellä tuliko tästä omasta ORMista helppokäyttöinen, tai edes ongelmaton.

Miksi olioiden ja relaatiomallin välinen mappaus on niin vaikeaa? Ei se olekaan, jos luokka sisältää vain primitiivityyppisiä propertyjä, mutta jo listan mallinnus vaatii relaatioympäristössä säätämistä. Listalle pitää luoda oma taulunsa, johon asetetaan vierasavaimena tieto siitä mikä alkuperäisen taulun rivi nämä rivit omistaakaan. Muinaiset hakkerijumalat sinua auttakoon, jos haluat tallentaa oliopropertyjä kantaan.

Sivuhuomiona haluan sanoa, että uskoisin näiden ongelmien olevan paljon helpompia ratkottavia Clojurella, koska SQL on siellä ensiluokkainen kansalainen, joten SQL-stringien ja geneeristen prepared statementtien täyttäjien sijaan tietokantakoodin voi generoida ihan lispinä, niinkuin kaiken muunkin. En kuitenkaan ole vielä tehnyt tietokantakoodia, saati nettisivuja, Clojurella, joten jätän nämä uskonasioiksi vielä.

Mikä ratkaisuksi persistenssiin?

En minä tiedä! Helpointa olisi sijoittaa muutama lantti ympäristöön, joka oikeasti tukee Clojurea, ja johon voi asentaa minkä tietokannan haluaa, mutta sellaiset ympäristöt eivät ole halpoja, enkä tiedä onko tuotoiltaan olematonta sivustoa järkeä siirtää sellaiseen ympäristöön. Jos viuhkan tietokanta olisi SQL Server, voisi luonnoksia ja muita olioita tallentaa XML:nä sinne. MySQL ei ymmärtääkseni anna upottaa XPathia/XQueryä kyselyihin, toisinkuin SQL Server, ja tämä helpottaisi kyselyiden tekoa relaatiokantaan upotettuja XML-sarakkeita vasten.

Vai helpottaisiko?

Emmekö juuri todenneet O/R - mappauksen olevan vaikeaa? Miksi siis O <-> R <-> X - mappaus olisi yhtään helpompaa!?

Voisin kuitenkin väittää O/X-mappauksen olevan suhteellisen helppoa, niin kauan kuin XML:n ulkoasulle ei ole keinotekoisia rajoitteita. Yllättäen en ole tehnyt tätä PHPlla laisinkaan (okei, Pröngin XML-toteutukset ovat teoriassa olleet OX-mappausta, koska ne ovat olleet stringien katenointiin perustuvaa purkkaa, niitä ei lasketa), mutta Javalla olioista saa aika helposti XML:ää, ja C#llä vaaditaan 10 riviä jotta jokaiselle oliolle saadaan täysin geneerinen XMLSerialize() ja Stringeille XMLDeserialize<T>(). Jos Pröngiin tekisi tiedostopohjaisen "säilön" luonnoksille, ja tarpeen tullen laajentaisi tätä keskustelun pohjalle? Tai jos ensin saisi edes tagien mukaisen ryhmittelyn toimimaan kunnolla...

Mallinnuksesta kun tuli puhe, haluatteko kuulla miten mbnetin, jossa ei tosiaan ole tietokantaa saatavilla, aikaisessa Pröngissä luonnokset tallennettiin? XML:nä tiedostoon? Ha! Luonnokset löytyivät tiedostosta ./Luonnokset/$lid.php, jonne ne tallennettiin formaattiin:

    <?php
      $luonnos = "Luonnos";
      $lähettäjä = "Feuer";
      $lähetysaika = "01-06-2013";
    ?>
    
Näitä tiedostoja sitten include()tettiin luupin sisällä, ja työstettiin selaimelle, tai johonkin.

Joten mitä Pröngille tapahtuu

Pröngin persistenssiongelmat pitäisi vähitellen ratkaista. Marraskuusta käytössä ollut DAO oli hauska rakentaa, mutta suorituskyvyltään se oli niin urpo, että siihen piti rakentaa purkasta kakku ympärille, jottei tarvitsisi käydä tietokannassa kuin kerran per sivulataus. Pystyttänen macille jonkinlaisen kehitysympäristön, jossa testata tämän teksin konsepteja, ja päivittynyt koodi valuu viuhkaan kun ehtii.

No comments:

Post a Comment