Kurssin viimeisimmän version löydät täältä: O1: 2024
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?
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?
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 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 kovin dramatisoitu, sillä onhan tuo ylempi koodi todella viheliäisesti kirjoitettu. Mutta jo paljon pienemmät ja tahattomammat tyylirikkeetkin 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 usein lukemaan toisen kirjoittamaa koodia, mikä on hyvin vaikeaa, ellei kirjoittaja ole pyrkinyt helpottamaan koodin lukemista ja työstämistä.
Ohjelman luettavuuteen ja muokattavuuteen vaikuttaa moni asia. Hyvin tärkeää on — tottakai — se, miten ohjelmakokonaisuus on suunniteltu ja jäsennetty loogisiin kokonaisuuksiin: Millaisia luokkia ja metodeita on laadittu ongelman ratkaisemiseksi? Ohjelmoidaanko 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ä
Ohjelmointikielen yhteyteen liittyy yleensä joitakin tyylikäytäntöjä (style conventions tai coding conventions). Voi myös olla 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ä.
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ä. Siksi on hyvä oppia:
- noudattamaan jotakin selkeää ohjelmointityyliä itse; ja
- 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?
Luettavuuden kannalta tärkeimpiin koodin muotoilukeinoihin kuuluvat:
- 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 on syytä huomioida ohjelman ulkoasussa. 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ä.
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 kannattaa harkita metodin jakamista useaksi osaksi, joista kullakin on oma tehtävänsä.
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 tarkoitusta ja sitä rajapintaa, 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?
Dokumentaatiokommentit ja Scaladoc
Jos Scala-ohjelman osia dokumentoivat kommentit muotoilee tietyllä tavalla, niin niiden perusteella voi automaattisesti muodostaa Scaladoc-sivuja, kuten kurssimateriaalin luvussa 3.2 mainitaan. Lisää Scaladoc-työkalusta voit lukea Alvin Alexanderin tutoriaalista.
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. Kuitenkin yleensä tulisi pyrkiä 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.
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 eräisiin nimenomaan Scala-kieleen liittyviin tyylisuosituksiin.
Scala: nimeäminen
Isot ja pienet kirjaimet
Nimen ensimmäinen kirjain kertoo jotakin siitä, mihin nimi viittaa:
- Luokkien nimet on tapana kirjoittaa isolla alkukirjaimella.
- Yksittäisolioiden nimet on tapana kirjoittaa isolla alkukirjaimella, joskin tästä voidaan perustellusti poiketa esimerkiksi silloin, kun kyse on ns. pakkausoliosta (luku 5.2).
- Muuttujien nimet on tapana kirjoittaa pienellä alkukirjaimella.
- 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:
pakkausten nimet on tapana kirjoittaa kokonaisuudessaan pienillä kirjaimilla.
Rajoituksia
Välilyönnit nimissä
Scalassa välilyöntejä on teknisesti mahdollista käyttää nimien sisällä, jos koko nimen ympäröi gravis-aksenttimerkeillä näin:
val `mun hieno muuttuja` = 10mun hieno muuttuja = 10 `mun hieno muuttuja`res0: Int = 10 mun hieno muuttuja<console>:11: error: not found: value mun mun hieno muuttuja ^
Tätä mahdollisuutta ei ole tapana hyödyntää. Sen käyttö luultavasti lähinnä hämmentää.
Ä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 5.2).
Skandinaavisia "ääkkösiä" ja eksoottisempia kirjoitusmerkkejä voi periaatteessa käyttää, mutta koska ne voivat joidenkin aputyökalujen kanssa tuottaa ongelmia, niin saattaa 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: funktioiden määrittelemisestä
Scalan tyylikäytännöistä konstikkaimmat koskevat funktioiden määrittelyjä.
Funktio voi olla vaikutukseton (eli "sivuvaikutuksettomia") tai vaikutuksellinen. Kuten luvussa 1.7 kerrottiin, Scalassa on tapana osoittaa funktion luonne välimerkkien käytöllä. Näin pyritään tekemään koodin lukijalle helpoksi havaita, mitkä funktiot voivat vaikuttaa ohjelman tilaan ja mitkä eivät. Tästä on hyötyä ainakin kokeneelle Scala-ohjelmoijalle.
Yhtäsuuruusmerkeistä
Käytä aina yhtäsuuruusmerkkiä parametriluettelon ja funktion rungon välissä:
def summa(eka: Int, toka: Int) = eka + toka
Yhtäsuuruusmerkin pois jättäminen
Teknisesti ottaen Scala sallii yhtäsuuruusmerkin pois jättämisen
funktioiden määrittelyistä eräissä tapauksissa (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)
}
Vaikutuksettomat funktiot
Aaltosulkeita käytetään vaikutuksettomissa funktioissa 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ällainen funktio kirjoitettaisiin tavallisesti yhdelle riville kuten hieman ylempänä tehtiinkin.
Vaikutukselliset funktiot
Vaikutuksellisten 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.
Vaikuttaako funktio tilaan? | Palauttaako arvon? | Kurssin termi | Yhtäsuuruus- merkki rungon eteen? | Aaltosulkeet rungon ympärille? | Rivinvaihdot ja sisennykset? |
---|---|---|---|---|---|
Ei vaikuta koskaan | Palauttaa | Vaikutukseton funktio | Kyllä | Ainakin kun useita käskyjä. | Aina kun aaltosulkeet. Myös pitkiin yksirivisiin. |
Ei palauta | (Jos funktio ei vaikuta tilaan eikä palauta arvoa, niin se ei voi olla järin hyödyllinen kuin erikoistilanteissa. Tällaisia funktioita sinun ei tarvitse tällä kurssilla laatia.) | ||||
Vaikuttaa ainakin joskus | Palauttaa | Vaikutuksellinen funktio | Kyllä | Kyllä | Kyllä |
Ei palauta | Vaikutuksellinen funktio | Kyllä | Kyllä | Kyllä |
Helppo vaihtoehto
Jos tällä kurssilla laitat kaikkiin funktiomäärittelyihin yhtäsuuruusmerkin, aaltosulkeet ja sisennykset, niin et ainakaan tee mitään vakavasti pieleen. Mutta voit koettaa noudattaa yllä lueteltuja käytäntöjä.
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 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 on tapana määritellä 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
}
Tässä on kyse nimenomaan 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 eräissä muissa ohjelmointikielissä tehdään). Valinnalla tosin on merkitystä 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 vaikutukseton, jos tyylikäytäntöä on noudatettu) vai onko sillä tyhjä parametriluettelo (eli se on vaikutuksellinen).
Aidosti parametritonta funktiota kutsuessa kirjoitetaan — ja on virheen välttämiseksi kirjoitettava — kutsuun ainoastaan funktion nimi ilman kaarisulkeita:
jokuKappale.massa // toimii
jokuKappale.massa() // ei toimi
Jos funktiolla on tyhjä parametriluettelo, on teknisesti mahdollista kutsua sitä kummalla tahansa tavalla, mutta on tapana suosia kaarisulkeellista tapaa vaikutuksellisuuden korostamiseksi:
tulostaVakiolause() // toimii; suositeltava tapa
tulostaVakiolause // toimii mutta vältä; ei toimi tulevissa Scala-versioissa
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 kuitenkin hyvä tietää, että tämän kurssin ulkopuolella operaattorinotaatiota käytetään joiltain osin 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 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 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 kaikill julkisille metodeille.
Joissakin tilanteissa tyyppien kirjaaminen koodiin voi parantaa Scala-työkalujen antamia käännösaikaisia virheilmoituksia.
Scala: lisää tyhjästä tilasta
Kuten luvun alun esimerkinrumiluskin osoittaa, tyhjän tilan käyttö on Scalassa varsin vapaata; sama pätee useimpiin muihinkin ohjelmointikieliin. Joitakin rajoituksia toki on: varatun sanan keskelle ei sovi lisätä tyhjää, (tavallisen) merkkijonoliteraalin keskellä ei voi vaihtaa riviä jne. Rajoitukset löytyvät tyhjentävästi Scala-kielen määrittelyä tavaamalla.
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ä 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, 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. Toki on syytä myös arvioida sitä, 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ö korostaa lukijalle sitä, missä kohdissa käytetään olion muuttujia ja missä paikallisia. Suosittelemme kaikille kurssilaisillethis
-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: valintakäskyt if
ja match
Kuten funktioita määritellessä, myös valintakäskyä muotoillessa huomioidaan, onko käsky vaikutukseton vai ei. Perusajatus on muutenkin sama: vaikutuksellinen koodi jaotellaan peräkkäisille riveille.
Vaikutuksellinen valintakäsky
Jos kyseessä on vaikutuksellinen if
-käsky, kirjoitetaan haarojen ympärille aaltosulkeet
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.")
Vaikutuksellisesta if
-käskystä voi jättää else
-osion pois (luku 3.4). 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.")
}
Vastaavasti vaikutuksellisessa match
-käskyssä rivitetään ja sisennetään:
ehkaArvo match {
case Some(arvo) =>
println("Arvo on olemassa.")
println("Se on: " + arvo)
case None =>
println("Ei arvoa.")
}
Vaikutukseton valintakäsky
Jos kyseessä on vaikutukseton 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 vaikutuksellisessakin tapauksessa.
Jos kummassakin vaikutuksettoman if
-käskyn haarassa kuitenkin on vain yksi käsky,
voi molemmat haarat kirjoittaa yhdelle riville:
val tulos = if (jakaja != 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)
match
-käskyn kanssa logiikka on samankaltainen. Kun valinnaisen osion muodostaa
yksittäinen vaikutukseton lauseke, sen voi kirjoittaa samalle riville kuin case
ja
nuoli =>
.
val raportti = ehkaArvo match {
case Some(arvo) => "Arvo on: " + arvo
case None => "Ei arvoa"
}
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 6.2.
Scala: while
-, do
- ja for
-silmukat
while
- ja do
-toistokäskyjä (luku 8.3) voi käyttää järkevästi vain ohjelman
tilaan vaikuttamiseen. Myös kaikki tämän kurssin virallisessa materiaalissa esiintyvät
for
-silmukat (luku 5.5) ovat vastaavasti vaikutuksellisia.
Muotoile tuollaiset vaikutukselliset silmukat vastaavasti kuin yllä kuvattu
vaikutuksellinen if
-käsky: käytä 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()
Yhteenvetoa
- Hyvä ohjelmointityyli on tärkeää, jotta ohjelmia on helpompi lukea ja jatkokehittää. Hyvään ohjelmointityyliin kuuluu mm. 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.
- Valitsi minkä tyylin tahansa, sitä on syytä käyttää 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.
Viimeinen sana
Any fool can write code that a computer can understand.Good programmers write code that humans can understand.—Martin Fowler
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, Jaakko Kantojärvi, Niklas Kröger, Teemu Lehtinen, Strasdosky Otewa, Timi Seppälä, Teemu Sirkiä 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 tällä hetkellä 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 ovat luoneet Nikolai Denissov, Olli Kiljunen ja Nikolas Drosdek yhteistyössä Juha Sorvan, Otto Seppälän, Arto Hellaksen ja muiden kanssa.
Kurssin tämänhetkinen henkilökunta löytyy luvusta 1.1.
Joidenkin lukujen lopuissa on lukukohtaisia lisäyksiä tähän tekijäluetteloon.