Scala-tyyliopas

Tämä sivu esittelee eräitä ohjelmakoodin muotoilemiseen liittyviä käytäntöjä, jotka edistävät koodin luettavuutta ja ovat oleellisia aloittelevallekin Scalaa käyttävälle ohjelmoijalle. Oppaan ei ole tarkoitus olla kattava vaan pikemminkin johdanto aiheeseen.

Suuri osa tästä oppaasta on ymmärrettävissä kurssin ensimmäisen kahden kierroksen perusteella. Eräät kohdat kuitenkin liittyvät myöhemmin kurssilla käsiteltäviin asioihin.

Johdanto: miksi?

Hyvää ohjelmointityyliä tarvitaan tekemään ohjelmista luettavampia ja ymmärrettävämpiä ihmisille.

Tietokone ei luettavuudesta välitä. Esimerkiksi seuraavat Scala-kieliset luokkamäärittelyt tekevät periaatteessa ihan saman asian:

class
peli
(
val


kaalikeittoa       :Joukkue,val vi:                   Joukkue,val
  m1:Int,M2:
     Int){def
     Kumpi={if(m1>this
                                                                .
      M2)Some(
  this.kaalikeittoa)else if(M2
    > this.m1)
      Some(this.vi) else None
                           }}
/* Kukin Ottelu-olio edustaa yhtä urheiluottelua. Ottelu-luokka
 * soveltuu käytettäväksi sellaisten joukkuelajien yhteydessä,
 * joissa kaksi joukkuetta pelaa toisiaan vastaan tehden maaleja. */
class Ottelu(val kotijoukkue: Joukkue,
             val vierasjoukkue: Joukkue,
             val kotimaalit: Int,
             val vierasmaalit: Int) {

    /* Tämä metodi palauttaa ottelun voittajajoukkueen
     * tai None, jos kyseessä on tasapeli. */
    def voittaja = {
      if (this.kotimaalit > this.vierasmaalit)
        Some(this.kotijoukkue)
      else if (this.vierasmaalit > this.kotimaalit)
        Some(this.vierasjoukkue)
      else
        None
    }

}

Käytännössä nämä luokat eivät kuitenkaan tee samaa asiaa, koska ihminen ei halua ensimmäisen käyttämistä edes lähteä yrittämään.

Tämä esimerkki on tietysti kovin dramatisoitu, sillä onhan tuo ylempi koodi todella viheliäisesti kirjoitettu. Mutta jo paljon pienemmät ja tahattomammat tyylirikkeetkin aiheuttavat vaikeuksia ihmislukijalle, semminkin kun ohjelma on suuri.

Kuinka tärkeää tämä sitten on?

Lähes mitä tahansa menestyvää ohjelmaa muunnellaan ja korjaillaan moneen kertaan sen elinaikana. Ohjelman kasvaessa satoihin, tuhansiin tai jopa miljooniin koodiriveihin hyvä ohjelmointityyli on entistäkin tärkeämpää. Yksinäinenkään ohjelmoija ei muista täsmälleen, mitä on ollut tekemässä kuukausi tai vuosi sitten, ja voi olla vaikeuksissa joutuessaan muuttamaan omaa huonosti kirjoitettua ohjelmaansa. Tiimityötä tekevä tai edeltäjänsä ohjelmaa jatkokehittävä ohjelmoija joutuu usein lukemaan toisen kirjoittamaa koodia, mikä on hyvin vaikeaa, ellei kirjoittaja ole pyrkinyt helpottamaan ymmärtämistä.

Ohjelman luettavuuteen ja ymmärrettävyyteen vaikuttavat monet tekijät. Hyvin tärkeää on — tottakai — muun muassa se, miten ohjelmakokonaisuus on suunniteltu ja jäsennetty loogisiin kokonaisuuksiin. (Esimerkiksi: Millaisia luokkia ja metodeita on laadittu ongelman ratkaisemiseksi? Ohjelmoidaanko funktionaalisella vai imperatiivisella "tyylillä"?) Tällä sivulla kuitenkin keskitytään vain "muotoseikkoihin", jotka ovat tärkeitä nekin. Ohjelmointityyli tarkoittaa tällä sivulla "koodaustyyliä", tapaa, jolla rakenteeltaan tietynlainen ohjelma kirjoitetaan ohjelmakoodiksi. Yllä olevien kahden esimerkkiluokan rakenne on sama, mutta ohjelmointityyli aivan erilainen.

Tyylikäytännöistä

Ohjelmointikielen yhteyteen liittyy yleensä joitakin tyylikäytäntöjä (style conventions tai code conventions). Saattaa olla niinkin, että on useita toisilleen vaihtoehtoisia mutta yleisesti tunnustettuja tyylejä kirjoittaa koodia tietyllä kielellä.

Scala-kielellekin on olemassa puolivirallinen tyyliohje, joka ei kylläkään ole saavuttanut kaikkien Scala-ohjelmoijien varauksetonta hyväksyntää. Tämä kurssin tyyliopas pääosin noudattaa tuota ohjetta, mutta poikkeaa siitä joidenkin yksityiskohtien osalta.

Millekään ohjelmointikielelle ei ole olemassa yhtä ainoaa oikeaa tyyliä. Ei ole tärkeää, että noudatat tälläkään kurssilla mitään tiettyä ohjelmointityyliä, mutta on tärkeää, että noudatat kussakin ohjelmassasi jotakin tyyliä johdonmukaisesti. Ilman johdonmukaisuutta on vaikea ellei mahdoton saavuttaa tavoitetta eli ohjelmakoodin selkeyttä.

On hyvä oppia:

  • noudattamaan jotakin selkeää ohjelmointityyliä itse, ja
  • sietämään ja ymmärtämään myös muita ohjelmointityylejä.

Esimerkiksi tämän kurssin jatkokursseilla noudatetaan osin tästä kurssista poikkeavia ohjelmointityylejä. Ja jos etsit vaikkapa Internetistä käsiisi Scala-ohjelmia, huomaat pian, että niitä on kirjoitettu erilaisilla tyyleillä.

Tyylisuosituksia saa rikkoa, mutta vasta, kun tietää rikkovansa niitä, ja silloinkin vain harkitusti.

Miten luettavuutta parannetaan?

Luettavuuden kannalta tärkeimpiin koodin muotoilukeinoihin kuuluvat:

  • tyhjän tilan (whitespace) käyttö, erityisesti rivien sisentäminen,
  • kommenttien kirjoittaminen koodiin, ja
  • ohjelman osien nimeäminen järkevästi.

Näistä lisää seuraavissa kappaleissa.

Ohjelmointityylin perusteita: tyhjän tilan käyttö

Esimerkiksi Scala-kielellä kirjoitettu ohjelmakoodi muodostaa selvän hierarkisen rakenteen. Tiedostoissa on luokkamäärittelyjä. Kukin luokka sisältää muuttujien ja metodien määrittelyjä. Metodien toteutukset sisältävät käskyjä, jotka usein sisältävät toisia käskyjä.

Rakenne on syytä huomioida ohjelman ulkoasussa, jotta se on helpompi hahmottaa. Rivien sisentäminen (indentation) auttaa tässä.

Vertaa vaikkapa seuraavia koodinpätkiä (jotka eivät tee mitään järkevää). Ne eroavat toisistaan paitsi sisentämisen myös muun tyhjän tilan käyttämisen osalta. Lienet samaa mieltä, että jälkimmäistä on helpompi lukea.

class Esimerkkiluokka(val esimerkki:ToinenLuokka,var toinenEsimerkki:Int) {

def metodi()={
while(this.esimerkki.testaaLopetetaanko()){
this.teeJotain()
if (this.esimerkki.teeTesti()) {
this.teeJotainMuuta()
this.toinenEsimerkki+=1
}else{
this.esimerkki.muutteleJuttuja()
}
}
}

def toinenMetodi()={
println(this.esimerkki)
}

// ... muita metodeita ...

}
class Esimerkkiluokka(val esimerkki: ToinenLuokka, var toinenEsimerkki: Int) {

  def metodi() = {
    while (this.esimerkki.testaaLopetetaanko()) {
      this.teeJotain()
      if (this.esimerkki.teeTesti()) {
        this.teeJotainMuuta()
        this.toinenEsimerkki += 1
      } else {
        this.esimerkki.muutteleJuttuja()
      }
    }
  }

  def toinenMetodi() = {
    println(this.esimerkki)
  }

  // ... muita metodeita ...

}

Aloittelevat ohjelmoijat kuitenkin joskus kirjoittavat ensimmäistä versiota muistuttavaa sisentämätöntä koodia tai sisentävät holtittomasti. Älä sinä tee niin.

Scalassa on tapana käyttää kahden välilyönnin kokoista sisennystä.

Äskeisessä esimerkissä käytettiin sisennysten lisäksi myös muita tietokoneen näkökulmasta turhia välilyöntejä ja tyhjiä välirivejä jäsentämään ohjelmaa. Tyhjiä rivejä kannattaa käyttää ainakin monirivisten metodien välissä. Myös yksittäisen metodien sisällä voi ryhmitellä rivejä tyhjää tilaa käyttäen. Kuitenkin jos metodista tulee muutamaa riviä pidempi ja siinä esiintyy toisistaan erottuvia kokonaisuuksia, niin kannattaa harkita metodin jakamista useaksi.

Ohjelmointityylin perusteita: kommentit

Kaikkea ohjelmasta ei voi kertoa pelkällä tietokoneenluettavalla ohjelmakoodilla. Sekä ohjelman osien käyttötarkoitusta että toteusta on syytä selostaa ihmiskielellä, kirjoittamalla koodiin kommentteja.

Erityisen tärkeitä ovat sellaiset kommentit, joilla dokumentoidaan ohjelman osien tarkoitusta ja sitä, miten niitä voi käyttää muusta ohjelmakoodista käsin. Esimerkiksi:

  • Millaista käsitettä luokkamäärittely kuvaa? Mihin luokkaa voi käyttää?
  • Mitä kukin metodi tekee? Millaisia parametreja sille kuuluu antaa? Mitä metodin kutsujan sopii odottaa palautusarvona? Mitä erikoistilanteita kutsujan tulee huomioida?

Tällaiset osien käyttöä dokumentoivat kommentit kannattaa kirjoittaa sillä ajatuksella, että ohjelman osan käyttäjä voi käyttää sitä dokumentaation perusteella tuntematta toteuttavan ohjelmakoodin yksityiskohtia.

Löydät runsaasti esimerkkejä luokkien ja metodien dokumentoinnista kurssin oheisprojektien ohjelmakoodista.

Myös metodien toteutusta — niitä käskyjä, jotka suoritetaan, kun metodia kutsutaan — voi selventää kommentein. Kuitenkin yleensä tulisi pyrkiä siihen, että tällaisia kommentteja on vain erittäin vähän, jos ollenkaan, eikä enempää tarvitakaan. Jos ohjelman rakenne on suunniteltu hyvin, ohjelman osat on nimetty järkevästi ja kunkin osan käyttötapa on dokumentoitu, niin yksittäiset metodit ovat usein niin pieniä ja selkeitä, ettei niihin ihmeempiä lisäselityksiä tarvita.

Tällä kurssilla ei ole pakollista, että itse kirjoitat kommentteja ohjelmakoodiisi ellei erikseen pyydetä. Hyvä idea se voi silti olla. Monilla jatkokursseilla ja monissa käytännön ohjelmointitehtävissä kurssien ulkopuolella oman ohjelmakoodin kommentointi on pakollista.

Ohjelmointityylin perusteita: nimeäminen

Muuttujanimi "lapsivesiliukumäki" ei useimmissa yhteyksissä ole kovin havainnollinen. Eivät yleensä myöskään "a", "a2", "vasEtu", "luku", "arvo", "jaska" tai "fsdhafsdkjh".

Ohjelman osia — luokkia, metodeita, muuttujia, jne. — nimetessäsi:

  • Valitse nimi eli tunnus (identifier) siten, että se kuvaa osan merkitystä ja käyttötarkoitusta ohjelmassa. Pikkuisissa kokeilukoodinpätkissä toki voi käyttää yleisluontoisempia ja lyhyempiä nimiä kuten luku, a tai vaikka kokeilu.
  • Noudata ainakin sellaisia ohjelmointikielen yhteyteen sovittuja nimeämiskäytäntöjä, joiden rikkominen hämäisi lukijaa. (Esimerkki: nimeä Scala-ohjelman luokat isolla alkukirjaimella.)

Tyyliseikkojen lisäksi on tietenkin huomattava kielen tekniset rajoitteet. Ohjelmointikielen varattuja sanoja (reserved words) kuten var ja val ei voi käyttää niminä. (Scalan varatut sanat on lueteltu Scalaa kootusti -sivun lopussa.) Kieli voi muutenkin rajoittaa nimien rakennetta; Scalassa ja monessa muussa kielessä nimi ei voi alkaa numerolla, vaikka numeroita muuten voikin käyttää.

Siirrytään nyt tästä yleisemmästä tyyliseikkojen tarkastelusta Scala-kielen pariin. Alla on valikoima aloittelijallekin oleellisia tyyliteemoja ja niihin liittyviä suosituksia.

Scala: nimeäminen

Sallitut merkit

Älä käytä nimissä välilyöntejä.

Erikoismerkkejä (+, & jne.) on aloittelijan syytä välttää nimissä kokonaan. Joillakin merkeillä on myös erityismerkityksiä Scalassa. Toisaalta näitä merkkejä voi kyllä perustellusti käyttää esimerkiksi silloin, jos haluaa määritellä omia metodeita, joita on tarkoitus käyttää operaattorinotaatiolla (luku 4.4).

Skandinaavisia "ääkkösiä" ja eksoottisempia kirjoitusmerkkejä voi periaatteessa käyttää, mutta koska ne voivat joidenkin ohjelmoijan aputyökalujen kanssa tuottaa ongelmia, niin saattaa olla parempi välttää niitä.

Isot ja pienet kirjaimet

Useasta sanasta koostuvassa nimessä käytetään isoja alkukirjaimia ainakin muissa kuin ensimmäisessä sanassa: esim. LuokanNimi, muuttujanNimi, metodinNimi.

Luokkien nimet kirjoitetaan käytännössä aina isolla alkukirjaimella. (Poikkeuksen tekevät vain jotkin todella poikkeukselliset luokat, joista ei tässä enempää.)

Yksittäisolioiden nimet kirjoitetaan normaalisti isolla alkukirjaimella, joskin tästä voidaan perustellusti poiketa esimerkiksi silloin, kun kyse on ns. pakkausoliosta (luku 4.4).

Metodien nimet kirjoitetaan aina pienellä alkukirjaimella.

Muuttujien nimet kirjoitetaan normaalisti pienellä alkukirjaimella.

Ohjelmiin määritellään usein sellaisia val-muuttujia, joiden arvo on tiedossa jo ennen ohjelman ajoa ja jotka edustavat "pysyvästi" jotakin arvoa. Tällaisia val-muuttujia sanotaan vakioiksi (constant) ja ne on tapana nimetä isolla alkukirjaimella. Esimerkiksi pakkauksessa scala.math on vakio nimeltä Pi; se on val-muuttuja, jonka arvona on piin likiarvo. Vakioiden käyttö "maagisten arvojen" sijaan on hyvää ohjelmointityyliä (ks. oppimateriaalin luku 6.2).

Muista yllä mainituista nimistä poiketen pakkausten nimet kirjoitetaan kokonaisuudessaan pienillä kirjaimilla.

Yleisimmätkään tietotyyppien nimet kuten Int ja String eivät ole varattuja sanoja, joten niitä voi periaatteessa käyttää niminä. Eivät myöskään kirjastofunktioiden nimet kuten abs tai sqrt. Yleensä tuskin on kuitenkaan hyvä ajatus vaikkapa laatia omaa luokkaa nimeltä Int, koska sekaannushan siitä seuraa.

Scala: funktioiden määrittelemisestä

Scalan tyylikäytännöistä konstikkaimmat liittyvät metodien ja muiden funktioiden määrittelyyn. Kuten kurssimateriaalin luku 1.6 ensimmäisen kerran toi esiin, Scalassa voi määritellä sekä tilaa muuttamattomia (eli "sivuvaikutuksettomia") että tilaa muuttavia (eli "sivuvaikutuksellisia") funktioita. Ja kuten luvussa 1.7 kerrottiin, Scalassa on tapana osoittaa funktion luonne välimerkkien käytöllä. Seuraavilla ehkä tarpeettoman monimutkaiseltakin tuntuvilla tyylikäytännöillä pyritään tekemään koodin lukijalle helpoksi sen havaitseminen, mitkä funktiokutsut voivat muuttaa ohjelman tilaa ja mitkä eivät. Tästä on hyötyä ainakin kokeneelle Scala-ohjelmoijalle.

Alla on esimerkkejä siitä, miten funktioita on tapana määritellä (ja olla määrittelemättä) Scalassa. Muista kuitenkin tämä yleinen ohje:

Yksinkertainen nyrkkisääntö

Jos tällä kurssilla laitat kaikkiin funktiomäärittelyihin sekä yhtäsuuruusmerkin, aaltosulut että rivinvaihdot ja sisennykset, niin et ainakaan tee mitään vakavasti pieleen. Mutta voit koettaa noudattaa alla lueteltuja käytäntöjä.

Yhtäsuuruusmerkeistä

Käytä aina yhtäsuuruusmerkkiä parametriluettelon ja funktion rungon välissä:

def summa(eka: Int, toka: Int) = eka + toka

Teknisesti ottaen Scala sallii yhtäsuuruusmerkin pois jättämisen funktioiden määrittelyistä eräissä tapauksissa ("arvoa palauttamattomat" eli Unit-palautusarvoiset funktiot). Esimerkiksi tämä on toimiva funktiomäärittely, mutta näin ei pidä kirjoittaa:

def moikkaa(teksti: String) {
  println(teksti)
}

Vaan näin:

def moikkaa(teksti: String) = {
  println(teksti)
}

Scala-kielen kehittäjätiimi on antanut ymmärtää, että myöhemmissä Scala-kielen versioissa yhtäsuuruusmerkki tullee olemaan pakollinen.

Tilaa muuttamattomat funktiot

Aaltosulkeita käytetään vain, jos funktion runkoon (aaltosulkeiden väliseen osaan) kuuluu useita rivejä, kuten tässä:

def tulos(eka: Int, toka: Int) = {
  val valitulos = eka * 2 + toka
  valitulos * valitulos
}

Seuraavakin kyllä toimii, mutta näin ei ole tapana kirjoittaa:

def summa(eka: Int, toka: Int) = {
  eka + toka
}

Tämän sijaan vaikkapa summa-funktiolle voi käyttää ja yleensä käytetäänkin ylempänä näkyvää täysin yksirivistä funktiomäärittelyä.

Tilaa muuttavat funktiot

Tilaa muuttavien funktioiden tapauksessa on tapana käyttää aina aaltosulkeita, rivinvaihtoja ja sisennyksiä, vaikka funktion toteutuksen sisältyisi vain yksi rivi:

def tulostaSumma(eka: Int, toka: Int) = {
  println("Summa on: " + (eka + toka))
}

Yhteenveto edellisestä

Tässä luvusta 1.7 toistettuna taulukko, joka kokoaa yhteen esimerkkien sisällön. Siirtämällä hiiren kursorin alleviivatun kohdan päälle saat lisätietoja.

Aiheuttaako funktio tilan muutoksia? Palauttaako arvon? Kurssin termi Yhtäsuuruus- merkki rungon eteen? Aaltosulut rungon ympärille? Rivinvaihdot ja sisennykset?
Ei aiheuta koskaan Palauttaa Tilaa muuttamaton funktio Kyllä! Ainakin kun useita käskyjä. Aina kun aaltosulut. Myös pitkiin yksirivisiin.
Ei palauta (Jos funktio ei muuta tilaa eikä palauta arvoa, niin se ei voi olla järin hyödyllinen kuin erikoistilanteissa. Tällaisia funktioita sinun ei tarvitse tällä kurssilla laatia.)
Aiheuttaa ainakin joskus Palauttaa Tilaa muuttava funktio Kyllä! Kyllä Kyllä
Ei palauta Tilaa muuttava funktio Kyllä Kyllä Kyllä

Eikä siinäkään vielä kaikki: parametrittomat funktiot

Edellisten lisäksi erikoistapauksen muodostavat funktiot, jotka eivät vastaanota lainkaan parametreja. Scalassa on kaksi tapaa määritellä tällaisia funktioita: tyhjän parametriluettelon vastaanottaviksi tai aidosti parametrittomiksi.

Tyhjän parametriluettelon vastaanottaviksi määritellään parametrittomat tilaa muuttavat funktiot (jollainen esiintyy ensi kerran kurssimateriaalissa luvussa 2.7). Tässä esimerkki:

def tulostaVakiolause() = {
  println("Huomaa tyhjät kaarisulut eli tyhjä parametriluettelo tuossa yllä.")
}

Aidosti parametrittomiksi on tapana määritellä tilaa muuttamattomat parametrittomat funktiot, kuten massa-metodi alla. Tällöin ei nimen perään laiteta tyhjiä kaarisulkeita.

class Kappale(val tilavuus: Double, val tiheys: Double) {

  def massa = this.tilavuus * this.tiheys

}

Tässä on kyse nimenomaan tyylikäytännöstä, ei Scala-kielen pakottamasta säännöstä. Periaatteessa voisit vaikka laittaa aina tyhjät kaarisulut kaikkien parametrittomien metodien määrittelyihin (kuten eräissä muissa ohjelmointikielissä tehdään). Valinnalla tosin on vaikutusta siihen, miten funktiota voi kutsua, mistä lisää alla.

Scala: funktioiden kutsumisesta

Parametrittomat funktiot

Parametritonta funktiota kutsuessa on huomioitava, onko funktio määritelty aidosti parametrittomaksi (eli se on tilaa muuttamaton, jos tyylikäytäntöä on noudatettu) vai onko sillä tyhjä parametriluettelo (eli se on tilaa muuttava).

Aidosti parametritonta funktiota kutsuessa kirjoitetaan — ja on virheilmoituksen välttämiseksi kirjoitettava — kutsuun ainoastaan funktion nimi ilman kaarisulkeita:

jokuKappale.massa     // toimii
jokuKappale.massa()   // ei toimi

Jos funktiolla on tyhjä parametriluettelo, on periaatteessa mahdollista kutsua sitä kummalla tahansa tavalla, mutta on tapana suosia kaarisulullista tapaa:

tulostaVakiolause()   // toimii; suositeltava tapa
tulostaVakiolause     // toimii, mutta näin ei ole tapana kirjoittaa

Operaattori- vai pistenotaatio?

Kurssimateriaalin luvussa 4.4 todetaan, että yksiparametrisia Scala-metodeita voi kutsua kahdella eri tavalla: operaattorinotaatiolla ja pistenotaatiolla. Esimerkiksi ns. "plus-operaattorikin" on Scalassa itse asiassa lukuolion metodi ja yhteenlaskun voi kirjoittaa joko 1 + 1 tai 1.+(1). Vastaavasti puskurista voi etsiä alkiota joko lausekkeella lukupuskuri.indexOf(10) tai lukupuskuri indexOf 10.

Luvussa 4.4 myös valitaan kurssin tyylikäytännöksi pistenotaation suosiminen. Operaattorinotaatiota käytetään vain eräissä tarkkaan valituissa yhteyksissä, joissa käytetään yleisiä tietotyyppejä (kuten Int) ja niiden yleisiä lyhytnimisiä metodeita (kuten +).

On kuitenkin hyvä tietää, että tämän kurssin ulkopuolella pistenotaatiota käytetään joiltain osin yleisemmin kuin kurssilla. Monet esimerkiksi suosivat sitä korkeamman asteen funktioiden yhteydessä (mistä on lyhyt selitys luvun luvun 8.1 lopussa).

Valitse itse, kumpaa merkintätapaa haluat käyttää. Pyri johdonmukaisuuteen.

Scala: tyyppien kirjaamisesta

Scala-kieleen liittyy erottamattomasti tyyppipäättely (luku 1.8), jonka ansiosta monia ohjelman osien tietotyyppejä (siis staattisia tyyppejä; luku 6.2) ei tarvitse erikseen kirjoittaa koodiin. Ei-pakolliset tyypit jätetään monesti kirjoittamatta Scala-ohjelmakoodiin.

Muuttujien tyypit kirjataan yleensä vain silloin, kun se on välttämätöntä, mistä yleisin esimerkki on funktioiden parametrimuuttujat. Joskus ohjelmoija voi muutoinkin katsoa, että muuttujan tyypin merkitseminen oleellisesti selventää ohjelmaa, ja mikäs siinä.

Funktioiden palautusarvon tyypin saa yleensä jättää kirjaamatta. Poikkeuksiakin on, kuten kuormitetut (luku 3.5) ja rekursiiviset (luku 11.3) funktiot. Toisaalta palautusarvojen tyypit voi kyllä mainiosti aina kirjata, jos se tuntuu parantavan luettavuutta.

Joissakin tilanteissa tyyppien kirjaaminen koodiin voi myös parantaa Scala-työkalujen antamia käännösaikaisia virheilmoituksia.

Scala: lisää tyhjästä tilasta

Tyhjän tilan käyttö on Scala-ohjelmissa varsin vapaata, kuten luvun alun esimerkinrumiluskin osoittaa. Joitakin rajoituksia toki on: muuttujan nimessä ei saa olla tyhjää, tavallisen merkkijonoliteraalin keskellä ei voi vaihtaa riviä, jne. Rajoitukset käyvät tyhjentävästi ilmi Scala-kielen määrittelyä tavaamalla, mutta tähän hätään riittänevät seuraavat rivinvaihtoihin liittyvät suositukset.

Ohjelmakoodin rivin ei ole hyvä antaa kasvaa ylettömän pitkäksi. (Toisia tämä tuntuu haittaavan paljon enemmän ja herkemmin kuin toisia.) Yhden monimutkaisen ilmaisun voi jakaa useammalle riville. Eräs tyypillinen esimerkki on luokka, jolla on lukuisia konstruktoriparametreja; sellaisen voi rivittää kuten tehtiin luokassa Ottelu tämän sivun alussa.

Entä toisin päin? Voiko rivinvaihtoja jättää pois, kun käskyt ovat lyhyitä? Scala sallii tämän, kun käytät puolipisteitä erottamaan käskyt. Esimerkiksi tämä on täysin mahdollinen Scala-ohjelman rivi:

var luku = 0; println(luku); luku += 1; println(luku); luku += 10; println(luku)

Näin ei kuitenkaan ole yleensä hyvä tehdä, vaan peräkkäin suoritettavat käskyt tulisi kirjoittaa peräkkäisille riveille, jotta ohjelman suorituksen eteneminen näkyisi koodista suoremmin.

Joskus esimerkiksi REPLissä työskennellessä voi olla kätevää syöttää tämänsorttisia monikäskyisiä rivejä. REPLin "kertakäyttökoodissahan" muutenkin ohjelmointityylillä on vähemmän merkitystä.

Scala: this-sanasta

Jos olion muuttujalle ja metodin paikalliselle muuttujalle on annettu keskenään sama nimi, niin ei ole itsestään selvää, kumpaan pelkkä nimi metodin koodissa viittaa. (Keskenään samannimisten muuttujien käyttö on joskus perusteltua. Toki on syytä myös arvioida sitä, olisiko mahdollista nimetä ohjelman osat kuvaavammin.) Tällaisissa ja vastaavissa nimeämiskonfliktitilanteissa this-sanaa on käytettävä, kun halutaan viitata nimenomaan olion tietoihin. Luvussa 2.2 on pieni esimerkki.

Samasta luvusta 2.2 löytyy myös tämä suositus:

Silloinkin, kun se ei ole pakollista, this-sanan käyttö korostaa lukijalle sitä, missä kohdissa käytetään olion muuttujia ja missä paikallisia. Suosittelemme kaikille kurssilaisille this-sanan käyttöä aina olion muuttujiin viitatessa, sillä se selkeyttää ohjelmia.

Kyseessä on osittain makuasia. Jos haluat — ja jos tiedät mitä teet — niin saat kyllä kurssillakin jättää ei-välttämättömät this-sanat pois. this-sanojen pois jättäminen sen ollessa sallittua on kurssimme ulkopuolella varsin yleistä.

Scala: if

Kuten funktioita määritellessäkin, myös if-valintakäskyä muotoillessa huomioidaan, onko käsky tilaa muuttamaton vai ei.

Tilaa muuttava if

Jos kyseessä on tilaa muuttava if-käsky, kirjoitetaan haarojen ympärille aaltosulut riippumatta siitä, kuinka monta riviä kyseisessä haarassa on:

if (luku > 0) {
  println("On positiivinen.")
} else {
  println("Ei ole positiivinen.")
}

Siis ei näin, vaikka tämäkin toimii:

if (luku > 0) println("On positiivinen.") else println("Ei ole positiivinen.")

Tilaa muuttavasta if-käskystä voi jättää else-osion pois (luku 2.9). Tällöin valinnaisesti suoritettava osio laitetaan aina aaltosulkeisiin:

if (luku > 0) {
  println("Tämä tulostuu vain, jos luku on positiivinen. Muuten ei tulostu mitään.")
}

Tilaa muuttamaton if

Jos kyseessä on tilaa muuttamaton if-käsky, on ensin katsottava, onko jommassa kummassa haarassa useita peräkkäisiä käskyjä. Jos näin on, käytetään aaltosulkeita ryhmittelemään käskyt kuten tilaa muuttavassakin tapauksessa.

Jos kummassakin tilaa muuttamattoman if-käskyn haarassa kuitenkin on vain yksi käsky, voi molemmat haarat kirjoittaa yhdelle riville:

val tulos = if (jaettava != 0) jaettava / jakaja else 0

Silti jos rivistä tulisi näin kovin pitkä tai muuten epäselvä, on parempi jakaa käsky useaksi riviksi:

val toinenTulos =
  if (ekaEhtoOnTotta && tokaEhtoOnMyosTotta && tahanVielaJokuTosiPitkaEhto && cetera)
    muodostaTulosKayttaenTataFunktiotaJollaOnPitkaNimi(luku)
  else
    muodostaTulosKayttaenJotainToistaFunktiota(luku)

Scala: while-, do- ja for-silmukat

while- ja do-toistokäskyjä (luku 6.1) voi käyttää järkevästi vain ohjelman tilan muuttamiseen. Myös kaikki tämän kurssin materiaalissa esiintyvät for-silmukat (luku 5.3) aiheuttavat tilanmuutoksia. Kaikki mainitut muotoillaan vastaavasti kuin yllä kuvattu tilaa muuttava if-käsky: käytetään aina useaa riviä ja aaltosulkeita. Esimerkiksi while-silmukka kirjoitetaan näin:

while (ehtoOnVoimassa()) {
  teeJuttu()
}

Ei näin (vaikka tämäkin toimii):

while (ehtoOnVoimassa()) teeJuttu()

Scala: funktioliteraaleista

Scala-kieli tarjoaa erilaisia tapoja luoda nimettömiä funktioita funktioliteraaleilla. Tässä ei ole yhtä kaikkiin tilanteisiin parhaiten sopivaa tapaa eikä liioin yhtä tapaa, jota kaikki Scala-ohjelmoijat käyttäisivät. On tarpeen opetella useita erilaisia tapoja, mutta itse voit ohjelmissasi suosia sitä tapaa, joka sinusta tuntuu kyseiseen kohtaan selkeimmältä. Aihetta käsitellään luvussa 7.4.

Viimeinen sana

Any fool can write code that a computer can understand.
Good programmers write code that humans can understand.

—Martin Fowler

Palaute

Palautusta lähetetään...

Palautus on vastaanotettu.