Osa 2

Ohjelmoinnin termejä

Emme vielä kurssin ensimmäisessä osassa kiinnittäneet kovin tarkasti huomiota ohjelmoinnin terminologiaan. Nyt on hyvä hetki tutustua joihinkin käsitteisiin.

Lause

Lause (engl. statement) tarkoittaa ohjelman osaa, joka suorittaa jonkin toiminnon. Usein lause viittaa yksittäiseen komentoon.

Esimerkiksi print("Moi!") on lause, joka tulostaa rivin tekstiä, ja luku = 2 on lause, joka asettaa muuttujalle arvon.

Lause voi olla myös monimutkaisempi, ja sen sisällä voi olla muita lauseita. Esimerkiksi seuraava ehtolause muodostuu kolmesta rivistä:

if nimi == "Anna":
    print("Moi!")
    luku = 2

Tässä tapauksessa ehtolauseen sisällä on kaksi lausetta.

Lohko

Lohko (engl. block) on joukko peräkkäin sijoitettuja lauseita, jotka ovat samalla tasolla ohjelman rakenteessa. Esimerkiksi ehtolauseessa lohkossa ovat lauseet, jotka suoritetaan ehdon ollessa tosi.

if ika > 17:
    # ehtolauseessa oleva lohko alkaa
    print("Olet täysi-ikäinen!")
    ika = ika + 1
    print("nyt vuoden vanhempi...")
    # lohko loppuu

print("tämä on eri lohkossa")

Pythonissa lohko ilmaistaan sisentämällä lohkon koodi eli lauseet samalle tasolle.

Kannattaa huomata, että Python-ohjelman "päälohkon" on oltava sisennetty tiedoston vasempaan reunaan:

# tämä ohjelma ei toimi sillä koodia ei ole sisennetty vasempaan reunaan
  print("hei maailma")
  print("huono ohjelma...")

Lauseke

Lauseke (engl. expression) on koodin osa, jolla on jokin tyyppi. Ohjelman suorituksen aikana lauseke saa arvon, jota voidaan käyttää ohjelmassa.

Tarkastellaan muutamaa esimerkkiä lausekkeista:

LausekeArvoTyyppiTyyppi Pythonissa
2 + 4 + 39kokonaislukuint
"abc" + "de""abcde"merkkijonostr
11 / 25.5liukulukufloat
2 * 5 > 9Truetotuusarvobool

Koska lausekkeella on arvo, voi sen sijoittaa muuttujaan:

# muuttuja x saa arvoksi lausekkeen 1 + 2 arvon
x = 1 + 2

Yksinkertaisesta lausekkeesta saa muodostettua monimutkaisempia lausekkeita esim. laskuoperaattorien avulla:

# muuttuja y saa arvoksi lausekkeen '3 kertaa x plus x toiseen' arvon
y = 3 * x + x**2

Funktio

Funktio (engl. function) suorittaa jonkin toiminnon. Funktiolla voi olla yksi tai useampi parametri (engl. parameter), jotka ilmaisevat, mitä funktion tulee tehdä tarkalleen.

Funktio suoritetaan, kun sitä kutsutaan eli koodissa on funktion nimi ja funktiolle annettavat parametrit suluissa. Esimerkiksi seuraava koodi kutsuu print-funktiota parametrilla "tämä on parametri":

print("tämä on parametri")

Myös käyttäjältä syötteitä lukeva input on funktio. Parametrina funktio saa käyttäjälle näytettävän viestin:

nimi = input("Kerro nimesi: ")

Tässä tapauksessa funktio palauttaa arvon, mikä tarkoittaa, että funktion kutsukohtaan ilmestyy arvo funktion suorituksen jälkeen. Funktion input palauttama arvo on käyttäjän syöttämä teksti merkkijonona. Funktion palauttama arvo sijoitetaan usein muuttujan arvoksi, jotta arvoa voidaan hyödyntää ohjelmassa.

Tyyppi

Tyyppi (engl. type) tarkoittaa, millainen jokin koodissa esiintyvä arvo on. Esimerkiksi seuraavassa koodissa muuttujan nimi tyyppi on merkkijono ja muuttujan tulos tyyppi on kokonaisluku:

nimi = "Anna"
tulos = 100

Funktio type kertoo annetun lausekkeen tyypin. Esimerkiksi:

print(type("Anna"))
print(type(100))
Esimerkkitulostus

<class 'str'> <class 'int'>

Syntaksi

Syntaksi (engl. syntax) määrittää, miten ohjelman koodi tulee kirjoittaa. Jokaisella ohjelmointikielellä on omanlainen syntaksinsa.

Esimerkiksi Python-kielen syntaksiin kuuluu, että if-lauseen aloitusrivin lopussa on kaksoispiste ja ehtoon kuuluva koodi on sisennetty:

if nimi == "Anna":
    print("Moi!")

Jos ohjelmointikielen syntaksia ei noudateta, seurauksena on virheilmoitus:

if nimi == "Anna"
    print("Moi!")
Esimerkkitulostus
  File "testi.py", line 1
    if nimi == "Anna"
                    ^
SyntaxError: invalid syntax

Debuggaaminen

Kun ohjelman syntaksi on kunnossa mutta ohjelma ei toimi halutulla tavalla, ohjelmassa on bugi.

Bugit ilmenevät eri tavoin. Jotkin bugit aiheuttavat suoritusaikaisen virheen. Esim. ohjelma

x = 10
y = 0
tulos = x / y

print(f"{x} jaettuna {y} on {tulos}")

aiheuttaa seuraavan virheen:

Esimerkkitulostus
ZeroDivisionError: integer division or modulo by zero on line 3

Ongelma on siis siinä, että nollalla jakaminen ei ole sallittua ja se keskeyttää ohjelman suorituksen.

Suoritusaikaiseen virheeseen johtavat bugit ovat usein helpohkoja korjata, sillä bugin aiheuttama rivi selviää virheilmoituksesta. Toki bugin varsinainen syy on usein muualla kuin virheilmoituksen aiheuttaneessa rivissä.

Joskus bugi taas ilmenee siten, että koodin tuottama tulos on virheellinen. Tälläisten bugien havaitseminen ja niiden syyn paikallistaminen voi olla haastavaa. Kurssin tehtävissä testit paljastavat usein juuri tämän kategorian bugeja. Ennen kuin ongelma päästään korjaamaan, on bugi paikallistettava.

Koodarijargonissa bugien syiden selvittämistä kutsutaan debuggaamiseksi. Debuggaaminen on äärimmäisen keskeinen taito, ja ammatikseen ohjelmoivat käyttävät usein enemmän aikaa debuggaamiseen kuin varsinaiseen ohjelmointiin.

Yksinkertainen mutta tehokas debuggauskeino on lisätä ohjelmaan debug-tulostuksia eli print-komentoja, joiden avulla varmistetaan, että koodissa tapahtuu ohjelmoijan olettamia asioita.

Seuraavassa on ratkaisuyritys yhteen edellisen osan tehtävään:

tuntipalkka = float(input("Tuntipalkka: "))
tunnit = int(input("Työtunnit: "))
paiva = input("Viikonpäivä: ")

palkka = tuntipalkka * tunnit
if paiva == "sunnnuntai":
    palkka * 2

print(f"Palkka {palkka} euroa")

Ohjelma ei näytä toimivan oikein ja testien suoritus kertoo seuraavaa:

Esimerkkitulostus
FAIL: PalkkaTest: test_sunnuntai_1

Syötteellä 23.0, 12, sunnuntai oikeaa palkkaa 552.0 ei löydy tulosteestasi Palkka 276.0 euroa

Ensimmäinen askel debuggaamisessa on useimmiten kokeilla ohjelmaa ongelmallisella syötteellä. Kokeilu varmistaa, että tulos ei ole haluttu:

Esimerkkitulostus

Palkka 276.0 euroa

Debugattaessa ohjelman toimintaa kokeillaan usein. Voikin olla hyödyllisä "kovakoodata" ongelman aiheuttavat syötteet suoraan koodiin sen sijaan, että ne kysyttäisiin joka kerta käyttäjältä. Tämä onnistuu esimerkiksi muuttamalla koodia tilapäisesti seuraavalla tavalla:

# tuntipalkka = float(input("Tuntipalkka: "))
# tunnit = int(input("Työtunnit: "))
# paiva = input("Viikonpäivä: ")
tuntipalkka = 23.0
tunnit = 12
paiva = "sunnuntai"

palkka = tuntipalkka * tunnit
if paiva == "sunnnuntai":
    palkka * 2

print(f"Palkka {palkka} euroa")

Seuraava askel on lisäillä koodiin debug-tulostuksia. Koska nimenomaan sunnuntain palkka lasketaan väärin, laitetaan sen hoitavaan osaan tulostukset korotusta ennen ja sen jälkeen:

# ...

palkka = tuntipalkka * tunnit
if paiva == "sunnnuntai":
    print("palkka alussa:", palkka)
    palkka * 2
    print("palkka kasvatuksen jälkeen:", palkka)

print(f"Palkka {palkka} euroa")

Kun ohjelma nyt suoritetaan, ei debug-tulostuksia jostain syystä näy ollenkaan. Vaikuttaa siltä, että ohjelman suoritus ei edes mene if-haaraan. Komennon ehdossa täytyy siis olla jokin ongelma. Ehdon arvokin voidaan tulostaa koodista:

# ...

palkka = tuntipalkka * tunnit
print("ehto:", paiva=="sunnnuntai")
if paiva == "sunnnuntai":
    print("palkka alussa:", palkka)
    palkka * 2
    print("palkka kasvatuksen jälkeen:", palkka)

print(f"Palkka {palkka} euroa")

Ja tosiaan kun koodi suoritetaan, ehdon arvo on False eli koodi hyppää if-lohkon ohi:

Esimerkkitulostus

ehto: False Palkka 276.0 euroa

Vian täytyy siis olla if-komennon ehdossa, ja kun sitä katsotaan tarkemmin, huomataan, että sunnuntai on vahingossa kirjoitettu väärin. Korjataan typo:

# ...

palkka = tuntipalkka * tunnit
print("ehto:", paiva=="sunnuntai")
if paiva == "sunnuntai":
    print("palkka alussa:", palkka)
    palkka * 2
    print("palkka kasvatuksen jälkeen:", palkka)

print(f"Palkka {palkka} euroa")

Koodin suoritus aiheuttaa nyt seuraavan tulostuksen:

Esimerkkitulostus

ehto: True palkka alussa: 276.0 palkka kasvatuksen jälkeen: 276.0 Palkka 276.0 euroa

Koska tuntipalkka = 23.0 ja tunnit = 12, vaikuttaa muuttujassa palkka olevan oikea arvo aluksi, mutta kasvatuskomento ei kuitenkaan kasvata muuttujan arvoa. Komento on siis mitä ilmeisemmin virheellinen. Ja toden totta, komento

palkka * 2

ainoastaan laskee tuplapalkan mutta ei tee tulokselle mitään. Korjataan komento muotoon, joka tallentaa korotetun palkan muuttujaan palkka:

palkka *= 2

Kun ohjelma suoritetaan nyt, huomataan että lopputuloskin on oikea:

Esimerkkitulostus

ehto: True palkka alussa: 276.0 palkka kasvatuksen jälkeen: 552.0 Palkka 552.0 euroa

Kun ohjelma on kunnossa, tulee debuggaustulosteet ja muu debuggauksen takia kirjoitettu ylimääräinen koodi poistaa.

Esimerkki oli yksinkertainen ja näin lyhyessä ohjelmassa oleva bugi selviäisi varmasti myös koodia lukemalla. Monesti kuitenkin debug-tulostuksilla pääsee huomattavasti nopeammin jyvälle siitä, missä vika piilee. Tulostamalla voidaan usein varmistua siitä, mitkä osat ohjelmasta toimivat "varmuudella" oikein, ja bugien jäljitys voidaan nopeasti saada kohdistettua niihin koodiriveihin, joissa ongelma todennäköisesti piileskelee.

Debuggaukseen on olemassa muitakin keinoja kuin debug-tulostusten tekeminen. Palaamme asiaan myöhemmin kurssilla. Sinun kannattaa tästä lähtien kurssilla käyttää debug-tulostamista virheiden etsimiseen. Ohjelmoinnin ammattilaiset eivät selviä työstään ilman debug-tulostuksia, joten on vaikea kuvitella, että aloittelijoidenkin ei kannattaisi laajentaa työkalupakkiaan tältä osin.

Loading...
:
Loading...

Log in to view the quiz