Animaatio

Takaisin pääsivulle

Ohjeita koodin kirjoittamiseen ja ongelmanratkaisuun

Yksi maailman johtavista ohjelmistonkehittäjistä Kent Beck on lausunut mm. seuraavasti:

Yritämme jatkossa toimia näiden ohjeiden mukaan. Aletaan ottamaan nyt ensimmäisiä askelia Kent Beckin viitoittamalla tiellä.

Hengittävä koodi

Koodia joka on kirjoitettu ilman välejä on ikävä lukea:

def main():
    t = [1, 2, 3, 4, 5]
    for luku in t:
        print luku
    print ""
    for i in range(len(t)):
        t[i] = t[i] + 1
    for luku in t:
        print luku
    print ""

main()

Erotellaan loogiset kokonaisuudet rivinvaihdoin:

def main():
    t = [1, 2, 3, 4, 5]

    for luku in t:
        print luku
    print ""

    for i in range(len(t)):
        t[i] = t[i] + 1

    for luku in t:
        print luku
    print ""

main()

Nyt koodissa alkaa olla jo järkeä. Esim. listan tulostus ja listan alkioiden arvojen kasvatus ovat omia loogisia kokonaisuuksia, joten ne on erotettu rivinvaihdolla. Koodissa on mukavaa ilmavuutta ja koneen lisäksi ihmisen alkaa jo olla miellyttävämpi lukea koodia.

Copy pasten eliminointi metodeilla

Ohjelmoijan lähes pahin mahdollinen perisynti on copy paste -koodi, eli samanlaisen koodinpätkän toistuminen koodissa useaan kertaan. Esimerkissämme listan tulostus tapahtuu kahteen kertaan. Tulostuksen hoitava koodi on syytä erottaa omaksi funktiokseen ja laittaa pääohjelma kutsumaan luotua funktiota.

def tulostaLista(lista):
    for luku in lista:
        print luku
    print ""

def main():
    t = [1, 2, 3, 4, 5]

    tulostaLista(t)

    for i in range(len(t)):
        t[i] = t[i] + 1

    tulostaLista(t)

main()

Erillisten tehtävien erottaminen omiksi selkeästi nimetyiksi funktioiksi

Nyt koodi alkaa olla jo selkeämpää. Selvästi erillinen kokonaisuus, eli listan tulostus on oma helposti ymmärrettävä funktionsa. myös itse ohjelman luettavuus on kasvanut. Huomaa myös, että funktio on nimetty mahdollisimman kuvaavasti, eli siten että funktion nimi sanoo sen mitä funktio tekee.

Ohjelmassa on kuitenkin vielä hiukan siistimisen varaa. Pääohjelma on vielä sikäli ikävä, että siistien funktiokutsujen seassa on vielä suoraan listaa käsittelevä "epäesteettinen" koodinpätkä. Erotetaan tämäkin omaksi funktiokseen:

def tulostaLista(lista):
    for luku in lista:
        print luku
    print ""

def kasvataListanAlkioitaYhdella(lista):
    for i in range(len(lista)):
        lista[i] = lista[i] + 1

def main():
    t = [1, 2, 3, 4, 5]

    tulostaLista(t)
    kasvataListanAlkioitaYhdella(t)
    tulostaLista(t)

main()

Eli vaikka copy pastea ei ollutkaan, loimme loogiselle kokonaisuudelle (listan arvojen kasvattaminen yhdellä) oman kuvaavasti nimetyn funktion. Lopputuloksena oleva pääohjelma on nyt erittäin ymmärrettävä, lähes suomen kieltä. Molemmat metodit ovat myös erittäin yksinkertaisia ja selkeitä ymmärtää.

Kuvaava muuttujien nimentä

Esimerkkikoodissamme on vielä hyvin epämääräinen muuttuja t. Korvataan se kuvaavammalla muuttujannimellä lista.

def tulostaLista(lista):
    for luku in lista:
        print luku
    print ""

def kasvataListanAlkioitaYhdella(lista):
    for i in range(len(lista)):
        lista[i] = lista[i] + 1

def main():
    luvut = [1, 2, 3, 4, 5]

    tulostaLista(luvut)
    kasvataListanAlkioitaYhdella(luvut)
    tulostaLista(luvut)

main()

Koodin yleistäminen

Abstrahoimalla eli yleistämällä koodia hiukan lisää voisimme muokata funktion kasvataListanAlkioitaYhdella ottamaan toisena parametrinaan kasvatusmäärän. Funktiot ja ohjelma näyttäisivät tällöin seuraavalta:

def tulostaLista(lista):
    for luku in lista:
        print luku
    print ""

def kasvataListanAlkioita(lista, maara):
    for i in range(len(lista)):
        lista[i] = lista[i] + maara

def main():
    luvut = [1, 2, 3, 4, 5]

    tulostaLista(luvut)
    kasvataListanAlkioita(luvut, 1)
    tulostaLista(luvut)

main()

Kent Beck olisi varmaan tyytyväinen aikaansaannokseemme, koodi on helposti ymmärrettävää, helposti muokattavaa eikä sisällä copy pastea.

Ongelman ratkaiseminen paloittain

Tarkastellaan seuraavaa tehtäväkuvausta:

Tee ohjelma, joka kysyy käyttäjältä sanoja, kunnes käyttäjä antaa saman sanan uudestaan. Voit olettaa, että käyttäjä antaa korkeintaan 1000 sanaa.

Anna sana: porkkana
Anna sana: selleri
Anna sana: nauris
Anna sana: lanttu
Anna sana: selleri

Annoit saman sanan uudestaan!

Useasti tehtävää ratkottaessa ohjelmoija ei oikein tiedä miten lähestyä tehtävää, eli miten jäsentää ongelma ja mistä aloittaa.

Ongelma koostuu oikeastaan kahdesta "aliongelmasta". Ensimmäinen on sanojen toistuva lukeminen käyttäjältä kunnes tietty ehto toteutuu. Tämä voitaisiin hahmotella seuraavaan tapaan ohjelmarungoksi:

def main()
    while True:
        sana = raw_input("Anna sana: ")

        if "pitää lopettaa":
            break

        print "Annoit saman sanan uudestaan"

main()

Sanojen kysely pitää lopettaa siinä vaiheessa kun on syötetty jokin jo aiemmin syötetty sana. Päätetään tehdä funktio, joka huomaa että sana on jo syötetty. Vielä ei tiedetä miten funktio kannattaisi tehdä, joten tehdään siitä vasta runko:

def onJoSyotetty(sana):
    return True

def main()
    while True:
        sana = raw_input("Anna sana: ")

        if onJoSyotetty(sana):
            break;

        print "Annoit saman sanan uudestaan"

main()

Ohjelmaa on hyvä testata koko ajan, joten tehdään funktiosta kokeiluversio:

def onJoSyotetty(sana):
    if sana == "loppu":
        return True

    return False


def main():
    while True:
        sana = raw_input("Anna sana: ")

        if onJoSyotetty(sana):
            break

        print "Annoit saman sanan uudestaan"

main()

Nyt jatkuu nyt niin kauan kunnes syötteenä on sana loppu:

Anna sana: porkkana
Anna sana: selleri
Anna sana: nauris
Anna sana: lanttu
Anna sana: loppu
Annoit saman sanan uudestaan!

Ohjelma ei siis toimi vielä kokonaisuudessaan, mutta ensimmäinen osaongelma eli ohjelman pysäyttäminen kun tietty ehto toteutuu on saatu toimimaan.

Toinen osaongelma on aiemmin syötettyjen sanojen muistaminen. Lista sopii tietysti mainiosti tähän tarkoitukseen.

Kun uusi sana syötetään, on se lisättävä syötettyjen sanojen joukkoon. Tämä tapahtuu kutsumalla lista-muuttujan append-funktiota.

def onJoSyotetty(sana):
    if sana == "loppu":
        return True

    return False


def main():
    sanat = [] # luodaan tyhjä lista

    while True:
        sana = raw_input("Anna sana: ")

        if onJoSyotetty(sana):
            break

        sanat.append(sana)

        print "Annoit saman sanan uudestaan"

main()

Jälleen kannattaa testata, että ohjelma toimii edelleen. Voi olla hyödyksi esim. lisätä ohjelman loppuun testitulostus, joka varmistaa että syötetyt sanat todella menivät listaan:

def onJoSyotetty(sana):
    if sana == "loppu":
        return True

    return False

def main():
    sanat = [] # luodaan tyhjä lista

    while True:
        sana = raw_input("Anna sana: ")

        if onJoSyotetty(sana):
            break

        sanat.append(sana)

        print "Annoit saman sanan uudestaan"
        print sanat # voimme tulostaa listan sisällön yksinkertaisella print-kutsulla

main()

Muokataan aiemmin tekemämme funktio onJoSyotetty tutkimaan onko kysytty sana jo syötettyjen joukossa, eli listassa:

def onJoSyotetty(sana, joSyotetyt):
    for syotetty in joSyotetyt:
        if sana == syotetty:
            return True

    return False

Yllä oleva funktio toimii toivotusti. Huomaamme kuitenkin että teemme ylimääräistä työtä. Listarakenteella on olemassa myös operaattori in, jonka avulla voimme tutkia onko alkio listassa.

def onJoSyotetty(sana, joSyotetyt):
    if sana in joSyotetyt:
        return True

    return False

Ohjelma vielä kokonaisuudessaan, muutetaan myös kohtaa jossa funktiota onJoSyotetty kutsutaan.

def onJoSyotetty(sana, joSyotetyt):
    if sana in joSyotetyt:
        return True

    return False


def main():
    sanat = [] # luodaan tyhjä lista

    while True:
        sana = raw_input("Anna sana: ");

        if onJoSyotetty(sana, sanat):
            break

        sanat.append(sana)

        print "Annoit saman sanan uudestaan"

main()

Nyt koodi on valmis ja alkaa olla kohtuullisen luettava.

Eli ohjelmoidessasi, seuraa aina näitä neuvoja:

Hyvät ja kokeneet ohjelmoijat noudattavat näitä käytänteitä sen takia että ohjelmointi olisi helpompaa, ja että ohjelmien lukeminen, ylläpitäminen ja muokkaaminen olisi helpompaa.

Takaisin pääsivulle