Tämä kurssi on jo päättynyt.

Scala-tyyliopas

Tästä sivusta:

Pääkysymyksiä: Miten kirjoitan ohjelmakoodini helpommin luettavaan ja muokattavaan muotoon? Miten käytän esimerkiksi sisennyksiä, sulkeita ja eri kokoisia kirjaimia Scala-koodissani? Ovatko kommentit koodissa hyvä juttu?

Mitä käsitellään? Johdanto ohjelmointityyliin ja tyylikäytäntöihin. Valittuja ohjelmakoodin muotoilemiseen liittyviä seikkoja.

Esitiedot: Suuri osa tästä oppaasta on ymmärrettävissä kurssin ensimmäisen kolmen kierroksen perusteella ja melkein koko opas neljän ensimmäisen. Yksittäiset kohdat liittyvät myöhemmin kurssilla käsiteltäviin asioihin.

Johdanto: miksi?

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

—Martin Fowler

Hyvää ohjelmointityyliä tarvitaan tekemään ohjelmista luettavampia ja ymmärrettävämpiä ihmisille. Hyvällä tyylillä kirjoitettuihin ohjelmiin tulee vähemmän virheitä, ja niiden muokkaaminen on vaivattomampaa.

Tietokone ei luettavuudesta välitä. Esimerkiksi seuraavat luokkamäärittelyt periaatteessa ovat Scalaa ja periaatteessa tekevät ihan saman asian:

class
peli
(
val


kaalikeittoa       :Joukkue,val vi:       Joukkue,val
    m1:Int,M2:
       Int):
  def

                   Kumpi={if (m1)>this
                                                                   .
      M2
      then (Some(
  this.kaalikeittoa))else if M2
    >this .m1 then
      Some(this.vi) else None
                           }
/* Kukin Ottelu-olio edustaa ottelua kahden urheilujoukkeen välillä. Tämä
 * luokka sopii lajeihin, joissa pelataan vastakkain ja tehdään maaleja. */
class Ottelu(val kotijoukkue: Joukkue, val vierasjoukkue: Joukkue,
             val kotimaalit: Int,      val vierasmaalit: Int):

  /* Palauttaa ottelun voittajajoukkueen tai tasapelin sattuessa None. */
  def voittaja =
    if this.kotimaalit > this.vierasmaalit then
      Some(this.kotijoukkue)
    else if this.vierasmaalit > this.kotimaalit then
      Some(this.vierasjoukkue)
    else
      None

end Ottelu

Käytännössä nämä luokat eivät kuitenkaan tee samaa asiaa, koska ihminen ei halua ensimmäistä ohjelmaansa laittaa.

Esimerkki on kovin dramatisoitu, sillä onhan tuo ylempi koodi naurettavankin viheliäisesti kirjoitettu. Mutta jo paljon pienemmät ja tahattomammat tyylirikkeet vaikeuttavat tai ainakin hidastavat ohjelmoijan työtä, semminkin kun ohjelma on suuri.

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 lukemaan toisen kirjoittamaa koodia, mikä on vaikeaa tai ainakin työlästä, ellei kirjoittaja ole ollut tyylillisesti hereillä.

Ohjelman luettavuuteen ja muokattavuuteen vaikuttaa moni asia. Hyvin tärkeää on — tottakai — miten ohjelmakokonaisuus on suunniteltu ja jäsennetty loogisiin kokonaisuuksiin: Millaisia luokkia ja metodeita laadit ongelman ratkaisemiseksi? Ohjelmoitko funktionaalisella vai imperatiivisella "tyylillä"? Ja niin edelleen.

Tällä sivulla kuitenkin keskitymme vain "muotoseikkoihin", jotka ovat tärkeitä nekin. Ohjelmointityyli tarkoittaa tällä sivulla "koodinkirjoittamistyyliä" eli tapaa, jolla rakenteeltaan tietynlainen ohjelma kirjataan ohjelmakoodiksi. Yllä olevien kahden esimerkkiluokan rakenne on sama, mutta ohjelmointityyli aivan erilainen.

Tyylikäytännöistä

Ohjelmointikielille on usein määrilty tyylikäytäntöjä (style conventions tai coding conventions). Voi myös olla useita toisilleen vaihtoehtoisia mutta yleisesti tunnustettuja tyylejä kirjoittaa koodia tietyllä kielellä. Ei ole olemassa yhtä ainoaa oikeaa tyyliä.

Scala-kielellekin on olemassa puolivirallinen tyyliohje, joka ei kylläkään ole saavuttanut kaikkien Scala-ohjelmoijien varauksetonta hyväksyntää eikä ole (vuoden keskivaiheilla 2022) aivan ajantasainen. Tämä kurssin tyyliopas pääosin noudattaa tuota ohjetta, mutta täydentää sitä ja poikkeaa siitä joissakin yksityiskohdissa.

Ei ole tärkeää, että noudatat O1-kurssilla mitään tiettyä ohjelmointityyliä, mutta on tärkeää, että noudatat kussakin ohjelmassasi jotakin tyyliä johdonmukaisesti. Olematta johdonmukainen on vaikea ellei mahdoton saavuttaa tavoitetta eli ohjelmakoodin selkeyttä.

Tämän kurssin jatkokursseilla noudatetaan osin poikkeavia ohjelmointityylejä. Ja jos etsit vaikkapa internetistä käsiisi Scala-ohjelmia, huomaat pian, että niitä on kirjoitettu erilaisilla tyyleillä. Siksi on hyvä oppia a) noudattamaan jotakin selkeää ohjelmointityyliä itse; ja b) sietämään ja ymmärtämään myös muita ohjelmointityylejä.

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

Miten luettavuutta parannetaan?

Eräitä tärkeimpiä koodin muotoilukeinoja ovat

  • rivien sisäntäminen ja muu tyhjän tilan (whitespace) käyttö,

  • kommenttien kirjoittaminen koodiin kun perusteltua, ja

  • ohjelman osien nimeäminen järkevästi.

Näistä lisää seuraavissa kappaleissa.

Ohjelmointityylin perusteita: tyhjän tilan käyttö

Esimerkiksi Scala-kielellä kirjoitetulla ohjelmakoodilla on hierarkkinen rakenne. 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ä.

Jotta tuo rakenne olisi helpompi hahmottaa, se huomioidaan ohjelman ulkoasussa. Rivien sisentäminen (indentation) johdonmukaisesti auttaa tässä.

Vertaa vaikkapa seuraavia koodinpätkiä. Ne eivät tee mitään järkevää mutta ovat teknisesti toimivaa Scalaa ja eroavat toisistaan lähinnä sisennyksissä ja muussa tyhjän tilan käytössä. Jälkimmäistä versiota on helpompi lukea. Aloittelevat ohjelmoijat joskus kirjoittavat ensimmäistä muistuttavaa holtittomasti sisennettyä koodia. Älä sinä tee niin.

class Esimerkkiluokka(val esimerkki:ToinenLuokka,var toinenEsimerkki:Int):
 def metodi()=
  while this.esimerkki.testaaLopetetaanko()do
   this.teeJotain()
    if this.esimerkki.teeTesti()then
       this.teeJotainMuuta()
          this.toinenEsimerkki+=1
    else
     this.esimerkki.muutteleJuttuja()
 def toinenMetodi()=
 println(this.esimerkki)
// ... muita metodeita ...
end Esimerkkiluokka
class Esimerkkiluokka(val esimerkki: ToinenLuokka, var toinenEsimerkki: Int):

  def metodi() =
    while this.esimerkki.testaaLopetetaanko() do
      this.teeJotain()
      if this.esimerkki.teeTesti() then
        this.teeJotainMuuta()
        this.toinenEsimerkki += 1
      else
        this.esimerkki.muutteleJuttuja()
    end while

  def toinenMetodi() =
    println(this.esimerkki)

  // ... muita metodeita ...

end Esimerkkiluokka

Vaikka sisennystä voikin turhaan kasvattaa kuten ylemmässä äskeisistä koodeista, älä tee niin. Vaikka sisennyksen voi yksinkertaisissa metodeissa jättää poiskin, älä tee niinkään.

Scalassa käytetään tyypillisimmin kahden välilyönnin kokoista sisennystä. Jotkut kyllä suosivat leveämpiä. Tärkeintä on johdonmukaisuus.

Alemmassa äskeisistä koodeista on 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ä.

Yksittäisen metodin sisälläkin voi rivejä ryhmitellä vähän kuin luonnollista kieltä ryhmitellään kappaleiksi. Kuitenkin jos metodista tulee muutamaa riviä pidempi, niin harkitse metodin jakamista useaksi, joista kullakin on oma tehtävänsä.

Sisennyksistä, aaltosulkeista ja vanhoista Scala-versiosta

Käytämme ajantasaista Scalan kolmosversiota. Kuten luvusta 1.7 alkaen opetetaan, sisennyksillä on merkitystä: ne vaikuttavat ohjelman rakenteeseen ja sitä kautta toimintaan. Näin ei ole aina ollut.

Scala 2:ssa ja aiemmin olivat aaltosulkeet tarpeen monessa eri ohjelman kohdassa. Esimerkiksi äskeinen koodi voisi näyttää tältä:

// Vanhantyylistä Scalaa
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 ...

}

Itse asiassa näinkin voi kirjoittaa myös Scala 3:ssa jos Scala-työkaluston asetukset on laitettu sopivasti. Se ei kuitenkaan ole tapana. O1:llä emme käytä emmekä suosittele tätä tyyliä.

Sisennyksiin asia liittyy siten, että vanhassa aaltosulkeellisessa tyylissä sisennyksillä ei ole mitään vaikutusta ohjelman toimintaan, vaan ne voi kirjoittaa ihan miten sattuu. Myös vanhalla tyylillä kirjoittaessa kuitenkin on vahva suositus (vaikkei pakko) sisentää koodi siten kuin yllä. Käytännössä vanha tyyli siis turhaan mahdollistaa epäsiistin koodin.

Ohjelmointityylin perusteita: kommentit

Jo luvussa 1.2 näkyi, että ohjelmaan voi kirjoittaa ihmiselle tarkoitettuja kommentteja.

Kommentit voivat olla koodin luettavuudelle hyväksi tai pahaksi.

Tärkeitä ja hyödyllisiä ovat tyypillisesti sellaiset kommentit, joilla dokumentoidaan ohjelman osien tarkoitus ja se rajapinta, jolla niitä voi käyttää:

  • Millaista käsitettä luokkamäärittely kuvaa? Mihin luokkaa voi käyttää?

  • Mitä kukin julkinen metodi tekee? Millaisia parametreja sille kuuluu antaa? Mitä metodin kutsujan sopii odottaa palautusarvona? Mitä erikoistilanteita kutsujan tulee huomioida?

Tällaiset rajapintoja dokumentoivat kommentit kirjoitetaan auttamaan ohjelmoijia käyttämään rajapintaa välttämättä tuntematta toteuttavan ohjelmakoodin yksityiskohtia. Löydät runsaasti esimerkkejä kurssin oheismoduulien koodista.

Myös metodien toteutusta — niitä käskyjä, jotka suoritetaan, kun metodia kutsutaan — voi selventää kommentein. Pyri silti siihen, että tällaisia kommentteja on vain 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. Selittävä kommentti koodin seassa voikin olla merkki siitä, että koodi olisi kannattanut kirjoittaa toisin! Kommentin lisäämisen sijaan kannattaa harkita muita selkiytyskeinoja; esimerkiksi mutkikkaan rivin voi rikkoa useaksi ja ottaa välitulokset talteen kuvaavasti nimettyihin muuttujiin.

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

Kun nimeät ohjelman osia — luokkia, metodeita, muuttujia jne. — valitse nimi eli tunnus (identifier) kuvaamaan tuon osan merkitystä ja käyttötarkoitusta ohjelmassa. arvo ja luku ovat yleensä varsin epähavainnollisia muuttujan nimiä. Samoin a, a2, vasEtu, jaska ja fsdhafsdkjh.

(Pikkuisissa kokeilukoodinpätkissä voit toki käyttää yleisluontoisempia ja lyhyempiä nimiä kuten luku, a tai vaikka kokeilu.)

Noudata ohjelmointikielen yhteyteen sovittuja nimeämiskäytäntöjä, joiden rikkominen hämäisi lukijaa. Esimerkiksi Scalassa nimeä luokat isolla alkukirjaimella ja muuttujat yleensä pienellä.

Tyyliseikkojen lisäksi on tietenkin huomattava kielen tekniset rajoitteet. Varattuja sanoja kuten var ja val ei voi käyttää niminä. Kieli voi muutenkin rajoittaa nimien rakennetta; Scalassa ja monessa muussa kielessä nimi ei voi alkaa numerolla, vaikka numeroita muuten voikin käyttää.


Tähän mennessä olemme käsitelleet ohjelmointityyliä pitkälti yleisellä tasolla. Siirrytään nyt nimenomaan Scala-kieleen liittyviin tyylisuosituksiin.

Scala: loppumerkit

Scala-ohjelman monirivisille osille voi kirjoittaa loppumerkin, joka ei vaikuta ohjelman toimintaan mutta voi auttaa lukijaa hahmottamaan ohjelman rakennetta (ks. esim. luvut 1.7 ja 2.2). Alla olevista koodeista ensimmäisessä ei ole loppumerkkejä mutta toisessa on neljä:

class Esimerkki(val muuttuja: Int):

  def pikkumetodi =
    this.muuttuja + 1

  def isompiMetodi(luku: Int) =
    if luku > 0 then
      luku + 1
    else
      luku
class Esimerkki(val muuttuja: Int):

  def pikkumetodi =
    this.muuttuja + 1
  end pikkumetodi

  def isompiMetodi(luku: Int) =
    if luku > 0 then
      luku + 1
    else
      luku
    end if
  end isompiMetodi

end Esimerkki

Teknisessä mielessä loppumerkit ovat aina vapaaehtoisia. Tietyissä tilanteissa niiden käyttö on kuitenkin erittäin suositeltavaa. Nämä tilanteet ovat:

  • Päättyvän rakenteen (siis luokan, olion, metodin, if-käskyn tms.) sisällä on tyhjiä rivejä. Tämä on yleisin syy käyttää loppumerkkiä.

  • Rakenne on parisenkymmentä riviä pitkä tai pidempi.

  • Rakenteen sisällön loppu on sisennetty monta tasoa alkua syvemmälle.

  • Ohjelmoija muusta syystä arvioi, että loppumerkki selkiyttää kyseistä koodia.

Näiden suositusten mukaisesti äskeisen koodinpätkän voisi kirjoittaa näin:

class Esimerkki(val muuttuja: Int):

  def pikkumetodi =
    this.muuttuja + 1

  def isompiMetodi(luku: Int) =
    if luku > 0 then
      luku + 1
    else
      luku

end Esimerkki

Luokan koodissa on tyhjiä rivejä, joten sille kirjataan loppumerkki. Metodille ja if-käskylle ei loppumerkkiä tarvita.

Esimerkkikoodi on sikäli tyypillinen, että usein loppumerkki tulee juuri luokkien ja yksittäisolioiden loppuun. Hyvin kirjoitetun koodin metodit ovat yleensä lyhyitä, eikä niille tai niiden sisältämille käskyille yleensä tarvitse loppumerkkiä, vaan koodi on selkeää muutenkin. Toisaalta loppumerkin kyllä saa aina kirjoittaa, jos parhaaksi näkee.

Scala: nimeäminen

Isot ja pienet kirjaimet

Nimen ensimmäinen kirjain kertoo jotakin siitä, mihin nimi viittaa:

  • Luokkien nimet on tapana kirjoittaa isolla alkukirjaimella.

  • Pakkausten nimet on tapana kirjoittaa kokonaan pienellä.

  • Yksittäisolioiden nimet on tapana kirjoittaa isolla alkukirjaimella, joskin tästä voi harkitusti poiketa.

  • Muuttujien nimet on tapana kirjoittaa pienellä alkukirjaimella.

    • Kuitenkin vakioiksi mielletyt muuttujat aloitetaan isolla. Vakion arvo on tiedossa jo ennen ohjelman ajoa ja edustaa "pysyvästi" jotakin tietoa. Vakioiden käyttö "maagisten arvojen" sijaan on hyvää ohjelmointityyliä (ks. esim. luku 2.6).

  • Metodien nimet on tapana kirjoittaa pienellä alkukirjaimella.

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

Rajoituksia

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

Erikoismerkkejä (+, &, { jne.) on aloittelijan syytä välttää nimissä. Joillakin merkeistä on 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 5.2).

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

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

Scala: funktiot

Scalan tyylikäytännöistä konstikkaimmat koskevat funktioita.

Funktio voi olla vaikutukseton tai vaikutuksellinen. Kuten luvussa 1.7 kerrottiin, Scalassa on tapana osoittaa funktion luonne välimerkkien käytöllä. Näin autetaan koodin lukijaa havaitsemaan, mitkä funktiot voivat vaikuttaa ohjelman tilaan ja mitkä eivät. Tästä on hyötyä ainakin kokeneelle Scala-ohjelmoijalle.

Rivittäminen riippuu vaikutuksellisuudesta

Yksinkertaisen vaikutuksettoman funktion voi kirjoittaa yhdelle riville näin:

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

Saa kyllä rivittääkin, eli tämäkin on OK:

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

Kun funktio on vaikutuksellinen, laita runko aina omalle rivilleen, vaikka siinä olisi vain yksi rivi:

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

Jos funktion runkoon kuuluu useita rivejä, on rivittäminen aina tarpeen riippumatta siitä, onko funktio vaikutuksellinen vai ei.

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

Parametrittomat funktiot ja kaarisulkeet

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 vaikutukselliset funktiot (jollainen esiintyy ensi kerran kurssimateriaalissa luvussa 2.6). Tässä esimerkki:

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

Aidosti parametrittomiksi määritellään vaikutuksettomat 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

Kyse on tyylikäytännöstä, ei Scala-kielen pakottamasta säännöstä. Periaatteessa voisit vaikka laittaa aina tyhjät kaarisulkeet kaikkien parametrittomien metodien määrittelyihin kuten joissakin muissa ohjelmointikielissä tehdään. (Mutta älä laita.)

Jos funktiolla on tyhjä parametriluettelo () on myös sitä kutsuessa kirjoitettava nuo sulkeet: tulostaVakiolause(). Tämä erottaa vaikutuksellisen funktiokutsun vaikutuksettomasta koodin lukijallekin. Toisaalta vaikutuksettomat funktiokutsut näyttävät täsmälleen samalta kuin muuttujien käyttökin käyttötavan yhtenevyyden periaatteen mukaisesti.

Operaattori- vai pistenotaatio?

Kurssimateriaalin luvussa 5.2 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.

Tällä kurssilla suosimme pistenotaatiota. Operaattorinotaatiota käytämme vain eräissä tarkkaan valituissa yhteyksissä, joissa käytetään yleisiä tietotyyppejä (kuten Int) ja niiden yleisiä lyhytnimisiä metodeita (kuten +).

On silti hyvä tietää, että tämän kurssin ulkopuolella operaattorinotaatiota käytetään vähän yleisemmin kuin kurssilla. Monet esimerkiksi suosivat operaattorinotaatiota korkeamman asteen metodeita kutsuessa (mistä on lyhyt selitys luvun 6.3 lopussa).

Scala: tyyppien kirjaamisesta

Scala-kieleen liittyy erottamattomasti tyyppipäättely, jonka ansiosta monia koodin osien tietotyyppejä (siis staattisia tyyppejä) 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 voit muutoinkin katsoa, että muuttujan tyypin merkitseminen oleellisesti selventää ohjelmaa, ja mikäs siinä.

Funktioiden palautusarvon tyypin saa monesti jättää kirjaamatta. Poikkeuksiakin on, kuten kuormitetut ja rekursiiviset funktiot. Toisaalta palautusarvojen tyypit voi kyllä aina kirjata; tapauksesta riippuen se voi selkiyttää ohjelmia ja vähentää virheitä.

Osa Scala-ohjelmoijoista merkitsee tyypit kaikille julkisille muuttujille ja palautusarvon tyypit kaikille julkisille metodeille. Tämä voi ohjelman luonteesta riippuen olla erittäinkin hyvä ajatus.

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

Scala: lisää koodin rivittämisestä

Ohjelmakoodin rivin ei ole hyvä antaa kasvaa ylettömän pitkäksi. (Siitä on eri näkemyksiä, mikä on liian pitkä.) 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 sen, 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ä, vaan peräkkäin suoritettavat käskyt tulisi kirjoittaa peräkkäisille riveille, jotta imperatiivisen koodin suoritusvaiheet näkyisivät selvemmin.

Esimerkiksi REPLissä voi toki joskus kätevästi 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, on käytettävä this-sanaa, kun halutaan viitata nimenomaan olion tietoihin; luvussa 2.2 on tästä pieni esimerkki. (Keskenään samannimisten muuttujien käyttö on joskus perusteltua. Arvioi toki, olisiko mahdollista nimetä ohjelman osat kuvaavammin.)

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

Silloinkin, kun se ei ole pakollista, this-sanan käyttö voi olla perusteltua. Sana 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. Kurssimme ulkopuolella on (valitettavasti?) varsin yleistä jättää this-sana pois kun sallittua.

Scala: valintakäskyt if ja match

Myös valintakäskyä muotoillessa huomioidaan, onko käsky vaikutukseton vai ei. Perusajatus on sama kuin funktiossa: vaikutuksellinen koodi jaotellaan aina peräkkäisille riveille, mutta vaikutuksettoman voi kirjoittaa yhdellekin. Alla on esimerkkejä.

Vaikutuksellinen valintakäsky

Kirjoita vaikutuksellisen if-käskyn haarat aina omille riveilleen:

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

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

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

Vaikutuksellisessa if-käskyssä ei välttämättä ole else-haaraa (luku 3.4). Tällöinkin then-haara omalle rivilleen:

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

Vaikutuksellinen match-käsky rivitetään vastaavasti:

ehkaArvo match
  case Some(arvo) =>
    println("Arvo on olemassa.")
    println("Se on: " + arvo)
  case None =>
    println("Ei arvoa.")

Vaikutukseton valintakäsky

Monissa vaikutuksettomissa if-lausekkeissa on yksinkertaiset then- ja else-haarat. Kun näin on, voit kirjoittaa koko käskyn yhdelle riville ja vaikka muuttujamäärittelyn peräänkin:

val tulos = if jakaja != 0 then jaettava / jakaja else 0

Toisaalta on täysin sallittua jakaa käsky riveiksi esimerkiksi jommallakummalla näistä tavoista:

val tulos =
  if jakaja != 0 then jaettava / jakaja else 0
val tulos =
  if jakaja != 0 then
    jaettava
  else
    0

Viimeisin kirjoitustapa on erityisen suositeltava silloin, jos rivistä tulisi muuten kovin pitkä tai muuten epäselvä:

val tulos =
  if ekaEhtoOnTotta && tokaPitkaEhtoOnMyosTotta && cetera then
    muodostaTulosKayttaenTataFunktiotaJollaOnPitkaNimi(luku)
  else
    muodostaTulosKayttaenJotainToistaFunktiota(luku)

Rivittäminen on luonnostaan tarpeen myös silloin, jos kummassa tahansa haarassa on useita peräkkäisiä käskyjä:

val tulos =
  if ehtoTayttyy then
    val valitulos = laskeValitulos(luku)
    Some(laskeLopputulos(valitulos))
  else
    None

Joihinkin tilanteisiin sopii tyyli, jossa kukin yksirivinen haara kirjoitetaan if- tai else-sanan perään (ja ehkä vielä välilyönneillä jämäköitetään):

val kuvaus =
  if      luku < 0     then "negatiivinen"
  else if luku < 100   then "pieni"
  else if luku < 10000 then "iso"
  else                      "tosi iso"

match-käskyn kanssa logiikka on samankaltainen. Kun valinnaisen osion muodostaa yksittäinen vaikutukseton lauseke, sen voi kirjoittaa samalle riville kuin case ja nuoli =>. Kumpi vain näistä käy:

val raportti = ehkaArvo match
  case Some(arvo) => "Arvo on: " + arvo
  case None       => "Ei arvoa"
val raportti =
  ehkaArvo match
    case Some(arvo) => "Arvo on: " + arvo
    case None       => "Ei arvoa"

Scala: while- ja for-silmukat

while-käskyillä (luku 9.1) vaikutetaan ohjelman tilaan. Muotoile while-silmukat vastaavasti kuin yllä kuvattu vaikutuksellinen if-käsky eli rivittäen:

while ehtoOnVoimassa() do
  teeJuttu()

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

while ehtoOnVoimassa() do teeJuttu()

Scalassa on kahdenlaisia for-silmukoita: fordo ja foryield. Kurssin virallisessa materiaalissa esiintyy vain ensin mainittuja (luvusta 5.5 alkaen). Ne ovat vaikutuksellisia ja rivitetään vastaavasti kuin while-silmukatkin:

for alkio <- kokoelma do
  teeJuttu(alkio)

Vaikutuksettomat foryield-silmukat

Scalassa on myös foryield-silmukkatyyppi, jota käytetäänkin laajasti. O1:llä se ei ole esillä isosti, mutta vähän kuitenkin. (Ks. luku 6.3 tai Scalaa kootusti.)

Nämä silmukat ovat lähes aina vaikutuksettomia. Kun näin on, ja kun silmukka on yksinkertainen, voi silmukan kirjoittaa yhdellekin riville näin tai näin:

val pituudet =
  for sana <- sanakokoelma yield sana.length
val pituudet = for sana <- sanakokoelma yield sana.length

Scala: funktioliteraaleista

Scalassa on erilaisia tapoja luoda nimettömiä funktioita funktioliteraaleilla. Mikään tavoista ei ole kaikissa tilanteissa paras eikä ylivoimaisesti yleisin. On syytä tuntea eri tavat, vaikka itse voitkin ohjelmissasi suosia sitä tapaa, joka sinusta tuntuu kyseiseen kohtaan selkeimmältä. Aihetta käsitellään luvussa 6.2.

Yhteenvetoa

  • Hyvä ohjelmointityyli on tärkeää, jotta ohjelmia on helpompi lukea ja kehittää. Hyvään ohjelmointityyliin kuuluu koodin muotoilu siten, että se auttaa ihmislukijaa.

  • Tyylisäännöstöt kehottavat tietynlaiseen sisentämiseen, muuttujien nimeämiseen ja niin edelleen. Eri ohjelmointikielille on omia tyylisäännöstöjään; yhdellekin kielelle on tyypillisesti useita vaihtoehtoisia.

  • Valitset minkä tyylin tahansa, käytä sitä johdonmukaisesti, jotta koodin lukijalle ei aiheudu tarpeetonta vaivaa. Toisaalta tyylikäytännöistä voi myös luistaa, kun sille on perusteluja.

  • Termejä sanastosivulla: ohjelmointityyli, tyylikäytäntö; kommentti; sisentää; tunnus.

Palaute

Tekijät

Tämän oppimateriaalin kehitystyössä on käytetty apuna tuhansilta opiskelijoilta kerättyä palautetta. Kiitos!

Materiaalin luvut tehtävineen ja viikkokoosteineen on laatinut Juha Sorva.

Liitesivut (sanasto, Scala-kooste, usein kysytyt kysymykset jne.) on kirjoittanut Juha Sorva sikäli kuin sivulla ei ole toisin mainittu.

Tehtävien automaattisen arvioinnin ovat toteuttaneet: (aakkosjärjestyksessä) Riku Autio, Nikolas Drosdek, Joonatan Honkamaa, Antti Immonen, Jaakko Kantojärvi, Niklas Kröger, Kalle Laitinen, Teemu Lehtinen, Jaakko Nakaza, Strasdosky Otewa, Timi Seppälä, Teemu Sirkiä, Anna Valldeoriola Cardó ja Aleksi Vartiainen.

Lukujen alkuja koristavat kuvat ja muut vastaavat kuvituskuvat on piirtänyt Christina Lassheikki.

Yksityiskohtaiset animaatiot Scala-ohjelmien suorituksen vaiheista suunnittelivat Juha Sorva ja Teemu Sirkiä. Teemu Sirkiä ja Riku Autio toteuttivat ne apunaan Teemun aiemmin rakentamat työkalut Jsvee ja Kelmu.

Muut diagrammit ja materiaaliin upotetut vuorovaikutteiset esitykset laati Juha Sorva.

O1Library-ohjelmakirjaston ovat kehittäneet Aleksi Lukkarinen ja Juha Sorva. Useat sen keskeisistä osista tukeutuvat Aleksin SMCL-kirjastoon.

Tapa, jolla käytämme O1Libraryn työkaluja (kuten Pic) yksinkertaiseen graafiseen ohjelmointiin, on saanut vaikutteita tekijöiden Flatt, Felleisen, Findler ja Krishnamurthi oppikirjasta How to Design Programs sekä Stephen Blochin oppikirjasta Picturing Programs.

Oppimisalusta A+ luotiin alun perin Aallon LeTech-tutkimusryhmässä pitkälti opiskelijavoimin. Nykyään tätä avoimen lähdekoodin projektia kehittää Tietotekniikan laitoksen opetusteknologiatiimi ja tarjoaa palveluna laitoksen IT-tuki. Pääkehittäjänä on nyt Markku Riekkinen, jonka lisäksi A+:aa ovat kehittäneet kymmenet Aallon opiskelijat ja muut.

A+ Courses -lisäosa, joka tukee A+:aa ja O1-kurssia IntelliJ-ohjelmointiympäristössä, on toinen avoin projekti. Sen suunnitteluun ja toteutukseen on osallistunut useita opiskelijoita yhteistyössä O1-kurssin opettajien kanssa.

Kurssin tämänhetkinen henkilökunta löytyy luvusta 1.1.

Joidenkin lukujen lopuissa on lukukohtaisia lisäyksiä tähän tekijäluetteloon.

a drop of ink
Palautusta lähetetään...