Tervehdys Putkalaiset.
Ajattelin kysäistä teidän mielipidettänne, rämpiessäni loputtomassa ohjelmointiperiaatteiden suossa, seuraavasta asiasta.
Suunnitellessa tietokantaa tulisiko pakollisten tietojen olla yhdessä taulussa ja vapaaehtoisten toisessa vai molempien samassa taulussa jolloin vapaaehtoinen kenttä asetettaisiin NULLiksi, kun siihen ei tule tietoa? Vai (riippuen asiasta) tulisiko kenttään asettaa jokin "järkevä" oletusarvo? Alla hypoteettinen esimerkki siitä mitä ajan takaa.
Yhdessä taulussa USER id fullname nickname ---------------------------- 1 Lorem Ipsum 2 dolor NULL
Eri tauluissa USER id fullname ---------------- 1 Lorem 2 dolor USER_NICKNAME userId nickname ---------------- 1 Ipsum
Tai jos samaan soppaan lyödään vielä vaikka "gender" ENUMina niin miten silloin tekisitte?
EDIT:
Vielä hieman lukemista aiheesta kiinnostuneille
Stack Overflow - Are nulls in a relational database okay?
How To Handle Missing Information Without Using NULL by Hugh Darwen
Vastasitkin itse jo kysymykseesi linkeilläsi: jotkut fanaatikot vastustavat NULL-arvoja täysin ehdottomasti, ja toiset taas eivät näe niissä mitään ongelmaa – onhan joku jopa ohjelmoinut sen mahdollisuuden tietokantamoottoriin, tuskinpa ihan vain trollatakseen.
Mielestäni ei ole järkeviä perusteita käyttää useaa taulua heti, kun jossain uhkaa vilahtaa NULL-arvo. On kapeakatseista ajatella, että taulussa oleva NULL (oikeastaan yleensä vain sarakkeen puuttumista ilmaiseva bitti) olisi jotenkin aivan eri asia kuin LEFT JOIN -liitoksella tuloksiin ilmestyvä NULL tai erillisen SELECT-kyselyn tuottama tyhjä tulosjoukko. Minusta näissä on hyvin vähän ajatuksellista eroa, ja tärkein tekijä valinnassa saisikin olla ratkaisun ylläpidon helppous.
Tietenkin on tilanteita, joissa monen taulun käyttö on aivan selvä ratkaisu. Kahden taulun käyttö sopii hyvin tilanteeseen, jossa ensimmäinen taulu vastaa jotain kantaluokkaa ja toinen taulu vastaa perivän luokan vaatimia lisätietoja. Sen sijaan oman taulun lisääminen jokaiselle sarakkeelle ei minusta ole kovin käytännöllistä, ei varsinkaan silloin, kun sarakkeita on paljon tai niitä on tarkoitus muokata dynaamisesti. Sellaisessa tilanteessa yksinkertaisempi ratkaisu on käyttää vain kolmea taulua: perustaulu (X (id)), avaintaulu (X_data_key (id, name)) ja arvotaulu (X_data (X_id, X_data_key_id, data)). Näistä avaintaulu ei toki ole teknisesti välttämätön mutta kuuluu asiaan, jotta avaimet ovat yksiselitteiset eikä niihin tule aivan irrallisia, mielivaltaisia tekstejä.
Kiitokset kattavasta vastauksesta.
Hieman aihetta sivuten, mutta itselläni on tämä ikuinen tuntikortti projekti. Tietokannassa on taulu "tuntikortti" jossa parikymmentä saraketta. Järkevien kokonaisuuksien erottaminen kyseisestä taulusta lienee paikallaan? Esimerkiksi jotkin työt sisältävät matkoja, mutta toiset eivät ja matkan tiedot mielestäni muodostavat hyvän kokonaisuuden omillaankin. Mutta toisaalta onko siitä mitään hyötyä, koska aina kun tuntikortin tietoja haetaan tarvitaan myös kyseisten rivien kilometrit?
time card id kilometers startAddress destinationAddress arriveAddress --------------------------------------------------------------------------------- Jos pilkottaisiin kahdeksi tauluksi => time card id ----------- travelExpenses timeCardId kilometers startAddress destinationAddress arriveAddress -----------------------------------------------------------------------------------------
Sitten aihetta hieman enemmän sivuten, kun mennään itse koodin puolelle niin miten tällaista parikymmentä sarakkeista taulua olisi järkevintä lähteä käpistelemään? Itselleni tulee lähinnä mieleen "plain old data" struktuuri johon arvot olisi helppo sijoittaa ja tarvittaessa tulostaa. Jos tekisi taas OO-tyylisesti niin muodostajaan tulisi parikymmentä paremetria niin se ei kuulosta kovin houkuttelevalta.
// PHP-example - Plain old data $timeCard = new TimeCard; $timeCard->id = $row['id']; $timeCard->date = new DateTime($row['date']); $timeCard->travelExpense = new TravelExpense; $timeCard->travelExpense->kilometers = $row['kilometers']; $timeCard->travelExpense->startAddress = $row['startAddress']; //... echo htmlspecialchars($timeCard->date); echo htmlspecialchars($timeCard->travelExpense->kilometers);
Vai kannattaako tehdä useita eri luokkia jotka muodostavat kyseisen kokonaisuuden?
$timeCard = new TimeCard($row['id'], $row['date']); $travelExpense = new TravelExpense($row['kilometers'], $row['startAddress']); //... echo htmlspecialchars($timeCard->id()); echo htmlspecialchars($travelExpence->kilometers());
Vai jollain ihan eri tavalla?
Suurin osa netistä löytyvistä vastauksista antavat kyllä todella hienoja esimerkkejä tilanteille joissa on muutamia arvoja, mutta itselleni ei ole vielä tullut vastaan esimerkkiä jossa käsiteltäisiin hieman isompia kokonaisuuksia. Näistä muutamista suurista kompastuskivistä kun pääsisi ylitse niin voisi käyttää oikeasti ajan koodaamiseen sen sijaan, että tuhlaisi yhtään enempää aikaa lukien hienoja ohjelmointiperiaatteita joiden lukemiseen lienee mennyt jo pitkälle toistasataa tuntia, ellei enempääkin.
Othnos kirjoitti:
Järkevien kokonaisuuksien erottaminen kyseisestä taulusta lienee paikallaan? Esimerkiksi jotkin työt sisältävät matkoja, mutta toiset eivät ja matkan tiedot mielestäni muodostavat hyvän kokonaisuuden omillaankin. Mutta toisaalta onko siitä mitään hyötyä, koska aina kun tuntikortin tietoja haetaan tarvitaan myös kyseisten rivien kilometrit?
No ideahan on, että samaa tietoa ei tietokannassa toisteta ja jos sulla on 1 - n tai 0..1 - n suhde, niin teet kaksi taulua (tai jos n-n suhde niin aputaulu eli yhteensä 3 taulua).
Esimerkissäsi ei olisi suoranaista höytyä pilkkoa, jos time card taulussa ei tosiaan ole mitään muuta kuin Id. Normaalisti kuitenkin time card taulussa olisi time cardin otsikkotason tiedot ja travelexpenses taulussa sitten rivitason tietoja.
Othnos kirjoitti:
Sitten aihetta hieman enemmän sivuten, kun mennään itse koodin puolelle niin miten tällaista parikymmentä sarakkeista taulua olisi järkevintä lähteä käpistelemään? Itselleni tulee lähinnä mieleen "plain old data" struktuuri johon arvot olisi helppo sijoittaa ja tarvittaessa tulostaa. Jos tekisi taas OO-tyylisesti niin muodostajaan tulisi parikymmentä paremetria niin se ei kuulosta kovin houkuttelevalta.
Mielestäni ei kannata keksiä pyörää uudestaan vaan käyttää jotai valmista ORMia.. Sitten jos nimenomaan olisit tekemässä omaa ORMia, niin asiaa voisi miettiä enemmän.
En nyt oikeastaan näe edes miten kenttien tunkeminen muodostajaan olisi "OO-tyylistä".
Grez kirjoitti:
Esimerkissäsi ei olisi suoranaista höytyä pilkkoa, jos time card taulussa ei tosiaan ole mitään muuta kuin Id. Normaalisti kuitenkin time card taulussa olisi time cardin otsikkotason tiedot ja travelexpenses taulussa sitten rivitason tietoja.
Time card taulu on sinällään harhaanjohtavasti nimetty, koska normaalistihan tuntikorttiin merkitään esim. 2 viikon työt. Tässä tapauksessa time card taulu sisältää jokaisena rivinään yksittäisen työn eli parempi nimi taululle voisi olla "Works" tai vastaava, mutta ei takerruta nyt siihen.
Jätin pois time card taulusta kentät jotka eivät olennaisesti liittyneet esimerkkiin vaan totesin tekstissä, että time card taulu sisältää nuo parikymmentä kenttää joista viisi on esimerkissä esillä.
Kyseisessä esimerkissä siis vain töille joilla on matkoja lisättäisiin oma rivi travel expenses tauluun ja se saa avaimekseen TimeCardId:n. Toisaalta Metabolix itseasiassa vastasikin jo tähänkin kysymykseen, että "puritistit" tekisivät sen jotakuinkin näin(?). Itselleni tuli nyt lähinnä se mieleen, että jos kyseiset tiedot olisivat time card taulussa niin työt joilla ei ole matkoja sisältäisivät jo 4 NULL solua.
Kuinka pitkälle tuo normalisointi pitäisi mielestäsi viedä? Esimerkiksi nuo travel expensesin address pitäisi varmaan normalisoida omaan tauluunsa vaikka käyttäjä käsin kirjoittaakin osoitteet? Toistuvia tietoja niissä jossain vaiheessa/varsin pian tulee.
Grez kirjoitti:
Mielestäni ei kannata keksiä pyörää uudestaan vaan käyttää jotai valmista ORMia.. Sitten jos nimenomaan olisit tekemässä omaa ORMia, niin asiaa voisi miettiä enemmän.
Joo, tarkoitus ei ole keksiä pyörää uudelleen vaan pikku hiljaa saada koodia aikaan. The Alchemist taisi joskus mainita Doctrinen niin taidan lähteä sitä kokeilemaan. Kiitos vinkistä.
Grez kirjoitti:
En nyt oikeastaan näe edes miten kenttien tunkeminen muodostajaan olisi "OO-tyylistä".
Mielestäni kenttien tunkeminen muodostajaan on välttämättömyys jotta ne voidaan tarkistaa oikeiksi jonka jälkeen tiedetään oliolla olevan kelvollinen tila. Ja eikös olion kelvollinen tila ole juuri tärkeä osa "OO-tyylistä" ohjelmointia? Onhan sitä tosin kaiken maailman fluent-patterneita, mutta et varmaan vastaavia tarkoittanut? Mikä sinun näkemyksesi on "OO-tyylisestä" ohjelmoinnista?
Othnos kirjoitti:
Mielestäni kenttien tunkeminen muodostajaan on välttämättömyys jotta ne voidaan tarkistaa oikeiksi jonka jälkeen tiedetään oliolla olevan kelvollinen tila.
...
Mikä sinun näkemyksesi on "OO-tyylisestä" ohjelmoinnista?
Voisin melkein semi-ilkeästi todeta, että oma näkemykseni OO-ohjelmoinnista on jotain muuta kuin PHP:tä.
Esim. Doctrinessa käytetään settereitä ja gettereitä.
Onhan toikin tietty tylsää kirjoittaa tyyliin:
$user = new User(); $user->setFirstName("Kalle"); $user->setLastName("Keinonen"); $user->setAge(25);
(Offtopic:
Jotkin kielet tarjoaa syntaktista sokeria, että täsmälleen saman asian saa kirjoitettua miellyttävämmin*, esim C#:
var user = new User() { FirstName = "Kalle", LastName = "Keinonen", Age = 25 };
Ja tuossa siis käytetään setteriä sen takia että User-luokassa FirstName jne. on määritelty ominaisuuksiksi eikä muuttujiksi.
* tämäkin tietty makuasia)
Grez kirjoitti:
Voisin melkein semi-ilkeästi todeta, että oma näkemykseni OO-ohjelmoinnista on jotain muuta kuin PHP:tä.
Näkemyksesi mahtanee olla todella pitkälle totta. Mutta setterit on siis mielestäsi hyvää OO:a?
Oma näkemykseni on varsin tuore eikä minulla ole käytännön kokemusta siitä, mutta voisin lainata Bjarje Stroustrupin "Programming" kirjaa josta se on lähtösin
Bjarne Stroustrup kirjoitti:
We try to design our types so that values are guaranteed to be valid; that is, we hide the representation, provide a constructor that creates only valid objects, and design all member functions to expect valid values and leave only valid values behind when they return.
Tältä osin siis kysyinkin, että kannattaako kyseisissä "value objecteissa" ylipäätään lähteä toteuttamaan hyvin puritista OO-tapaa vai jotain sen ja plain old data structuurin väliltä.
Othnos kirjoitti:
Mutta setterit on siis mielestäsi hyvää OO:a?
En suoraan sanoen tiedä mikä on PHP:ssä hyvää OO:ta. Setterien käytöstä en esittänyt omaa mielpidettä, vaan totesin että Doctrinessa käytetään settereitä ja gettereitä.
Othnos kirjoitti:
voisin lainata Bjarje Stroustrupin "Programming" kirjaa josta se on lähtösin
Bjarne Stroustrup kirjoitti:
We try to design our types so that values are guaranteed to be valid; that is, we hide the representation, provide a constructor that creates only valid objects, and design all member functions to expect valid values and leave only valid values behind when they return.
Mielestäni tuo lainattu kohta ei edellytä että konstruktorissa annetaan arvot kentille, eikä varsinkaan sitä, että annettaisiin arvot kaikille kentille.
Tietenkin jos mitään arvoja ei anneta konstruktorille ja tuloksena pitää olla validi objekti, niin konstruktorin pitää määrittää kenttiin jotkin validit oletusarvot.
Othnos kirjoitti:
Tältä osin siis kysyinkin, että kannattaako kyseisissä "value objecteissa" ylipäätään lähteä toteuttamaan hyvin puritista OO-tapaa vai jotain sen ja plain old data structuurin väliltä.
Se mikä kannattaa ja mikä ei riippuu ehkä jossain määrin siitäkin, millaiseen ympäristöön olet koodaamassa ja jossain määrin voisi sanoa sen olevan jopa makuasia. Jos kuitenkin käytät valmista ORMia (Doctrinea tai jotain muuta) niin ne yleensä määrittelee tietyn tavan tehdä asiat, jolloin paras tapa lienee se ainoa joka toimii.
Olen jossain nähnyt keskustelua, että "setterit/getterit on pahasta" mutta se taustalla ollut ajatus on enemmänkin ollut se, että "luokat jotka on todellisuudessa vain muuttujavarastoja on pahasta." Eli että luokan pitäisi tehdä jotain järkevää eikä paljastaa sisäisiä muuttujiaan suoraan eikä s/gettereiden kautta. Itse kuitenkin jakaisin luokkia hieman käyttötarkoituksen mukaan, ja ORMit on mielestäni sellainen, jossa tiedon suht suora käpistelymahdollisuus on toivottu juttu. Niitä tyhmiä "property bag" luokkia voi sitten käyttää vaikka niissä fiksummissa.
Jos ihan yleisellä tasolla ajattelee value objekteja, niin käyttötapa lienee aika olennainen juttu pohdittaessa mitä kannattaa tehdä. Joissakin ohjelmointikielissä voit määrittää esimerkiksi muuttujan joka on 32-bittinen kokonaisluku. Tämähän on itsessään "value object" ja ohjelmointikieli toteuttaa puolestasi sen validoinnin -> voit olla varma että aina kun luet sen arvon saat 32-bittisen kokonaisluvun. Php:ssä vastaava toiminnallisuus ilmeisesti vaatisi luokan ja arvon asettamisen konstruktorissa ja/tai setterissä ja lukemisen (oletus)getterillä.
Jos taas validius edellyttää jotain tarkempaa, esim. arvoa väliltä 25-517, niin sitten täytynee kirjoitella vähän koodia tms määrittelyä. Setterihän sitten mahdollistaa arvon muuttamisen olion luonnin jälkeen silti mahdollistaen validoinnin. Jos halutaan että olio saa lopullisen muotonsa luonnissa, niin silloin tietenkin kannattaa jättää setteri pois.
Esim. tietokantaentiteeteissä halutaan yleensä pystyä myös muuttamaan kannassa jo olevia arvoja, jolloin näen järkevämpänä laittaa setteri kuin jättää se pois. Vahvasti tyypitetyssä kielessä voisi tietysti käyttää suoraan muuttujaakin, esim. 32-bit kokonaislukumuuttuja kannan 32-bittiselle kokonaisluvulle. Kuitenkin setter/getterin käyttäminen automaattisetsi kaikille kentille mahdollistaa sen, että luokkaan voidaan myöhemmin lisätä tarkempaa validointikoodia ilman että olemassa olevat luokkaa käyttävät koodit hajoaa.
Tai ainakin näin kuvittelisin esim. tuon Doctrinen ja vaikkapa .Netin Entity Frameworkin valinnan käyttää set/get / ominaisuuksia olevan perusteltu.
Doctrinessa ei ole pakko käyttää juuri get/set-funktioita. Voi myös käyttää maagisia __get- ja __set-funktioita ja käyttää membereitä kuin ne olisivat julkisia muuttujia. Ainoa rajoitus on se, että luokan jäsenmuuttujat eivät saa olla publiceja, koska tällöin Doctrine ei pääse väliin tekemään omia taikojaan.
Riippuu oikeastaan käytetystä kehysympäristöstä, miten kannattaa itse lähteä liikkeelle. Zend Framework 2:ssa käytetään yksinomaan gettereitä ja settereitä, mutta toisaalta Symfony 2:ssa käytetään myös "jäsenmuuttujasyntaksia". On ihan luontevaa valita samanlainen ajatusmalli omaan koodiinkin kuin mitä valitussa kehyksessä käytetään.
Grez kirjoitti:
Jos kuitenkin käytät valmista ORMia (Doctrinea tai jotain muuta) niin ne yleensä määrittelee tietyn tavan tehdä asiat, jolloin paras tapa lienee se ainoa joka toimii.
Tällä lähdetään liikenteeseen. Joku jo tosiaan pähkäillyt nuo asiat valmiiksi niin turhaan niiden asioiden uudelleen keksimiseen tai kehittämiseen käyttää aikaa ainakaan tässä vaiheessa. Tuloksenakin voisin olettaa tekeväni huomattavasti huonompia ratkaisuja näillä tietotaidoilla.
Grez kirjoitti:
Kuitenkin setter/getterin käyttäminen automaattisetsi kaikille kentille mahdollistaa sen, että luokkaan voidaan myöhemmin lisätä tarkempaa validointikoodia ilman että olemassa olevat luokkaa käyttävät koodit hajoaa.
Tähän voisin ottaa siltä osin kantaa, että setterin käyttäminen nimenomaan tuohon validointikoodin lisäykseen todennäköisesti juuri hajottaa nuo olemassa olevat luokkaa käyttävät koodit. Jos kyseisiä asioita ei ole suunnitteluvaiheessa ottanut huomioon niin mielestäni niiden kanssa täytyy vain elää tai lisätä esimerkiksi uusi "setteri" jota käyttää sitten tulevaisuudessa.
Othnos kirjoitti:
Tähän voisin ottaa siltä osin kantaa, että setterin käyttäminen nimenomaan tuohon validointikoodin lisäykseen todennäköisesti juuri hajottaa nuo olemassa olevat luokkaa käyttävät koodit.
Nyt en kyllä ymmärrä että miten.
Eli oletetaan että teet ORM luokkiin aina setterit jotta voit koodaamisen edistyessä lisätä validointilogiikkaa (niin kuin viestissäni oli puhetta). Olet koodannut jonkun kyseistä ORM luokkaa käyttävän jutun. Sitten tulee mieleen että täällä ORMissahan on kenttä, johon ei voi laittaa kuin tietynlaisia arvoja ja lisäät validoinnin. Miten tämä validoinnin lisääminen hajottaa sen vanhan koodin?
Itse sanoisin että vanha koodi oli jo ennestään hajalla, jos laittoi vääriä arvoja. Validoinnin lisäämisen jäkeen se rikkinäisyys vaan huomataan ja helpompi korjata.
Grez kirjoitti:
Itse sanoisin että vanha koodi oli jo ennestään hajalla, jos laittoi vääriä arvoja. Validoinnin lisäämisen jäkeen se rikkinäisyys vaan huomataan ja helpompi korjata.
Tästä meillä on juuri se eriävä mielipide. Jos kehität esimerkiksi jonkin sortin kirjastoa ja olet päästänyt jo yhden version ulos. Sitten huomaat, että jonkin arvon validointi on puutteellinen ja lisäät kyseisen validoinnin. Nyt jos ja kun joku on käyttänyt kirjastosi aiempaa versiota ja "hyödyntänyt" kyseistä "ominaisuutta" jotenkin ja haluaa päivittää uusimpaan kirjaston versioon niin hänen koodinsa hajoaa.
Seuraavassa todella hypoteettinen esimerkki asiasta.
class Date { private $day, $month, $year; public function __construct($day, $month, $year) { $this->day = $day; $this->month = $month; $this->year = $year; } public function day() { return $this->day; } public function month() { return $this->month; } public function year() { return $this->year; } }
Seuraavaksi ajattelet lisätä kyseiseen luokkaan tarkistukset, että päivä, kuukausi ja vuosi ovat todellakin "valideja". Nyt joku on käyttänyt sinun luokkaasi siten, että jos jotain arvoa ei ole tiedossa hän on asettanut -1 arvoksi joita hän käyttää koodissaan. Nyt hänen päivittäessä uusimpaan versioon esimerkiksi päivän täytyy olla välillä 1-31 jne. ja kyseinen validointi rikkoo hänen koodinsa.
Bugit ja ominaisuudet ovat eri käsitteitä. Jos kolmas osapuoli kiertää bugia omassa koodissaan, niin hän ottaa vastuun koodinsa toimimisesta bugikorjauksen jälkeen. Ehkä, EHKÄ, jos bugi silti tuottaisi jossain määrin loogisen tuloksen ja sitä olisi selkeästi hyödynnetty laajasti, voitaisiin hyväksyä bugin olevan "ominaisuus" ja jättää se koodiin seuraavaan suurempaan revisioon asti.
Jos mulla olisi Date-luokassa mahdollisuus asettaa päiväksi vaikka -1 ja tämän jälkeen päivämääränä lukisi "-1.12.1990" eikä vaikkapa "29.11.1990", niin tuolloin kyse olisi selvästi bugista ja itse korjaisin sen heti. Ne, jotka jostain syystä ovat halunneet käyttää päiväystä "-1" vastoin speksejä ties mihin, joutuisivat vain keksimään uuden tavan bugikorjauksen jälkeen.
Mitä vikaa taulukoissa on... Jos luokkien ainut virka on toimia tietovarastona niin PDO voi kyllä palauttaa sinulle datan suoraan objekteina.
Getterit/setterit mahdollistavat joustavamman työskentelyn mahdollisten muutosten tullessa, kuten Grez jo mainitsi. Lisäksi ne tarjoavat järkevämmän julkisen rajapinna muille kehittäjille.
Luokalla on oltava yksi selkeä tehtävä ja sen sisäinen mahdollisesti muuttuva logiikka oltava hyvin enkapsuloitu, eli olisi hyvä jos rajapinta ei olisi julkinen jäsenmuuttuja.
Grez kirjoitti:
Voisin melkein semi-ilkeästi todeta, että oma näkemykseni OO-ohjelmoinnista on jotain muuta kuin PHP:tä.
PHP:llä on omat heikkoutensa, mutta kyllä kielen kun kielen suurin pullonkaula on paska ohjelmoija, hyvä tekijä kiertää puutteet. Ja jos huomaa että yrittää tehdä PHP:llä Androidiin pelin, niin on vika työkaluissa ei kielessä.
Offtopic..
OOP vaikuttaisi olevan kaikille jossain vaiheessa oppimiskäyrää vain uusi tapa kehystää omaa spagettia ilman sen kummempaa ymmärrystä mitä OOP on.
Othnos kirjoitti:
Tästä meillä on juuri se eriävä mielipide. Jos kehität esimerkiksi jonkin sortin ...
Seuraavassa todella hypoteettinen esimerkki asiasta.
...
Tolla logiikalla ei koskaan korjata virheitä koska jonkun virheellinen implementaatio saattaisi hajota.
Othnos kirjoitti:
Seuraavaksi ajattelet lisätä kyseiseen luokkaan tarkistukset, että päivä, kuukausi ja vuosi ovat todellakin "valideja". Nyt joku on käyttänyt sinun luokkaasi siten, että jos jotain arvoa ei ole tiedossa hän on asettanut -1 arvoksi joita hän käyttää koodissaan. Nyt hänen päivittäessä uusimpaan versioon esimerkiksi päivän täytyy olla välillä 1-31 jne. ja kyseinen validointi rikkoo hänen koodinsa.
Sehän olisi loistavaa, korjaisit kaksi virhettä yhdellä muutoksella.
The Alchemist kirjoitti:
Ne, jotka jostain syystä ovat halunneet käyttää päiväystä "-1" vastoin speksejä ties mihin, joutuisivat vain keksimään uuden tavan bugikorjauksen jälkeen.
Jos spekseissä on nimenomaan selvitetty nuo rajat niin sillon komppaan, että kyseessä on bugi. Mutta jos missään ei ole alunperin rajattu miten luokkaa tulisi käyttää niin jos jokin kyseisellä luokalla on mahdollista niin se on mielestäni ominaisuus. Jos luokan käyttäjä on hyödyntänyt kyseistä "-1"-ominaisuutta niin mikä mielestäsi tekee siitä bugin? Voihan hän jättää vaikka kyseisen arvon tulostamatta ja tulostaa vain kuukauden ja vuoden 12.1990 tai jotain muuta vastaavaa. Jos kyseinen ominaisuus halutaan jättää pois niin sitten siirrytään siihen "seuraavaan suurempaan revisioon".
qeijo kirjoitti:
Mitä vikaa taulukoissa on...
Ei kai mitään? Eli minun tulisi käyttää tietovarastoina nyt taulukoita noiden ORMien sijaan?
qeijo kirjoitti:
OOP vaikuttaisi olevan kaikille jossain vaiheessa oppimiskäyrää vain uusi tapa kehystää omaa spagettia ilman sen kummempaa ymmärrystä mitä OOP on.
Mitä OOP sinun mielestäsi sitten on? Otetaan nyt vaikka klassinen XY-kordinaatisto esimerkki.
class Player { private $x, $y; //... public function moveTo($x, $y) { $this->x = $x; $this->y = $y; } } // Vai sitten tämä setter/getter tyyli class Player { private $x, $y; //... public function setX($x) { $this->x = $x; } public function setY($y) { $this->y = $y; } }
qeijo kirjoitti:
Tolla logiikalla ei koskaan korjata virheitä koska jonkun virheellinen implementaatio saattaisi hajota.
Näinhän kaikki toimivat. Jos jotain ei ole suunnittelupöydällä otettu huomioon ja päästetään versio ilmoille niin korjaus tehdään siihen "seuraavaan suureen revisioon". Jos halutaan nopeammin "korjaus" kyseiseen niin tehdään uusi funktio/luokka/mikätahansa ja suositellaan sitä käytettäväksi. Vai onko esimerkiksi PHP:n 5 version jälkeen korjattu jotain niin, että esimerkiksi toiminto joka käyttäytyi 5.0.0:ssa toimisi eri tavalla uusimmassa 5. versiossa?
Edit:
The Alchemist kirjoitti:
Riippuu oikeastaan käytetystä kehysympäristöstä, miten kannattaa itse lähteä liikkeelle.
Kumpaako kehysympäristöä suosittelet opiskelemaan? Itse olen tähän asti aina puhtaalta pöydältä kaiken ohjelmoinut, mutta eiköhän aika olisi jo nuihin vastaaviin siirtyä?
Edit2:
Ylimääräiset sulut poistettu luokan nimen perästä
Othnos kirjoitti:
Ei kai mitään? Eli minun tulisi käyttää tietovarastoina nyt taulukoita noiden ORMien sijaan?
Ei tähän ole vastausta, se riippuu sinusta.
Tarkoitin siis että jos luokka ei kuitenkaan sisältäisi mitään toiminnallisuutta ja data on jo taulukossa, miksi viedä tiedot luokkaan.
Othnos kirjoitti:
qeijo kirjoitti:
Tolla logiikalla ei koskaan korjata virheitä koska jonkun virheellinen implementaatio saattaisi hajota.
Näinhän kaikki toimivat. Jos jotain ei ole suunnittelupöydällä otettu huomioon ja päästetään versio ilmoille niin korjaus tehdään siihen "seuraavaan suureen revisioon". Jos halutaan nopeammin "korjaus" kyseiseen niin tehdään uusi funktio/luokka/mikätahansa ja suositellaan sitä käytettäväksi. Vai onko esimerkiksi PHP:n 5 version jälkeen korjattu jotain niin, että esimerkiksi toiminto joka käyttäytyi 5.0.0:ssa toimisi eri tavalla uusimmassa 5. versiossa?
En tietenkään tarkoittanut ihan tolla skaalalla, vaan puhuin enemmänkin kehittäjien välisestä asioista. Todistit juuri pointtini, eli lähtökohtaisesti kannattaa ohjelmoida rajapinta niin ettei riko mitään muuttamalla sen toteutusta.
Tämähän ei tietenkään päde joka asiassa, esimerkiksi jos luokka on ennen hyväksynyt setterissä negatiivisia lukuja eikä yhtäkkiä enää hyväksy, niin voi miettiä että mitä on ollut alun perin edes tekemässä ja missä on mennyt pahasti pieleen.
Othnos kirjoitti:
Mitä OOP sinun mielestäsi sitten on?
qeijo kirjoitti:
OOP vaikuttaisi olevan kaikille jossain vaiheessa oppimiskäyrää vain uusi tapa kehystää omaa spagettia ilman sen kummempaa ymmärrystä mitä OOP on.
Tämä ei ollut suunnattu sinulle vaan se oli lähinnä yleinen kannanotto.
En ymmärrä mihin halusit ottavani kantaa? Et ainakaan getter/setter asiaan, koska kummassakaan ei käytetä julkisia jäsenmuuttujia. Lisäksi esimerkissäsi on virheitä.
Othnos kirjoitti:
class Player() { private $x, $y; public function moveTo($x, $y) { $this->x = $x; $this->y = $y; } } // Vai sitten tämä setter/getter tyyli class Player() { private $x, $y; public function setX($x) { $this->x = $x; } public function setY($y) { $this->y = $y; } }
Othnos kirjoitti:
qeijo kirjoitti:
Mitä vikaa taulukoissa on...
Ei kai mitään? Eli minun tulisi käyttää tietovarastoina nyt taulukoita noiden ORMien sijaan?
Toki voit käyttää taulukoita ORMien sijaan. ORMit ovat yksi tapa tehdä asioita ja niissä on puolensa. Esim. tietokantarivin lukeminen taulukkoon on triviaalia esim. PDO:ssa, mutta jos teet taulukon arvoihin muutoksia niin niiden muutosten saaminen myös kantaan ei enää olekaan triviaalia. ORMia käyttäen voit muuttaa olion arvoja ja kertoa että haluat muutosten heijastuvan myös tietokantaan. Idea on siis lähinnä tehdä ohjelmoinnista helpompaa.
Othnos kirjoitti:
qeijo kirjoitti:
Tolla logiikalla ei koskaan korjata virheitä koska jonkun virheellinen implementaatio saattaisi hajota.
Näinhän kaikki toimivat. Jos jotain ei ole suunnittelupöydällä otettu huomioon ja päästetään versio ilmoille niin korjaus tehdään siihen "seuraavaan suureen revisioon".
No tässähän nyt ei alunperin puhuttu edes mistään julkaistusta jutusta. En oikeasti näe mitään järkeä ajattelumallissa että vaikka 5 hengen tiimi tekee softaa, niin virheitä ei voitaisi korjata ennen julkaisua, vaan pitäisi julkaista virheineen ja sitten vasta seuraavassa majorissa korjata.
qeijo kirjoitti:
Tarkoitin siis että jos luokka ei kuitenkaan sisältäisi mitään toiminnallisuutta ja data on jo taulukossa, miksi viedä tiedot luokkaan.
Tätähän juuri toisessa postauksessani kysyin, että miten kannattaisi lähteä lähestymään? Vastaukseksi sain tuon ORMin ja nyt taulukon. Suurin osa datasta kyllä käytetään sellaisenaan, mutta osassa tarvitaan yksinkertaista toiminnallisuutta esimerkiksi tiettyjen tyhjien arvojen tilalle oletusarvoja jne.
Tarkoititko siis seuraavan tyylistä tapaa?
// ... if ($row['hasTravels'] && !$row['startAddress']) { $row['startAddress'] = getDefaultStartAddress($row['projectId']); } $timeCards[$row['id']] = $row; // ... return $timeCards;
qeijo kirjoitti:
En ymmärrä mihin halusit ottavani kantaa? Et ainakaan getter/setter asiaan, koska kummassakaan ei käytetä julkisia jäsenmuuttujia.
Lähinnä tuohon ymmärrystä mitä OOP on.
Eli nyt jo kolmelta suunnalta tullut palautetta, että setterit/getterit on olio-ohjelmoinnissa se paras tapa tuottaa "value objecteja" joten lähden sitä tapaa toteuttamaan, jos jostain syystä en ORMeja sattuisikaan käyttämään.
Edit:
Grez kirjoitti:
No tässähän nyt ei alunperin puhuttu edes mistään julkaistusta jutusta. En oikeasti näe mitään järkeä ajattelumallissa että vaikka 5 hengen tiimi tekee softaa, niin virheitä ei voitaisi korjata ennen julkaisua, vaan pitäisi julkaista virheineen ja sitten vasta seuraavassa majorissa korjata.
Itse en ohjelmointialasta tiedä mitään, mutta tilanteessasi sitä ensimmäistäkään versiota ei ole julkistettu niin totta kai siinä on hyvä kaikki mahdolliset ajatusvirheet korjata.
Othnos kirjoitti:
Tarkoititko siis seuraavan tyylistä tapaa?
En. Yritin vain sanoa ettei luokkien käyttö ko. tapauksessa ole mikään itsestäänselvyys, varsinkaan jos neíiden käyttö ei tuo mitään varsinaista lisäarvoa.
Se mitä kannattaa tehdä riippuu projektin koosta ja monesta muusta asiasta. Ei ole varmasti mitään haittaa sinulle vaikka tutustuisit nyt joka tapauksessa Doctrineen. Objekteja voi toki mapata ilman sitä.
Othnos kirjoitti:
Vai onko esimerkiksi PHP:n 5 version jälkeen korjattu jotain niin, että esimerkiksi toiminto joka käyttäytyi 5.0.0:ssa toimisi eri tavalla uusimmassa 5. versiossa?
Php ei välttämättä ole ihan paras esimerkki hyvin hallitusta projektista mutta vastataan nyt kuitenkin. Tosin tästä saadaan myös helposti riita aikaan, mikä on php:ssä uusi "major-versio" ja mikä vain päivityspaketti vanhan päälle.
Esimerkkejä:
5.3.3 poisti konstruktoristatuksen sellaisilta funktioilta, joiden nimi oli sama kuin luokan nimi (Foo::Foo), mutta vain luokilta jotka sijaitsevat jossain nimiavaruudessa.
5.4.0 liitti E_ALL-lippuun myös E_STRICT-lipun (vaikuttaa virheraportointiin).
Jokaisen 5.x-julkkarin yhteydessä on lista muutoksista, jotka rikkovat yhteensopivuuden vanhojen versioiden kanssa.
Othnos kirjoitti:
The Alchemist kirjoitti:
Riippuu oikeastaan käytetystä kehysympäristöstä, miten kannattaa itse lähteä liikkeelle.
Kumpaako kehysympäristöä suosittelet opiskelemaan? Itse olen tähän asti aina puhtaalta pöydältä kaiken ohjelmoinut, mutta eiköhän aika olisi jo nuihin vastaaviin siirtyä?
Enpähän suosittele kumpaakaan toisen kustannuksella. Minä olen itse koodaillut hyvin paljon Zendillä, joka on php:n kehityksestä vastaavan yrityksen oma projekti. Se on myös ilmeisesti käytetyin php-kehys. Symfony taasen on esimerkiksi valittu tulevan Drupal 8:n perustaksi, joten sen käyttö tulee varmasti yleistymään lähivuosina.
Molemmissa on samankaltaisia ominaisuuksia, joten yhteen tutustuttuaan tajuaa jotain myös toisestakin. Lisäksi monet kolmannen osapuolen kirjastot ovat käytettävissä molemmilla, eli esimerkiksi Doctrinen käyttö ei pakota valitsemaan jompaa kumpaa. Zend voi tuntua raskaammalta/bloatimmalta juuri tämän puristisen get/set-metodien käytön takia.
Sekä Zend että Symfony ovat kuitenkin aika massiivisia kokonaisuuksia, joten kaikkien mentaliteetteihin ne eivät välttämättä sovi. CakePHP on kai "kevyempi" ja siitä on tulossa uusittu kolmosversio jossain tässä lähiaikoina. Se ottaakin tosin radikaalimman lähtökohdat ja tukee vain php 5.4:ää ja uudempia.
Meni kyllä kuvitelmani ohjelmistojen kehityksestä kokonaan uusiksi. Lähinnä sylettää tuo pohjaton netin, stack overflown, blogien yms. lukeminen joka on mennyt parin vuoden osalta täysin hukkaan. Olisi jo sillon pari vuotta sitten pitänyt tulla kysymyksineni tänne niin ei olisi mennyt niin paljon aikaa hukkaan.
Kiitokset paljon topiciin osallistujille ja vaikka se normaaliin tapaan meni täysin offtopiciksi niin nyt on selvät sävelet seuraaviksi askeleiksi kohti parempaa nettisovellusten kehittämistä.
Mikään ei myöskään voita hyvää kirjaa.
Useat lähinnä olio-ohjelmointiin sekä softan kehitykseen olevista mielipiteistäni perustuivat tuohon aiemmin viittaamaani Programming kirjaan, mutta voinet suositella minulle parempaa kirjaa.
Esimerkiksi juuri luin Oreillyn: Learning PHP design patterns - kirjan. Kirjassa käsiteltiin mm. erillaisia suunnittelumalleja PHP:n näkökulmasta.
Othnos kirjoitti:
Eli minun tulisi käyttää tietovarastoina nyt taulukoita noiden ORMien sijaan?
En nyt ota ORMiin kantaa, mutta minusta taulukon käyttö on syntaksiltaan erittäin kömpelöä verrattuna ihan tavallisen olion käyttöön. Siis käyttäisin ehdottomasti olioita, vaikka sitten vain PHP:n stdClass-olioita, joita syntyy, kun taulukon pakottaa olioksi ((object)$taulukko).
Othnos kirjoitti:
Esimerkiksi jotkin työt sisältävät matkoja, mutta toiset eivät ja matkan tiedot mielestäni muodostavat hyvän kokonaisuuden omillaankin.
Minusta kuulostaa, että olisi järkevintä laittaa matkat aivan erikseen. Silloin on helppo eritellä matkat tarkemminkin ja tallentaa esimerkiksi meno- ja paluumatka erikseen.
Othnos kirjoitti:
Mutta toisaalta onko siitä mitään hyötyä, koska aina kun tuntikortin tietoja haetaan tarvitaan myös kyseisten rivien kilometrit?
Kuten sanoin, hyöty riippuu enemmän siitä, miten valinta vaikuttaa koodisi selvyyteen ja ylläpitoon. Taulujen ja luokkien suhteiden muuttaminen jälkikäteen on turha vaiva, jos pystyy jo etukäteen tekemään joustavampaa koodia. Toisaalta turhiakaan ominaisuuksia ei kannata rakennella etukäteen, jos on ihan varma, ettei niitä tarvita.
Metabolix kirjoitti:
Minusta kuulostaa, että olisi järkevintä laittaa matkat aivan erikseen. Silloin on helppo eritellä matkat tarkemminkin ja tallentaa esimerkiksi meno- ja paluumatka erikseen.
Jos nyt sinnepäinkään ymmärsin mitä ajat takaa niin kuulostaa jo todella paljon paremmalta ratkaisulta, kuin tämän hetkinen. Sitä tulee vaan niin kapeakatseiseksi omien ratkaisujen suhteen, kun tekee jonkin nopean ratkaisun jota jälkikäteen yrittää muuttaa paremmaksi. Tarkoitit varmaan tyyliin seuraavanlaista?
time card id travel returnTravel -------------------------------- 1 1 2 travels id startAddress destinationAddress kilometers -------------------------------------------------------------- 1 here there 10 2 there here 10
Toki nuo osoitteetkin pitäisi normalisoida.
Lähtökohtaisesti tietokantarakenteissa avainluvut on 0, 1 ja n.
Tuossa nyt olet rakentanut siten, että time cardilla on aina tasan kaksi matkaa. Itselle tulee heti mieleen, että jatkossa voi olla esim. kesken työkeikan liikkumista tms, jolloin n matkaa per kortti olisi heti joustavampi.
On toki tilanteita, joissa 2 on järkevä. Esim. vaikka joku otteluseuranta, jossa jo pelin säännöt määrää että pelissä on aina 2 joukkuetta, eikä ole nähtävissä että se tulisi muuttumaan.
Eli itse tekisin näin päin:
TimeCard Id -------- 1 Travels Id TimeCard StartAddress DestinationAddress Distance ----------------------------------------------------------------------- 1 1 here there 10 2 1 there here 10
Jos nyt jostain syystä olisi tärkeää tietää kumpi on meno- ja kumpi paluumatka eikä esim. kellonajoille ole muuten tarvetta, niin voi toki vielä lisätä vaikka Type -kentän, jossa kerrotaan mistä matkasta on kyse.
Othnos kirjoitti:
Tarkoitit varmaan tyyliin seuraavanlaista?
time card id travel returnTravel -------------------------------- 1 1 2 travels id startAddress destinationAddress kilometers -------------------------------------------------------------- 1 here there 10 2 there here 10
Mieti tarkkaan tarviiko matkoja edes yhdistää? Onko lopputuloksen kannalta mitään väliä että jokin matka on paluumatka toiselle?
Grez kirjoitti:
Tuossa nyt olet rakentanut siten, että time cardilla on aina tasan kaksi matkaa. Itselle tulee heti mieleen, että jatkossa voi olla esim. kesken työkeikan liikkumista tms, jolloin n matkaa per kortti olisi heti joustavampi.
Totta. Vastaavia tilanteita voi tulla vastaan.
Grez kirjoitti:
Lähtökohtaisesti tietokantarakenteissa avainluvut on 0, 1 ja n.
Pitää yrittää pitää mielessä niin helpottaa varmasti tulevaisuudessa.
qeijo kirjoitti:
Mieti tarkkaan tarviiko matkoja edes yhdistää? Onko lopputuloksen kannalta mitään väliä että jokin matka on paluumatka toiselle?
Tästähän tosiaan pääsee eroon tuolla Grezin esimerkillä. Sinällään tuo "paluumatka" oli huonoa nimeämistä minulta, kun voihan kyseisessä tapauksessa olla, että paluumatkan saapumispaikka on eri kun menomatkan lähtöpaikka.
Niin, tarkoitin tietenkin 1:n-suhdetta, josta Grez antoi mallin: matkan yhteydessä kerrotaan, mihin työhön se liittyy. Rajoittaminen kahteen matkariviin estää kunnollisen erittelyn monessa yksinkertaisessakin tilanteessa, koska voihan samaankin matkaan tarvita useaa kulkuneuvoa (esim. juna-asemalta linja-auto määränpäähän).
Aihe on jo aika vanha, joten et voi enää vastata siihen.