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ä.
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.
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()
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ää.
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()
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.
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.