Osaat kertoa proseduraalisen ohjelmoinnin ja olio-ohjelmoinnin eroista ja toteuttaa ohjelmia kumpaakin ohjelmointiparadigmaa noudattaen. Tiedät mitä käsite algoritmi tarkoittaa ja osaat kuvailla muutamien järjestämis- ja hakualgoritmien toimintaa. Osaat kirjoittaa yksikkötestejä ja tiedät testivetoisen ohjelmistokehityksen perusaskeleet.
Ohjelmointiparadigmoja
- Tunnet käsitteen ohjelmointiparadigma.
- Tiedät mitä proseduraalisella ohjelmoinnilla ja olio-ohjelmoinnilla tarkoitetaan.
Ohjelmointiparadigmalla tarkoitetaan ohjelmointikielen taustalla olevaa tapaa ajatella ja jäsentää ohjelman toimintaa. Ohjelmointiparadigmat eroavat toisistaan mm. siinä, miten ohjelman suorituksen eteneminen ja kontrolli määritellään sekä minkälaisista osista ohjelmat rakentuvat.
Suurin osa nykyään käytössä olevista ohjelmointikielistä tukee useampaa ohjelmointiparadigmaa. Ohjelmoijana kehittymiseen kuuluu kokemuksen kautta kehittyvä kyky sopivan ohjelmointikielen ja paradigman valintaan; yhtä kaikkialla toimivaa ohjelmointikieltä ja ohjelmointiparadigmaa ei toistaiseksi ole olemassa.
Tämän hetken yleisimpiä ohjelmointiparadigmoja ovat olio-ohjelmointi, proseduraalinen ohjelmointi sekä funktionaalinen ohjelmointi. Seuraavaksi näistä kahta ensimmäistä käsitellään lyhyesti.
Olio-ohjelmointi
Olio-ohjelmoinnissa käsiteltävä tieto esitetään luokkina, jotka kuvaavat ongelma-alueen käsitteitä sekä sovelluksen toimintalogiikkaa. Luokkiin määritellään metodit, jotka määräävät miten tietoa käsitellään. Ohjelman suorituksen aikana luokista luodaan olioita, jotka sisältävät ajonaikaisen tiedon, ja jotka myös vaikuttavat ohjelman suoritukseen: ohjelman suoritus etenee tyypillisesti olioihin liittyvien metodikutsujen kautta. Kuten joitakin viikkoja sitten totesimme, "ohjelma rakennetaan pienistä selkeistä yhteistoiminnassa olevista olioista".
Olio-ohjelmoinnin perusideat eli tiedon ja sen käsittelyyn liittyvien toimintojen esittäminen luokkien ja olioiden avulla esiintyivät ensimmäisiä kertoja simulaatioiden rakentamiseen tarkoitetussa Simula 67:ssä sekä Smalltalk-ohjelmointikielessä. Sen läpimurto tapahtui 1980-luvulla C++-ohjelmointikielen kautta ja siitä on muodostunut Java-ohjelmointikielen myötä yksi maailman eniten käytetty ohjelmointiparadigma.
Olio-ohjelmoinnin suurimpia etuja ovat ongelma-alueen käsitteiden mallintaminen luokkien ja olioiden kautta, mikä helpottaa ohjelman ymmärtämistä. Tämän lisäksi ongelma-alueen jäsentäminen luokiksi helpottaa ohjelmien rakentamista ja ylläpitoa. Olio-ohjelmointi ei kuitenkaan sovellu luontaisesti kaikkiin ongelmiin: esimerkiksi tieteellisessä laskennassa ja tilastotieteen sovelluksissa käytetään tyypillisemmin mm. R-kieltä.
Proseduraalinen ohjelmointi
Siinä missä olio-ohjelmoinnissa ohjelman rakenne muodostuu käsiteltävän tiedon kautta, proseduraalisessa ohjelmoinnissa ohjelman rakenne muodostuu ohjelmalta toivotun toiminnan kautta: ohjelma on askeleittainen ohje suoritettavalle toiminnalle. Ohjelmaa suoritetaa askel kerrallaan, tarvittaessa aliohjelmia (metodeja) kutsuen.
Proseduraalisessa ohjelmoinnissa ohjelman tilaa pidetään yllä muuttujissa ja taulukoissa, ja mahdolliset metodit käsittelevät vain niille parametrina annettuja arvoja. Ohjelmassa koneelle kerrotaan mitä pitäisi tapahtua. Esimerkiksi alla on muuttujien a ja b arvojen vaihtaminen.
int a = 10;
int b = 15;
// vaihdetaan muuttujien a ja b arvot
int c = b;
b = a;
a = c;
Kun olio-ohjelmointia verrataan proseduraaliseen ohjelmointiin, muutamat oleelliset erot tulevat ilmi. Olio-ohjelmoinnissa olion tila voi periaatteessa muuttua mitä tahansa olion metodia käytettäessä, ja tuo tilan muutos voi vaikuttaa myös muiden olion metodien toimintaan; tätä kautta muutos voi vaikuttaa myös muihin ohjelman suorituksen osa-alueisiin, sillä olioita voidaan käyttää ohjelmassa useammassa paikassa.
Konkreettisesti olio-ohjelmoinnin ja proseduraalisen ohjelmoinnin erot näkyvät viidennen osan alussa esitetyssä kello-esimerkissä. Alla on kuvattuna proseduraalista ohjelmointityyliä kuvastava ratkaisu, missä ajan tulostaminen on siiretty metodiin.
int tunnit = 0;
int minuutit = 0;
int sekunnit = 0;
while (true) {
// 1. tulostetaan aika
tulosta(tunnit, minuutit, sekunnit);
System.out.println();
// 2. Sekuntiviisarin eteneminen
sekunnit = sekunnit + 1;
// 3. Muiden viisarien eteneminen mikäli tarve
if (sekunnit > 59) {
minuutit = minuutit + 1;
sekunnit = 0;
if (minuutit > 59) {
tunnit = tunnit + 1;
minuutit = 0;
if (tunnit > 23) {
tunnit = 0;
}
}
}
}
public static void tulosta(int tunnit, int minuutit, int sekunnit) {
tulosta(tunnit);
tulosta(minuutit);
tulosta(sekunnit);
}
public static void tulosta(int luku) {
if (luku < 10) {
System.out.print("0");
}
System.out.print(luku);
}
Sama olio-ohjelmointia noudattaen:
public class Viisari {
private int arvo;
private int ylaraja;
public Viisari(int ylaraja) {
this.ylaraja = ylaraja;
this.arvo = 0;
}
public void etene() {
this.arvo = this.arvo + 1;
if (this.arvo >= this.ylaraja) {
this.arvo = 0;
}
}
public int arvo() {
return this.arvo;
}
public String toString() {
if (this.arvo < 10) {
return "0" + this.arvo;
}
return "" + this.arvo;
}
}
public class Kello() {
private Viisari tunnit;
private Viisari minuutit;
private Viisari sekunnit;
public Kello() {
this.tunnit = new Viisari(24);
this.minuutit = new Viisari(60);
this.tunnit = new Viisari(60);
}
public void etene() {
this.sekunnit.etene();
if (this.sekunnit.arvo() == 0) {
this.minuutit.etene();
if (this.minuutit.arvo() == 0) {
this.tunnit.etene();
}
}
}
public String toString() {
return tunnit + ":" + minuutit + ":" + sekunnit;
}
}
Kello kello = new Kello();
while (true) {
System.out.println(kello);
kello.etene();
}
Toteutetaan interaktiivinen ohjelma kahden nestesäiliön käsittelyyn. Säiliöt ovat nimeltään "ensimmäinen" ja "toinen". Kumpikin voi sisältää korkeintaan sata yksikköä nestettä.
Ohjelma tarjoaa toiminnallisuuden nesteen lisäämiseen, nesteen siirtämiseen, ja nesteen poistamiseen. Nesteen lisääminen lisää nestettä ensimmäiseen säiliöön, nesteen siirtäminen siirtää nestettä ensimmäisestä säiliöstä toiseen ja nesteen poistaminen poistaa nestettä toisesta säiliöstä.
Ohjelman komentojen tulee olla seuraavat:
-
lisaa maara
lisää ensimmäiseen säiliöön parametrina annetun määrän nestettä. Annettu määrä kuvataan kokonaislukuna. Säiliössä ei voi olla yli sataa yksikköä nestettä ja liialliset lisäykset menevät hukkaan. -
siirra maara
siirtää ensimmäisestä säiliöstä toiseen parametrina annetun määrän nestettä. Annettu määrä kuvataan kokonaislukuna. Säiliössä ei voi olla yli sataa yksikköä nestettä. Mikäli ohjelmassa yritetään siirtää enemmän kuin ensimmäisessä säiliössä on, siirretään ensimmäisen säiliön koko sisältö. Mikäli ohjelmassa yritetään siirtää enemmän kuin toiseen säiliöön mahtuu, valuu toiseen säiliöön mahtumaton osa hukkaan. -
poista maara
poistaa toisesta säiliöstä parametrina annetun määrän nestettä. Mikäli ohjelmaa pyydetään poistamaan enemmän kuin mitä säiliössä on, poistetaan säiliöstä vain säiliön sisältö.
Jokaisen komennon suorituksen jälkeen tulostetaan säiliöden sisältö. Negatiivisia määriä ei tule ottaa huomioon.
Toteuta ohjelma proseduraalista ohjelmointityyliä noudattaen ilman omia luokkia. Kaikki toiminnallisuus tulee lisätä luokassa Nestesailiot
olevaan metodiin main
(älä siis tee omia metodeja). Tehtäväpohjassa on valmiina toistolause, mistä poistutaan kun käyttäjä kirjoittaa "lopeta".
Alla on muistutuksena merkkijonon pilkkominen osiin.
String luettu = lukija.nextLine();
String[] osat = luettu.split(" ");
String komento = osat[0];
int maara = Integer.valueOf(osat[1]);
Lisääminen
Toteuta toiminnallisuus nesteen lisäämiseen ensimmäiseen säiliöön. Ohjelman toiminnan tulee olla seuraavanlainen:
Ensimmäinen: 0/100 Toinen: 0/100 > lisaa 5 Ensimmäinen: 5/100 Toinen: 0/100 > lisaa 25 Ensimmäinen: 30/100 Toinen: 0/100 > lisaa 60 Ensimmäinen: 90/100 Toinen: 0/100 > lisaa 1000 Ensimmäinen: 100/100 Toinen: 0/100 > lisaa -5 Ensimmäinen: 100/100 Toinen: 0/100 > lopeta
Siirtäminen
Toteuta toiminnallisuus nesteen siirtämiseen ensimmäisestä säiliöstä toiseen. Ohjelman toiminnan tulee olla seuraavanlainen:
Ensimmäinen: 0/100 Toinen: 0/100 > lisaa 1000 Ensimmäinen: 100/100 Toinen: 0/100 > siirra 50 Ensimmäinen: 50/100 Toinen: 50/100 > lisaa 100 Ensimmäinen: 100/100 Toinen: 50/100 > siirra 100 Ensimmäinen: 0/100 Toinen: 100/100 > lopeta
Toinen esimerkki:
Ensimmäinen: 0/100 Toinen: 0/100 > siirra 30 Ensimmäinen: 0/100 Toinen: 0/100 > lisaa 10 Ensimmäinen: 10/100 Toinen: 0/100 > siirra -5 Ensimmäinen: 10/100 Toinen: 0/100 > siirra 20 Ensimmäinen: 0/100 Toinen: 10/100 > siirra 10 Ensimmäinen: 0/100 Toinen: 10/100 > lopeta
Poistaminen
Toteuta toiminnallisuus nesteen poistamiseen toisesta säiliöstä. Ohjelman toiminnan tulee olla seuraavanlainen:
Ensimmäinen: 0/100 Toinen: 0/100 > poista 10 Ensimmäinen: 0/100 Toinen: 0/100 > lisaa 20 Ensimmäinen: 20/100 Toinen: 0/100 > poista 5 Ensimmäinen: 20/100 Toinen: 0/100 > siirra 15 Ensimmäinen: 5/100 Toinen: 15/100 > poista 5 Ensimmäinen: 5/100 Toinen: 10/100 > poista 20 Ensimmäinen: 5/100 Toinen: 0/100 > lopeta
Tehtävään on olemassa esimerkkiratkaisu Test My Code -järjestelmässä. Esimerkkiratkaisua voi käyttää oman oppimisen tukena, ja se on tarkasteltavissa jo ennen kuin olet saanut tehtävän valmiiksi. Esimerkkiratkaisuun pääset käsiksi täältä. Huomaathan, että tehtävän voi ratkaista monella tapaa, ja tässä annettu esimerkkiratkaisu on näistä vain yksi.
Toteutetaan edellä kuvattu interaktiivinen ohjelma kahden nestesäiliön käsittelyyn uudestaan. Tällä kertaa luodaan ohjelman toteutusta varten luokka "Sailio", jonka vastuulla on säiliön sisällön ylläpito.
Sailio
Toteuta luokka Sailio. Säiliöllä tulee olla parametriton konstruktori sekä seuraavat metodit:
-
public int sisalto()
palauttaa säiliössä olevan nesteen määrän kokonaislukuna. -
public void lisaa(int maara)
lisää parametrina annetun määrän nestettä säiliöön. Mikäli parametrin arvo on negatiivinen, ei nestettä lisätä. Lisäyksen jälkeen säiliössä on korkeintaan 100 yksikköä nestettä. -
public void poista(int maara)
poistaa parametrina annetun määrän nestettä säiliöstä. Mikäli parametrin arvo on negatiivinen, ei nestettä poisteta. Poistaminen poistaa vain olemassaolevaa nestettä -- poiston takia säiliössä ei voi koskaan olla alle nollaa nesteyksikköä. -
public String toString()
palauttaa olion merkkijonoesityksen muodossa "sisalto/100".
Luokan käyttöesimerkki:
Sailio sailio = new Sailio();
System.out.println(sailio);
sailio.lisaa(50);
System.out.println(sailio);
System.out.println(sailio.sisalto());
sailio.poista(60);
System.out.println(sailio);
sailio.lisaa(200);
System.out.println(sailio);
0/100 50/100 50 0/100 100/100
Toiminnallisuus
Kopioi ensimmäisessä osassa toteuttamasi käyttöliittymä ja muokkaa sitä siten, että ohjelmassa käytetään juuri toteuttamiasi säiliöitä. Luokassa NestesailiotOlioilla
olevan main-metodin suorituksen tulee käynnistää ohjelma.
Alla on esimerkkitulostus. Ohjelman tekstikäyttöliittymän toiminnan tulee olla seuraavanlainen:
Ensimmäinen: 0/100 Toinen: 0/100 > poista 10 Ensimmäinen: 0/100 Toinen: 0/100 > lisaa 20 Ensimmäinen: 20/100 Toinen: 0/100 > poista 5 Ensimmäinen: 20/100 Toinen: 0/100 > siirra 15 Ensimmäinen: 5/100 Toinen: 15/100 > poista 5 Ensimmäinen: 5/100 Toinen: 10/100 > poista 20 Ensimmäinen: 5/100 Toinen: 0/100 > lopeta
Tehtävään on olemassa esimerkkiratkaisu Test My Code -järjestelmässä. Esimerkkiratkaisua voi käyttää oman oppimisen tukena, ja se on tarkasteltavissa jo ennen kuin olet saanut tehtävän valmiiksi. Esimerkkiratkaisuun pääset käsiksi täältä. Huomaathan, että tehtävän voi ratkaista monella tapaa, ja tässä annettu esimerkkiratkaisu on näistä vain yksi.
Kerrataan seuraavaksi olio-ohjelmointia laajemman olioita olioiden sisällä käsittelevän tehtävän kautta.
Tässä tehtäväsarjassa tehdään luokat Tavara
, Matkalaukku
ja Lastiruuma
, joiden avulla harjoitellaan lisää olioita, jotka sisältävät toisia olioita.
Tavara-luokka
Tee luokka Tavara
, josta muodostetut oliot vastaavat erilaisia tavaroita. Tallennettavat tiedot ovat tavaran nimi ja paino (kg).
Lisää luokkaan seuraavat metodit:
- Konstruktori, jolle annetaan parametrina tavaran nimi ja paino
- Metodi
public String getNimi()
, joka palauttaa tavaran nimen - Metodi
public int getPaino()
, joka palauttaa tavaran painon - Metodi
public String toString()
, joka palauttaa merkkijonon muotoa "nimi (paino kg)"
Seuraavassa on luokan käyttöesimerkki:
public class Main {
public static void main(String[] args) {
Tavara kirja = new Tavara("Aapiskukko", 2);
Tavara puhelin = new Tavara("Nokia 3210", 1);
System.out.println("Kirjan nimi: " + kirja.getNimi());
System.out.println("Kirjan paino: " + kirja.getPaino());
System.out.println("Kirja: " + kirja);
System.out.println("Puhelin: " + puhelin);
}
}
Ohjelman tulostuksen tulisi olla seuraava:
Kirjan nimi: Aapiskukko Kirjan paino: 2 Kirja: Aapiskukko (2 kg) Puhelin: Nokia 3210 (1 kg)
Matkalaukku-luokka
Tee luokka Matkalaukku
. Matkalaukkuun liittyy tavaroita ja maksimipaino, joka määrittelee tavaroiden suurimman mahdollisen yhteispainon.
Lisää luokkaan seuraavat metodit:
- Konstruktori, jolle annetaan maksimipaino
- Metodi
public void lisaaTavara(Tavara tavara)
, joka lisää parametrina annettavan tavaran matkalaukkuun. Metodi ei palauta mitään arvoa. - Metodi
public String toString()
, joka palauttaa merkkijonon muotoa "x tavaraa (y kg)"
Tavarat kannattaa tallentaa ArrayList
-olioon:
ArrayList<Tavara> tavarat = new ArrayList<>();
Luokan Matkalaukku
tulee valvoa, että sen sisältämien tavaroiden yhteispaino ei ylitä maksimipainoa. Jos maksimipaino ylittyisi lisättävän tavaran vuoksi, metodi lisaaTavara
ei saa lisätä uutta tavaraa laukkuun.
Seuraavassa on luokan käyttöesimerkki:
public class Main {
public static void main(String[] args) {
Tavara kirja = new Tavara("Aapiskukko", 2);
Tavara puhelin = new Tavara("Nokia 3210", 1);
Tavara tiiliskivi = new Tavara("tiiliskivi", 4);
Matkalaukku matkalaukku = new Matkalaukku(5);
System.out.println(matkalaukku);
matkalaukku.lisaaTavara(kirja);
System.out.println(matkalaukku);
matkalaukku.lisaaTavara(puhelin);
System.out.println(matkalaukku);
matkalaukku.lisaaTavara(tiiliskivi);
System.out.println(matkalaukku);
}
}
Ohjelman tulostuksen tulisi olla seuraava:
0 tavaraa (0 kg) 1 tavaraa (2 kg) 2 tavaraa (3 kg) 2 tavaraa (3 kg)
Kielenhuoltoa
Ilmoitukset "0 tavaraa" ja "1 tavaraa" eivät ole kovin hyvää suomea – paremmat muodot olisivat "ei tavaroita" ja "1 tavara". Tee tämä muutos luokassa Matkalaukku
sijaitsevaan toString-metodiin.
Nyt edellisen ohjelman tulostuksen tulisi olla seuraava:
ei tavaroita (0 kg) 1 tavara (2 kg) 2 tavaraa (3 kg) 2 tavaraa (3 kg)
Kaikki tavarat
Lisää luokkaan Matkalaukku
seuraavat metodit:
- metodi
tulostaTavarat
, joka tulostaa kaikki matkalaukussa olevat tavarat - metodi
yhteispaino
, joka palauttaa tavaroiden yhteispainon
Seuraavassa on luokan käyttöesimerkki:
public class Main {
public static void main(String[] args) {
Tavara kirja = new Tavara("Aapiskukko", 2);
Tavara puhelin = new Tavara("Nokia 3210", 1);
Tavara tiiliskivi = new Tavara("tiiliskivi", 4);
Matkalaukku matkalaukku = new Matkalaukku(10);
matkalaukku.lisaaTavara(kirja);
matkalaukku.lisaaTavara(puhelin);
matkalaukku.lisaaTavara(tiiliskivi);
System.out.println("Matkalaukussa on seuraavat tavarat:");
matkalaukku.tulostaTavarat();
System.out.println("Yhteispaino: " + matkalaukku.yhteispaino() + " kg");
}
}
Ohjelman tulostuksen tulisi olla seuraava:
Matkalaukussa on seuraavat tavarat: Aapiskukko (2 kg) Nokia 3210 (1 kg) Tiiliskivi (4 kg) Yhteispaino: 7 kg
Muokkaa myös luokkaasi siten, että käytät vain kahta oliomuuttujaa. Toinen sisältää maksimipainon, toinen on lista laukussa olevista tavaroista.
Raskain tavara
Lisää vielä luokkaan Matkalaukku
metodi raskainTavara
, joka palauttaa painoltaan suurimman tavaran. Jos yhtä raskaita tavaroita on useita, metodi voi palauttaa minkä tahansa niistä. Metodin tulee palauttaa olioviite. Jos laukku on tyhjä, palauta arvo null.
Seuraavassa on luokan käyttöesimerkki:
public class Main {
public static void main(String[] args) {
Tavara kirja = new Tavara("Aapiskukko", 2);
Tavara puhelin = new Tavara("Nokia 3210", 1);
Tavara tiiliskivi = new Tavara("Tiiliskivi", 4);
Matkalaukku matkalaukku = new Matkalaukku(10);
matkalaukku.lisaaTavara(kirja);
matkalaukku.lisaaTavara(puhelin);
matkalaukku.lisaaTavara(tiiliskivi);
Tavara raskain = matkalaukku.raskainTavara();
System.out.println("Raskain tavara: " + raskain);
}
}
Ohjelman tulostuksen tulisi olla seuraava:
Raskain tavara: Tiiliskivi (4 kg)
Lastiruuma-luokka
Tee luokka Lastiruuma
, johon liittyvät seuraavat metodit:
- konstruktori, jolle annetaan maksimipaino
- metodi
public void lisaaMatkalaukku(Matkalaukku laukku)
, joka lisää parametrina annetun matkalaukun lastiruumaan - metodi
public String toString()
, joka palauttaa merkkijonon muotoa "x matkalaukkua (y kg)"
Tallenna matkalaukut sopivaan ArrayList
-rakenteeseen.
Luokan Lastiruuma
tulee valvoa, että sen sisältämien matkalaukkujen yhteispaino ei ylitä maksimipainoa. Jos maksimipaino ylittyisi uuden matkalaukun vuoksi, metodi lisaaMatkalaukku
ei saa lisätä uutta matkalaukkua.
Seuraavassa on luokan käyttöesimerkki:
public class Main {
public static void main(String[] args) {
Tavara kirja = new Tavara("Aapiskukko", 2);
Tavara puhelin = new Tavara("Nokia 3210", 1);
Tavara tiiliskivi = new Tavara("tiiliskivi", 4);
Matkalaukku adanLaukku = new Matkalaukku(10);
adanLaukku.lisaaTavara(kirja);
adanLaukku.lisaaTavara(puhelin);
Matkalaukku pekanLaukku = new Matkalaukku(10);
pekanLaukku.lisaaTavara(tiiliskivi);
Lastiruuma lastiruuma = new Lastiruuma(1000);
lastiruuma.lisaaMatkalaukku(adanLaukku);
lastiruuma.lisaaMatkalaukku(pekanLaukku);
System.out.println(lastiruuma);
}
}
Ohjelman tulostuksen tulisi olla seuraava:
2 matkalaukkua (7 kg)
Lastiruuman sisältö
Lisää luokkaan Lastiruuma
metodi public void tulostaTavarat()
, joka tulostaa kaikki lastiruuman matkalaukuissa olevat tavarat.
Seuraavassa on luokan käyttöesimerkki:
public class Main {
public static void main(String[] args) {
Tavara kirja = new Tavara("Aapiskukko", 2);
Tavara puhelin = new Tavara("Nokia 3210", 1);
Tavara tiiliskivi = new Tavara("tiiliskivi", 4);
Matkalaukku adanLaukku = new Matkalaukku(10);
adanLaukku.lisaaTavara(kirja);
adanLaukku.lisaaTavara(puhelin);
Matkalaukku pekanLaukku = new Matkalaukku(10);
pekanLaukku.lisaaTavara(tiiliskivi);
Lastiruuma lastiruuma = new Lastiruuma(1000);
lastiruuma.lisaaMatkalaukku(adanLaukku);
lastiruuma.lisaaMatkalaukku(pekanLaukku);
System.out.println("Ruuman matkalaukuissa on seuraavat tavarat:");
lastiruuma.tulostaTavarat();
}
}
Ohjelman tulostuksen tulisi olla seuraava:
Ruuman matkalaukuissa on seuraavat tavarat: Aapiskukko (2 kg) Nokia 3210 (1 kg) tiiliskivi (4 kg)
Tehtävään on olemassa esimerkkiratkaisu Test My Code -järjestelmässä. Esimerkkiratkaisua voi käyttää oman oppimisen tukena, ja se on tarkasteltavissa jo ennen kuin olet saanut tehtävän valmiiksi. Esimerkkiratkaisuun pääset käsiksi täältä. Huomaathan, että tehtävän voi ratkaista monella tapaa, ja tässä annettu esimerkkiratkaisu on näistä vain yksi.
Algoritmiikkaa
- Tiedät mitä käsite algoritmi tarkoittaa ja tunnet muutamia algoritmeja.
- Osaat kertoa miten valintajärjestämisalgoritmi toimii.
- Osaat kertoa miten peräkkäishaku- ja binäärihakualgoritmi toimii.
Ohjelman tehokas toiminta eli esimerkiksi tiedon nopea hakeminen ja näyttäminen on oleellinen osa ohjelmistojen käytettävyyttä. Mikäli ohjelman käyttäjä joutuu odottamaan kymmeniä sekunteja kun ohjelma etsii käyttäjän haluamaa tietoa, saattaa ohjelman käyttäjä lopettaa ohjelman käyttämisen kokonaan. Vastaavasti televisio-ohjelmistoja selaava käyttäjä ei hyödy televisio-ohjelman tiedoista mitään jos tiedot latautuvat vasta ohjelman katsomisen jälkeen.
Laajemmin voidaan ajatella, että nopeasti tapahtuva tiedon hakeminen ja näyttäminen on oleellista oikeastaan lähes missä tahansa sovelluksessa. Tutustutaan seuraavaksi tiedon hakemiseen ja järjestämiseen liittyviin algoritmeihin. Vaikka esimerkit käyttävät taulukoita, algoritmit toimivat myös muilla tiedon tallentamiseen tarkoitetuilla tietorakenteilla kuten listoilla.
Sana algoritmi juontaa juurensa Muhammad ibn Musa al-Khwarizmi -nimiseen henkilöön. Mistä tässä oikein on kyse?
Ensimmäiset korkeakulttuurit syntyivät (laajemman) lähi-idän alueelle, mikä nopeutti siellä myös henkistä kasvua. Lähi-idässä oltiin merkittävästi muuta maailmaa edellä muunmuassa matematiikassa ja tähtitieteessä -- esimerkiksi Euroopassa 1500-luvulla tapahtunut murros tähtitieteessä (maa kiertääkin aurinkoa eikä toisin päin), tapahtui laajemman lähi-idän vaikutuspiirissä olleessa kreikassa jo noin 300 vuotta ennen ajanlaskumme alkua.
Nimi al-Khwarizmi viittaa oikeastaan alueeseen, tai hieman laajemmin, etuosa al- viittaa usein henkilön synty- tai kotipaikkaan. Muhammad ibn Musa al-Khwarizmi -- tai hänen isänsä tai esi-isänsä -- tulivat keskiaasiasta alueelta, joka tunnetaan nykyään suomen kielessä nimellä Harezm. Nykyään käytetty termi algoritmi onkin hatunnosto sekä Muhammad ibn Musa al-Khwarizmille että hänen syntyperälleen.
Merkittävä osa al-Khwarizmin työstä tapahtui Baghdadissa sijaitsevassa Viisauden talossa, joka paikallisen hallinnon tukemana keräsi tiedemiehiä eri puolilta maailmaa yhteen. Tavoitteena oli "pienimuotoisesti" kerätä kaikki maailman tieto yhteen paikkaan ja kääntää se arabian kielelle, jota sitten jaettiin eteenpäin. Tätä kautta tietoa valui myös eurooppaan: esimerkiksi al-Khwarizmin kirja intialaisilla numeroilla laskemisesta (latinaksi "Algoritmi de numero Indorum") toi arabialaisten numeroiden käytön eurooppaan.
Tämä terminologia näkyy yhä esimerkikiksi espanjan kielessä. Espanjankielinen sana guarismo -- eli suomeksi luku -- tulee ilmeisesti juurikin al-Khwarizmin nimestä.
Vaikka Muhammad ibn Musa al-Khwarizmi kytketään nykyään -- ainakin tietojenkäsittelytieteilijöiden parissa -- ensisijaisesti algoritmeihin, on hän ennen kaikkea vaikuttanut merkittävästi algebran kehitykseen. Hänen työnsä tuolla alueella kontribuoi mm. ensimmäisen ja toisen asteen yhtälöiden ratkaisemiseen. Työn keskiössä olivat konkreettiset esimerkit sekä selkokieliset askeleittaiset ratkaisut -- numeroita työssä ei esiintynyt.
Tiedon järjestäminen
Jos tieto ei noudata minkäänlaista järjestystä tai sääntöä, on tiedon hakeminen tyypillisesti työlästä. Tarvitsemme siis järjestystä!
Valintajärjestäminen
Ohjelmoijan yleissivistykseen kuuluu ainakin yhden järjestämisalgoritmin (eli tavan järjestää taulukko) tuntemus. Tutustutaan erääseen "klassiseen" järjestämisalgoritmiin, valintajärjestämiseen. Tutustuminen tapahtuu harjoitustehtävien avulla.
Pienimmän arvon etsiminen
Tee luokkaan Paaohjelma
luokkametodi pienin
, joka palauttaa metodille parametrina annetun kokonaislukutaulukon pienimmän luvun.
Metodin runko on seuraava:
public static int pienin(int[] taulukko) {
// kirjoita koodia tähän
}
Seuraava esimerkki esittelee metodin toimintaa:
int[] luvut = {6, 5, 8, 7, 11};
System.out.println("Pienin: " + Paaohjelma.pienin(luvut));
Pienin: 5
Pienimmän arvon indeksi
Tee luokkaan Paaohjelma luokkametodi pienimmanIndeksi
, joka palauttaa sille parametrina annetun taulukon pienimmän luvun indeksin.
Metodin runko on seuraava:
public static int pienimmanIndeksi(int[] taulukko) {
// kirjoita koodia tähän
}
Seuraava koodi esittelee metodin toimintaa:
// indeksit: 0 1 2 3 4
int[] luvut = {6, 5, 8, 7, 11};
System.out.println("Pienimmän indeksi: " + new Paaohjelma.pienimmanIndeksi(luvut));
Pienimmän indeksi: 1
Taulukon pienin luku on 5, ja sen indeksi eli sijaintipaikka taulukossa on 1. Muistathan, että taulukon numerointi alkaa 0:sta.
Pienimmän arvon indeksi taulukon loppuosassa
Tee luokkaan Paaohjelma luokkametodi pienimmanIndeksiAlkaen
, joka toimii samalla tavalla kuin edellisen tehtävän metodi, mutta ottaa huomioon vain taulukon loppuosan jostain indeksistä alkaen. Metodille annetaan parametrina taulukon lisäksi aloitusindeksi, josta lähtien pienintä lukua etsitään.
Metodin runko on seuraava:
public static int pienimmanIndeksiAlkaen(int[] taulukko, int aloitusIndeksi) {
// kirjoita koodia tähän
}
Seuraava koodi esittelee metodin toimintaa:
// indeksit: 0 1 2 3 4
int[] luvut = {-1, 6, 9, 8, 12};
System.out.println(Paaohjelma.pienimmanIndeksiAlkaen(luvut, 0));
System.out.println(Paaohjelma.pienimmanIndeksiAlkaen(luvut, 1));
System.out.println(Paaohjelma.pienimmanIndeksiAlkaen(luvut, 2));
0 1 3
Esimerkissä ensimmäinen metodikutsu etsii pienimmän luvun indeksin aloittaen indeksistä 0. Indeksistä 0 alkaen pienin luku on -1, ja sen indeksi on 0. Toinen metodikutsu etsii pienimmän luvun indeksiä indeksistä 1 aloittaen. Tällöin pienin luku on 6, ja sen indeksi on 1. Kolmas kutsu etsii pienimmän luvun indeksiä aloittaen indeksistä 2. Indeksistä 2 alkaen pienin luku on 8, ja sen indeksi on 3.
Lukujen vaihtaminen
Tee luokkaan Paaohjelma luokkametodi vaihda
, jolle annetaan taulukko ja kaksi sen indeksiä. Metodi vaihtaa indekseissä olevat luvut keskenään.
Metodin runko on seuraava:
public static void vaihda(int[] taulukko, int indeksi1, int indeksi2) {
// kirjoita koodia tähän
}
Seuraavassa estellään metodin toimintaa. Taulukon tulostamisessa käytetään apuna taulukon merkkijonoksi muotoilevaa Arrays.toString-metodia:
int[] luvut = {3, 2, 5, 4, 8};
System.out.println(Arrays.toString(luvut));
Paaohjelma.vaihda(luvut, 1, 0);
System.out.println(Arrays.toString(luvut));
Paaohjelma.vaihda(luvut, 0, 3);
System.out.println(Arrays.toString(luvut));
[3, 2, 5, 4, 8] [2, 3, 5, 4, 8] [4, 3, 5, 2, 8]
Järjestäminen
Nyt koossa on joukko hyödyllisiä metodeja, joiden avulla voimme toteuttaa järjestämisalgoritmin nimeltä valintajärjestäminen.
Valintajärjestämisen idea on seuraava:
- Siirretään taulukon pienin luku indeksiin 0.
- Siirretään taulukon toiseksi pienin luku indeksiin 1.
- Siirretään taulukon kolmanneksi pienin luku indeksiin 2.
- Jne.
Toisin sanoen:
- Tarkastellaan taulukkoa indeksistä 0 alkaen. Vaihdetaan keskenään indeksissä 0 oleva luku sekä taulukon pienin luku indeksistä 0 alkaen.
- Tarkastellaan taulukkoa indeksistä 1 alkaen. Vaihdetaan keskenään indeksissä 1 oleva luku sekä taulukon pienin luku indeksistä 1 alkaen.
- Tarkastellaan taulukkoa indeksistä 2 alkaen. Vaihdetaan keskenään indeksissä 2 oleva luku sekä taulukon pienin luku indeksistä 2 alkaen.
- Jne.
Toteuta luokkaan Paaohjelma luokkametodi jarjesta
, joka perustuu yllä olevaan ideaan. Metodissa on syytä olla silmukka, joka käy läpi taulukon indeksejä. Metodeista pieninIndeksiAlkaen
ja vaihda
on varmasti hyötyä. Tulosta myös taulukon sisältö ennen järjestämistä ja jokaisen kierroksen jälkeen, jotta voit varmistaa algoritmin toimivan oikein.
Metodin runko on seuraava:
public static void jarjesta(int[] taulukko) {
}
Testaa metodin toimintaa ainakin seuraavalla esimerkillä:
int[] luvut = {8, 3, 7, 9, 1, 2, 4};
Paaohjelma.jarjesta(luvut);
Ohjelman tulosteen tulisi olla seuraavanlainen. Huomaa että sinun tulee tulostaa taulukon sisältö jokaisen vaihtamisen jälkeen!
[8, 3, 7, 9, 1, 2, 4] [1, 3, 7, 9, 8, 2, 4] [1, 2, 7, 9, 8, 3, 4] [1, 2, 3, 9, 8, 7, 4] [1, 2, 3, 4, 8, 7, 9] [1, 2, 3, 4, 7, 8, 9] [1, 2, 3, 4, 7, 8, 9]
Huomaat, miten taulukko tulee pikkuhiljaa järjestykseen alkaen alusta ja edeten loppua kohti.
Tehtävään on olemassa esimerkkiratkaisu Test My Code -järjestelmässä. Esimerkkiratkaisua voi käyttää oman oppimisen tukena, ja se on tarkasteltavissa jo ennen kuin olet saanut tehtävän valmiiksi. Esimerkkiratkaisuun pääset käsiksi täältä. Huomaathan, että tehtävän voi ratkaista monella tapaa, ja tässä annettu esimerkkiratkaisu on näistä vain yksi.
Javan valmiit järjestämisalgoritmit
Java tarjoaa merkittävän määrän valmiita järjestysalgoritmeja. Taulukot voi järjestää (luonnolliseen järjestykseen) luokan Arrays tarjoamalla metodilla sort, ja listat voi järjestää (luonnolliseen järjestykseen) luokan Collections tarjoamalla metodilla sort.
int[] luvut = {8, 3, 7, 9, 1, 2, 4};
System.out.println(Arrays.toString(luvut));
Arrays.sort(luvut);
System.out.println(Arrays.toString(luvut));
[8, 3, 7, 9, 1, 2, 4] [1, 2, 3, 4, 7, 8, 9]
ArrayList<Integer> luvut = new ArrayList<>();
luvut.add(8);
luvut.add(3);
luvut.add(7);
System.out.println(luvut);
Collections.sort(luvut);
System.out.println(luvut);
[8, 3, 7] [3, 7, 8]
Valmiit järjestämisalgoritmit toimivat sekä alkeistyyppisille muuttujille, että joillekin Javan valmiille viittaustyyppisille muuttujille kuten String. Omien luokkiemme järjestämistä varten joudumme antamaan Javalle hieman lisävinkkejä, sillä luokat eivät sisällä tietoa siitä, miten niistä luodut oliot pitäisi järjestää. Palaamme omista luokista tehtyjen olioiden järjestämiseen ohjelmoinnin jatkokurssilla.
Lisää luokkaan Paaohjelma
seuraavat luokkametodit:
-
public static void jarjesta(int[] taulukko)
järjestää kokonaislukutaulukon. -
public static void jarjesta(String[] taulukko)
järjestää merkkijonotaulukon. -
public static void jarjestaLuvut(ArrayList<Integer> luvut)
järjestää lukuja sisältävän listan. -
public static void jarjestaMerkkijonot(ArrayList<String> luvut)
järjestää merkkijonoja sisältävän listan.
Hyödynnä metodien toteutuksessa Javan valmiita kirjastoja.
Tehtävään on olemassa esimerkkiratkaisu Test My Code -järjestelmässä. Esimerkkiratkaisua voi käyttää oman oppimisen tukena, ja se on tarkasteltavissa jo ennen kuin olet saanut tehtävän valmiiksi. Esimerkkiratkaisuun pääset käsiksi täältä. Huomaathan, että tehtävän voi ratkaista monella tapaa, ja tässä annettu esimerkkiratkaisu on näistä vain yksi.
Tiedon hakeminen
Peräkkäishaku
Peräkkäishaku on hakualgoritmi, joka etsii tietoa taulukosta käymällä taulukkoa läpi alkio alkiolta. Heti kun haettu alkio löytyy, sen indeksi palautetaan. Jos alkiota ei löydy, palautetaan tieto siitä ettei haettavaa alkiota löytynyt -- tämä kerrotaan tyypillisesti palauttamalla indeksin sijaan arvo -1
.
public class Algoritmit {
public static int perakkaishaku(int[] taulukko, int haettava) {
for (int i = 0; i < taulukko.length; i++) {
if (taulukko[i] == haettava) {
return i;
}
}
return -1;
}
}
Pahimmassa tapauksessa, eli tilanteessa missä alkiota ei lödy, algoritmi tekee taulukon koon verran vertailuja. Vaikkapa 10 miljoonaa arvoa sisältävässä taulukossa tämä tarkoittaa kymmentä miljoonaa vertailua. Jos tietoa haetaan useampia kertoja, kannattaa tehokkuutta yrittää parantaa.
Binäärihaku (puolitushaku)
Kun tieto on järjestyksessä, hakeminen voidaan toteuttaa paljon peräkkäishakua tehokkaammin. Binäärihakualgoritmin idea aloittaa tiedon etsiminen taulukon (tai listan) keskimmäisestä indeksistä, verrata indeksissä olevaa arvoa haettuun arvoon, ja rajata tarvittaessa (eli mikäli haettavaa arvoa ei ole indeksissä) puolet tarkasteltavasta alueesta pois. Algoritmi esitellään seuraavassa slideshowssa.
Peräkkäishaun pahimmassa tapauksessa -- eli kun haettavaa ei löydy -- käydään kaikki taulukon arvot läpi. Miljoona alkiota sisältävässä taulukossa tämä tarkoittaa miljoonan alkion tarkastelua.
Binäärihaun pahimmassa tapauksessa tutkittava alue jaetaan kahteen osaan kunnes osan koko on yksi. Alkioita tarkastellaan huomattavasti vähemmän kuin peräkkäishaussa. Tarkastellaan tätä hieman tarkemmin.
Taulukko, jossa on 16 alkiota, voidaan jakaa kahteen osaan korkeintaan 4 kertaa, eli 16 -> 8 -> 4 -> 2 -> 1.
Toisaalta, taulukko, jossa on miljoona alkiota voidaan jakaa kahteen osaan korkeintaa 20 kertaa, eli 1000000 -> 500000 -> 250000 -> 125000 -> 62500 -> 31250 -> 15625 -> ~7813 -> ~3907 -> 1954 -> ~977 -> ~489 -> ~245 -> ~123 -> ~62 -> ~31 -> ~16 -> ~8 -> ~4 -> ~2 -> ~1.
Mitä tämä tarkoittaa? Binäärihakua käyttäen miljoona alkiota sisältävästä taulukosta tulee pahimmassa tapauksessa tarkastella noin kahtakymmentä alkiota, kun peräkkäishaussa tarkasteltavia alkioita on miljoona.
Koska haettavien alkioiden määrä puolittuu binäärihaussa jokaisen tarkastelun yhteydessä, voi binäärihaun tehokkuutta tarkastella kaksikantaisen logaritmin avulla. Kaksikantainen logaritmi (log2
) annetusta luvusta kertoo kuinka monta kertaa luku voidaan puolittaa. Esimerkiksi kaksikantainen logaritmi luvusta 16777216 (log2 16777216
) on 24, ja luvun 4294967296 kaksikantainen logaritmi, (log2 4294967296
) on 32. Tämä tarkoittaa että 4294967296 eri arvoa sisältävästä järjestyksessä olevasta taulukosta hakeminen vaatisi binäärihaulta korkeintaan 32 eri alkion tarkastamista.
Ensiaskeleet ohjelmien automaattiseen testaamiseen
- Osaat kertoa joitakin ohjelmistovirheistä johtuvia ongelmia.
- Tiedät mikä on stack trace, tunnet askeleet virheiden selvittämiseen ja osaat antaa tekstimuotoista testisyötettä Scannerille.
- Tiedät mikä on stack trace, tunnet askeleet virheiden selvittämiseen ja osaat antaa tekstimuotoista testisyötettä Scannerille.
- Tiedät mistä yksikkötestauksessa on kyse ja osaat kirjoittaa yksikkötestejä.
- Tunnet testivetoisen ohjelmistokehitysmenetelmän.
Otetaan seuraavaksi ensiaskeleet ohjelmien testaamiseen.
Virhetilanteet ja ongelman ratkaiseminen askel kerrallaan
Ohjelmia luodessa niihin päätyy virheitä. Joskus virheet eivät ole niin vakavia, ja aiheuttavat päänvaivaa lähinnä ohjelman käyttäjälle. Joskus toisaalta virheet voivat johtaa hyvinkin vakaviin seurauksiin. Varmaa on joka tapauksessa se, että ohjelmoimaan opetteleva ihminen tekee paljon virheitä.
Virheitä ei kannata missään nimessä pelätä tai välttää, sillä virheitä tekemällä oppii parhaiten. Pyri siis myös välillä rikkomaan työstämääsi ohjelmaa, jolloin pääset tutkimaan virheilmoitusta ja tarkastelemaan kertooko virheilmoitus jotain tekemästäsi virheestä.
Osoitteessa http://sunnyday.mit.edu/accidents/MCO_report.pdf oleva raportti kuvaa erään hieman vakavamman ohjelmistovirheestä johtuneen tapaturman sekä ohjelmistovirheen.
Ohjelmistovirhe liittyi siihen, että käytetty ohjelma odotti, että ohjelmoija käyttäisi kansainvälistä yksikköjärjestelmää laskuissa (metrit, kilogrammat, ...). Ohjelmoija oli kuitenkin käyttänyt amerikkalaista mittajärjestelmää erään järjestelmän osan laskuissa, jolloin satelliitin navigointiin liittynyt automaattinen korjausjärjestelmä ei toiminut oikein.
Satelliitti tuhoutui.
Ohjelmien muuttuessa monimutkaisemmiksi, tulee virheiden löytämisestäkin haastavampaa. NetBeansiin integroitu debuggeri voi olla avuksi virheiden löytämisessä. Debuggerin käyttöä on esitelty kurssimateriaaliin upotetuilla videoilla; niiden kertaamisesta ei ole koskaan haittaa.
Stack trace
Kun ohjelmassa tapahtuu virhe, ohjelma tyypillisesti tulostaa ns. stack tracen, eli niiden metodikutsujen listan, joiden seurauksena virhetilanne syntyi. Stack trace voi näyttää esimerkiksi seuraavalta:
Exception in thread "main" ... at Ohjelma.main(Ohjelma.java:15)
Listan alussa kerrotaan minkälainen virhe tapahtui (tässä ...), ja seuraavalla rivillä kerrotaan missä virhe tapahtui. Rivi "at Ohjelma.main(Ohjelma.java:15)" sanoo, että virhe tapahtui Ohjelma.java-tiedoston rivillä 15.
at Ohjelma.main(Ohjelma.java:15)
Muistilista virheenselvitykseen
Jos koodisi ei toimi etkä tiedä missä on virhe, näillä askeleilla pääset alkuun.
- Sisennä koodisi oikein ja selvitä, puuttuuko sulkuja.
- Tarkista ovatko käytetyt muuttujat oikean nimisiä.
- Testaa ohjelman kulkua erilaisilla syötteillä, ja selvitä minkälaisella syötteellä ohjelma ei toimi halutusti. Jos sait virheen testeistä, testit saattavat myös kertoa käytetyn syötteen.
- Lisää ohjelmaan tulostuskomentoja, joissa tulostat käytettävien muuttujien arvoja ohjelman suorituksen eri vaiheissa.
- Tarkista, että kaikki käyttämäsi muuttujat on alustettu. Jos tätä ei ole tehty, seuraa virhe NullPointerException.
- Jos ohjelmasi aiheuttaa poikkeuksen, kannattaa ehdottomasti kiinnittää huomiota poikkeuksen yhteydessä olevaan stack traceen, eli niiden metodikutsujen listaan, minkä seurauksena poikkeuksen aiheuttanut tilanne syntyi.
- Opettele käyttämään debuggeria, aiemmin nähdyllä videolla pääsee alkuun.
Testisyötteen antaminen Scannerille
Ohjelman testaaminen käsin on usein työlästä. Syötteen antaminen on mahdollista automatisoida esimerkiksi syöttämällä Scanner-oliolle luettava merkkijono. Alla on annettu esimerkki siitä, miten ohjelmaa voi testata automaattisesti. Ohjelmassa syötetään ensin viisi merkkijonoa, jonka jälkeen syötetään aiemmin nähty merkkijono. Tämän jälkeen yritetään syöttää vielä uusi merkkijono. Merkkijonoa "kuusi" ei pitäisi esiintyä sanajoukossa.
Testisyötteen voi antaa merkkijonona Scanner-oliolle konstruktorissa. Jokainen testisyötteessä annettava rivinvaihto merkitään merkkijonoon kenoviivan ja n-merkin yhdistelmänä "\n".
String syote = "yksi\n" + "kaksi\n" +
"kolme\n" + "nelja\n" +
"viisi\n" + "yksi\n" +
"kuusi\n";
Scanner lukija = new Scanner(syote);
ArrayList<String> luetut = new ArrayList<>();
while (true) {
System.out.println("Anna syote: ");
String rivi = lukija.nextLine();
if (luetut.contains(rivi)) {
break;
}
luettu.add(rivi);
}
System.out.println("Kiitos!");
if (luetut.sisaltaa("kuusi")) {
System.out.println("Joukkoon lisättiin arvo, jota sinne ei olisi pitänyt lisätä.");
}
Ohjelma tulostus näyttää vain ohjelman antaman tulostuksen, ei käyttäjän tekemiä komentoja.
Anna syote: Anna syote: Anna syote: Anna syote: Anna syote: Anna syote: Kiitos!
Merkkijonon antaminen Scanner-luokan konstruktorille korvaa näppäimistöltä luettavan syötteen. Merkkijonomuuttujan syote
sisältö siis "simuloi" käyttäjän antamaa syötettä. Rivinvaihto syötteeseen merkitään \n
:llä. Jokainen yksittäinen rivinvaihtomerkkiin loppuva osa syote-merkkijonossa siis vastaa yhtä nextLine()-komentoon annettua syötettä.
Kun haluat testata ohjelmasi toimintaa jälleen käsin, vaihda Scanner-olion konstruktorin parametriksi System.in
, eli järjestelmän syötevirta. Voit toisaalta halutessasi myös vaihtaa testisyötettä, sillä kyse on merkkijonosta.
Ohjelman toiminnan oikeellisuus tulee edelleen tarkastaa ruudulta. Tulostus voi olla aluksi hieman hämmentävää, sillä automatisoitu syöte ei näy ruudulla ollenkaan. Lopullinen tavoite on automatisoida myös ohjelman tulostuksen oikeellisuden tarkastaminen niin hyvin, että ohjelman testaus ja testituloksen analysointi onnistuu "nappia painamalla". Palaamme aiheeseen myöhemmissä osissa.
Yksikkötestaus
Edellä esitetty menetelmä automaattiseen testaamiseen missä ohjelmalle syötetyt syötteet muutetaan on varsin kätevä, mutta kuitenkin melko rajoittunut. Isompien ohjelmien testaaminen edellä kuvatulla tavalla on haastavaa. Eräs ratkaisu tähän on yksikkötestaus, missä ohjelman pieniä osia testataan erikseen.
Yksikkötestauksella tarkoitetaan lähdekoodiin kuuluvien yksittäisten osien kuten luokkien ja niiden tarjoamien metodien testaamista. Luokkien ja metodien rakenteen suunnittelussa käytettävän ohjesäännön -- jokaisella metodilla ja luokalla tulee olla yksi selkeä vastuu -- noudattamisen tai siitä poikkeamisen huomaa testejä kirjoittaessa. Mitä useampi vastuu metodilla on, sitä monimutkaisempi testi on. Jos laaja sovellus on kirjoitettu yksittäiseen metodiin, on testien kirjoittaminen sitä varten erittäin haastavaa ellei jopa mahdotonta. Vastaavasti, jos sovellus on pilkottu selkeisiin luokkiin ja metodeihin, on testienkin kirjoittaminen suoraviivaista.
Testien kirjoittamisessa hyödynnetään tyypillisesti valmiita yksikkötestauskirjastoja, jotka tarjoavat metodeja ja apuluokkia testien kirjoittamiseen. Javassa käytetyin yksikkötestauskirjasto on JUnit, johon löytyy myös tuki lähes kaikista ohjelmointiympäristöistä. Esimerkiksi NetBeans osaa automaattisesti etsiä JUnit-testejä projektista -- jos testejä löytyy, ne näytetään projektin alla Test Packages -kansiossa.
Tarkastellaan yksikkötestien kirjoittamista esimerkin kautta. Oletetaan, että käytössämme on seuraava luokka Laskin, ja haluamme kirjoittaa sitä varten automaattisia testejä.
public class Laskin {
private int arvo;
public Laskin() {
this.arvo = 0;
}
public void summa(int luku) {
this.arvo = this.arvo + luku;
}
public void erotus(int luku) {
this.arvo = this.arvo + luku;
}
public int getArvo() {
return this.arvo;
}
}
Laskimen toiminta perustuu siihen, että se muistaa aina edellisen laskuoperaation tuottaman tuloksen. Seuraavat laskuoperaatiot lisätään aina edelliseen lopputulokseen. Yllä olevaan laskimeen on jäänyt myös pieni copy-paste -ohjelmoinnista johtuva virhe. Metodin erotus pitäisi vähentää arvosta, mutta nyt se lisää arvoon.
Yksikkötestien kirjoittaminen aloitetaan testiluokan luomisella. Testiluokka luodaan Test Packages -kansion alle. Kun testaamme luokkaa Laskin
, testiluokan nimeksi tulee LaskinTest
. Nimen lopussa oleva merkkijono Test
kertoo ohjelmointiympäristölle, että kyseessä on testiluokka. Ilman merkkijonoa Test luokassa olevia testejä ei suoriteta. (Huom! Testit luodaan NetBeansissa Test Packages -kansion alle.)
Testiluokka LaskinTest on aluksi tyhjä.
public class LaskinTest {
}
Testit ovat testiluokassa olevia metodeja ja jokainen testi testaa yksittäistä asiaa. Aloitetaan luokan Laskin testaaminen -- luodaan ensin testimetodi, jossa varmistetaan, että juuri luodun laskimen sisältämä arvo on 0.
import static org.junit.Assert.assertEquals;
import org.junit.Test;
public class LaskinTest {
@Test
public void laskimenArvoAlussaNolla() {
Laskin laskin = new Laskin();
assertEquals(0, laskin.getArvo());
}
}
Yllä olevassa metodissa laskimenArvoAlussaNolla luodaan ensin laskinolio. Tämän jälkeen käytetään JUnit-testikehyksen tarjoamaa assertEquals-metodia arvon tarkistamiseen. Metodi tuodaan luokasta Assert komennolla import static, ja sille annetaan parametrina odotettu arvo -- tässä 0 -- sekä laskimen palauttama arvo. Jos metodin assertEquals arvot poikkeavat toisistaan, testin suoritus ei pääty hyväksytysti. Jokaisella testimetodilla tulee olla annotaatio @Test -- tämä kertoo JUnit-testikehykselle, että kyseessä on suoritettava testimetodi.
Testien suorittaminen onnistuu valitsemalla projekti oikealla hiirennapilla ja klikkaamalla vaihtoehtoa Test.
Testien suorittaminen luo output-välilehdelle (tyypillisesti NetBeansin alalaidassa) tulosteen, jossa on testiluokkakohtaiset tilastot. Alla olevassa esimerkissä on suoritettu pakkauksessa laskin olevan testiluokan LaskinTest testit. Testejä suoritettiin 1, joista yksikään ei epäonnistunut -- epäonnistuminen tarkoittaa tässä sitä, että testin testaama toiminnallisuus ei toiminut oletetusti.
Testsuite: LaskinTest Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.054 sec test-report: test: BUILD SUCCESSFUL (total time: 0 seconds)
Lisätään testiluokkaan summaa ja erotusta lisäävää toiminnallisuutta.
import static org.junit.Assert.assertEquals;
import org.junit.Test;
public class LaskinTest {
@Test
public void laskimenArvoAlussaNolla() {
Laskin laskin = new Laskin();
assertEquals(0, laskin.getArvo());
}
@Test
public void arvoViisiKunSummataanViisi() {
Laskin laskin = new Laskin();
laskin.summa(5);
assertEquals(5, laskin.getArvo());
}
@Test
public void arvoMiinusKaksiKunErotetaanKaksi() {
Laskin laskin = new Laskin();
laskin.erotus(2);
assertEquals(-2, laskin.getArvo());
}
}
Testien suorittaminen antaa seuraavanlaisen tulostuksen.
Testsuite: LaskinTest Tests run: 3, Failures: 1, Errors: 0, Skipped: 0, Time elapsed: 0.059 sec Testcase: arvoMiinusKaksiKunErotetaanKaksi(LaskinTest): FAILED expected:<-2> but was:<2> junit.framework.AssertionFailedError: expected:<-2> but was:<2> at LaskinTest.arvoMiinusKaksiKunErotetaanKaksi(LaskinTest.java:25) Test LaskinTest FAILED test-report: test: BUILD SUCCESSFUL (total time: 0 seconds)
Tulostus kertoo, että kolme testiä suoritettiin. Yksi niistä päätyi virheeseen. Testitulostuksessa on tieto myös testin rivistä, jossa virhe tapahtui (25) sekä tieto odotetusta (-2) ja saadusta arvosta (2). Kun testien suoritus päättyy virheeseen, NetBeans näyttää testien suoritukseen liitttyvän virhetilanteen myös visuaalisena.
Edellisillä testeillä kaksi testeistä menee läpi, mutta yhdessä on tapahtunut virhe. Korjataan luokkaan Laskin jäänyt virhe.
// ...
public void erotus(int luku) {
this.arvo -= luku;
}
// ...
Kun testit suoritetaan uudestaan, testit menevät läpi.
Testsuite: LaskinTest Tests run: 3, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.056 sec test-report: test: BUILD SUCCESSFUL (total time: 0 seconds)
Testivetoinen ohjelmistokehitys
Testivetoinen ohjelmistokehitys (Test-driven development) on ohjelmistokehitysprosessi, joka perustuu ohjelman rakentamiseen pienissä osissa. Testivetoisessa ohjelmistokehityksessä ohjelmoija kirjoittaa aina ensin automaattisesti suoritettavan yksittäistä tietokoneohjelman osaa tarkastelevan testin.
Testi ei mene läpi, sillä testin täyttävä toiminnallisuus eli tarkasteltava tietokoneohjelman osa puuttuu. Kun testi on kirjoitettu, ohjelmaan lisätään toiminnallisuus, joka täyttää testin vaatimukset. Testit suoritetaan uudestaan, jonka jälkeen -- jos kaikki testit menevät läpi -- lisätään uusi testi tai vaihtoehtoisesti -- jos testit eivät mene läpi -- korjataan aiemmin kirjoitettua ohjelmaa. Ohjelman sisäistä rakennetta korjataan eli refaktoroidaan tarvittaessa siten, että ohjelman toiminnallisuus pysyy samana mutta rakenne selkiytyy.
Testivetoinen ohjelmistokehitys koostuu viidestä askeleesta, joita toistetaan kunnes ohjelman toiminnallisuus on valmis.
- Kirjoita testi. Ohjelmoija päättää, mitä ohjelman toiminnallisuutta testataan, ja kirjoittaa toiminnallisuutta varten testin.
- Suorita testit ja tarkista menevätkö testit läpi. Kun uusi testi on kirjoitettu, testit suoritetaan. Jos testin suoritus päättyy hyväksyttyyn tilaan, testissä on todennäköisesti virhe ja se tulee korjata -- testin pitäisi testata vain toiminnallisuutta, jota ei ole vielä toteutettu.
- Kirjoita toiminnallisuus, joka täyttää testin vaatimukset. Ohjelmoija toteuttaa toiminnallisuuden, joka täyttää vain testin vaatimukset. Huomaa, että tässä ei toteuteta asioita, joita testi ei vaadi -- toiminnallisuutta lisätään vain vähän kerrallaan.
- Suorita testit. Jos testit eivät pääty hyväksyttyyn tilaan, kirjoitetussa toiminnallisuudessa on todennäköisesti virhe. Korjaa toiminnallisuus -- tai, jos toiminnallisuudessa ei ole virhettä -- korjaa viimeksi toteutettu testi.
- Korjaa ohjelman sisäistä rakennetta. Kun ohjelman koko kasvaa, sen sisäistä rakennetta korjataan tarvittaessa. Liian pitkät metodit pilkotaan useampaan osaan ja ohjelmasta eriytetään käsitteisiin liittyviä luokkia. Testejä ei muuteta, vaan niitä hyödynnetään ohjelman sisäiseen rakenteeseen tehtyjen muutosten oikeellisuuden varmistamisessa -- jos ohjelman rakenteeseen tehty muutos muuttaa ohjelman toiminnallisuutta, testit varoittavat siitä, ja ohjelmoija voi korjata tilanteen.
Tehtäväpohjassa tulee edellisen esimerkin alkutilanne -- tehtäväpohjaan on jo lisätty yksikkötestaukseen tarvittava JUnit-kirjasto. Seuraa esimerkkiä ja luo Tehtavienhallinnalta haluttu toiminnallisuus testivetoista ohjelmistokehitystä noudattaen. Kun olet saanut edellisen esimerkin loppuun asti, lisää sovellukseen vielä testit tehtävien poistamiseen sekä testien vaatima toiminnallisuus.
Tehtävä on jaettu kahteen osaan. Osat ovat seuraavat:
-
Noudata esimerkkiä kunnes esimerkissä refaktoroidaan ohjelma ja luodaan luokka Tehtava. Luo luokat
TehtavienhallintaTest
jaTehtavienhallinta
sekä niihin esimerkissä lisätty toiminnallisuus. - Noudata esimerkkiä loppuun asti, eli tee myös esimerkissä kuvattu refaktorointi.
Päivitä luokan Ohjelma metodia osiaToteutettu
palauttamaan valmiiksi saamasi osan numero. Voit palauttaa tehtävän vaikket tekisikään kumpaakin osaa, jolloin saat pisteitä tehtävän niistä osista, jotka olet tehnyt.
Esimerkiksi, kun olet saanut ensimmäisen osan tehtyä eli noudattanut esimerkkiä refaktorointiin asti, olet vaiheessa 1, jolloin metodin osiaToteutettu
tulisi palautta arvo 1
.
Tehtävään on olemassa esimerkkiratkaisu Test My Code -järjestelmässä. Esimerkkiratkaisua voi käyttää oman oppimisen tukena, ja se on tarkasteltavissa jo ennen kuin olet saanut tehtävän valmiiksi. Esimerkkiratkaisuun pääset käsiksi täältä. Huomaathan, että tehtävän voi ratkaista monella tapaa, ja tässä annettu esimerkkiratkaisu on näistä vain yksi.
Yksikkötestaus on vain osa ohjelmiston testaamista. Yksikkötestaamisen lisäksi ohjelmiston toteuttaja toteuttaa myös integraatiotestejä, joissa tarkastellaan komponenttien kuten luokkien yhteistoiminnallisuutta, sekä käyttöliittymätestejä, joissa testataan sovelluksen käyttöliittymää käyttöliittymän tarjoamien elementtien kuten nappien kautta.
Näitä testaamiseen liittyviä menetelmiä tarkastellaan tarkemmin muunmuassa kursseilla ohjelmistotekniikka sekä ohjelmistotuotanto.
Muutamia laajempia tehtäviä
Ohjelmoinnin perusteiden lopuksi teet muutamia laajempia tehtäviä. Tehtävissä ei ole ennalta määriteltyä rakennetta -- mieti tehtävää tehdessäsi minkälaiset luokat ja oliot auttavat tehtävien ratkaisemisessa.
Tässä tehtävässä toteutetaan ohjelma kurssipistetilastojen tulostamiseen. Ohjelmalle syötetään pisteitä (kokonaislukuja nollasta sataan), ja ohjelma tulostaa niiden perusteella arvosanoihin liittyviä tilastoja. Syötteiden lukeminen lopetetaan kun käyttäjä syöttää luvun -1. Lukuja, jotka eivät ole välillä [0-100] ei tule ottaa huomioon tilastojen laskemisessa.
Muistathan, että käyttäjältä luetun merkkijonon saa muunnettua kokonaisluvuksi Integer-luokan metodilla parseInt. Tämä toimii seuraavasti:
String lukuMerkkijonona = "3";
int luku = Integer.parseInt(lukuMerkkijonona);
System.out.println(lukuMerkkijonona + 7);
System.out.println(luku + 7);
37 10
Pisteiden keskiarvot
Kirjoita ohjelma, joka lukee käyttäjältä kurssin yhteispisteitä kuvaavia kokonaislukuja. Luvut väliltä [0-100] ovat hyväksyttäviä ja luku -1 lopettaa syötteen. Muut luvut ovat virhesyötteitä, jotka tulee jättää huomiotta. Kun käyttäjä syöttää luvun -1, tulostetaan syötettyjen yhteispisteiden keskiarvo.
Syötä yhteispisteet, -1 lopettaa: -42 24 42 72 80 52 -1 Pisteiden keskiarvo (kaikki): 54.0
Syötä yhteispisteet, -1 lopettaa: 50 51 52 -1 Pisteiden keskiarvo (kaikki): 51.0
Hyväksyttyyn arvosanaan liittyvien pisteiden keskiarvot
Täydennä ohjelmaa siten, että se laskee kaikkien pisteiden keskiarvon lisäksi myös hyväksyttyyn arvosanaan liittyvien pisteiden keskiarvot.
Hyväksytyn arvosanan saa vähintään 50 kurssipisteellä. Voit olettaa, että käyttäjä kirjoittaa aina vähintään yhden välillä [0-100] olevan kokonaisluvun. Jos hyväksyttyyn arvosanaan osuvia lukuja ei ole lainkaan, tulostetaan viiva hyväksyttyjen keskiarvon kohdalle "-".
Syötä yhteispisteet, -1 lopettaa: -42 24 42 72 80 52 -1 Pisteiden keskiarvo (kaikki): 54.0 Pisteiden keskiarvo (hyväksytyt): 68.0
Syötä yhteispisteet, -1 lopettaa: 49 48 47 -1 Pisteiden keskiarvo (kaikki): 48.0 Pisteiden keskiarvo (hyväksytyt): -
Hyväksyttyjen prosenttiosuus
Täydennä edellisessä osassa toteuttamaasi ohjelmaa siten, että ohjelma tulostaa myös hyväksymisprosentin. Hyväksymisprosentti lasketaan kaavalla 100 * hyväksytyt / osallistujat.
Syötä yhteispisteet, -1 lopettaa: 49 48 47 -1 Pisteiden keskiarvo (kaikki): 48.0 Pisteiden keskiarvo (hyväksytyt): - Hyväksymisprosentti: 0.0
Syötä yhteispisteet, -1 lopettaa: 102 -4 33 77 99 1 -1 Pisteiden keskiarvo (kaikki): 52.5 Pisteiden keskiarvo (hyväksytyt): 88.0 Hyväksymisprosentti: 50.0
Arvosanajakauma
Täydennä ohjelmaa siten, että ohjelma tulostaa myös arvosanajakauman. Arvosananajakauma muodostetaan seuraavasti.
pistemäärä | arvosana |
---|---|
< 50 | hylätty eli 0 |
< 60 | 1 |
< 70 | 2 |
< 80 | 3 |
< 90 | 4 |
>= 91 | 5 |
Jokainen koepistemäärä muutetaan arvosanaksi yllä olevan taulukon perusteella. Jos syötetty pistemäärä ei ole välillä [0-100], jätetään se huomiotta.
Arvosanajakauma tulostetaan tähtinä. Esim jos arvosanaan 5 oikeuttavia koepistemääriä on 1 kappale, tulostuu rivi 5: *. Jos johonkin arvosanaan oikeuttavia pistemääriä ei ole, ei yhtään tähteä tulostu, alla olevassa esimerkissä näin on mm. nelosten kohdalla.
Syötä yhteispisteet, -1 lopettaa: 102 -2 1 33 77 99 -1 Pisteiden keskiarvo (kaikki): 52.5 Pisteiden keskiarvo (hyväksytyt): 88.0 Hyväksymisprosentti: 50.0 Arvosanajakauma: 5: * 4: 3: * 2: 1: 0: **
Tehtävään on olemassa esimerkkiratkaisu Test My Code -järjestelmässä. Esimerkkiratkaisua voi käyttää oman oppimisen tukena, ja se on tarkasteltavissa jo ennen kuin olet saanut tehtävän valmiiksi. Esimerkkiratkaisuun pääset käsiksi täältä. Huomaathan, että tehtävän voi ratkaista monella tapaa, ja tässä annettu esimerkkiratkaisu on näistä vain yksi.
Tässä tehtävässä tehdään ohjelma, joka tarjoaa käyttäjälle mahdollisuuden reseptien hakuun reseptin nimen, keittoajan tai raaka-aineen nimen perusteella. Ohjelman tulee lukea reseptit käyttäjän antamasta tiedostosta.
Jokainen resepti koostuu kolmesta tai useammasta rivistä reseptitiedostossa. Ensimmäisellä rivillä on reseptin nimi, toisella rivillä reseptin keittoaika (kokonaisluku), ja kolmas ja sitä seuraavat rivit kertovat reseptin raaka-aineet. Reseptin raaka-aineiden kuvaus päättyy tyhjään riviin. Tiedostossa voi olla useampia reseptejä. Alla kuvattuna esimerkkitiedosto.
Lettutaikina 60 maito muna jauho sokeri suola voi Lihapullat 20 jauheliha muna korppujauho Tofurullat 30 tofu riisi vesi porkkana kurkku avokado wasabi
Ohjelma toteutetaan osissa. Ensin ohjelmaan luodaan mahdollisuus reseptien lukemiseen sekä listaamiseen. Tämän jälkeen ohjelmaan lisätään mahdollisuus reseptien hakemiseen nimen perusteella, keittoajan perusteella ja lopulta raaka-aineen perusteella.
Tehtäväpohjassa on mukana tiedosto reseptit.txt
, jota voi käyttää sovelluksen testaamiseen. Huomaa, että ohjelman ei tule listata reseptien raaka-aineita, mutta niitä käytetään hakutoiminnallisuudessa. Tiedoston reseptit.txt
voi myös ladata tämän linkin takaa.
Reseptien lukeminen ja listaaminen
Luo ohjelmaan ensin mahdollisuus reseptien lukemiseen sekä listaamiseen. Ohjelman käyttöliittymän tulee olla seuraavanlainen. Voit olettaa, että käyttäjä syöttää aina tiedoston, joka on olemassa. Alla oletetaan, että tehtävänannossa annetut esimerkkireseptit ovat tiedostossa reseptit.txt
.
Mistä luetaan? reseptit.txt Komennot: listaa - listaa reseptit lopeta - lopettaa ohjelman Syötä komento: listaa Reseptit: Lettutaikina, keittoaika: 60 Lihapullat, keittoaika: 20 Tofurullat, keittoaika: 30 Syötä komento: lopeta
Reseptien hakeminen nimen perusteella
Lisää ohjelmaan mahdollisuus reseptien hakemiseen nimen perusteella. Nimen perusteella hakeminen tapahtuu komennolla hae nimi
, jonka jälkeen käyttäjältä kysytään merkkijonoa, jota etsitään reseptin nimistä. Hakutoiminnallisuuden tulee toimia siten, että se tulostaa kaikki ne reseptit, joiden nimessä esiintyy käyttäjän kirjoittama merkkijono.
Mistä luetaan? reseptit.txt Komennot: listaa - listaa reseptit lopeta - lopettaa ohjelman hae nimi - hakee reseptiä nimen perusteella Syötä komento: listaa Reseptit: Lettutaikina, keittoaika: 60 Lihapullat, keittoaika: 20 Tofurullat, keittoaika: 30 Syötä komento: hae nimi Mitä haetaan: rulla Reseptit: Tofurullat, keittoaika: 30 Syötä komento: lopeta
Reseptien hakeminen keittoajan perusteella
Lisää seuraavaksi ohjelmaan mahdollisuus reseptien hakemiseen keittoajan perusteella. Keittoajan perusteella hakeminen tapahtuu komennolla hae keittoaika
, jonka jälkeen käyttäjältä kysytään suurinta hyväksyttävää keittoaikaa. Hakutoiminnallisuuden tulee toimia siten, että se tulostaa kaikki ne reseptit, joiden keittoaika on pienempi tai yhtä suuri kuin käyttäjän syöttämä keittoaika.
Mistä luetaan? reseptit.txt Komennot: listaa - listaa reseptit lopeta - lopettaa ohjelman hae nimi - hakee reseptiä nimen perusteella hae keittoaika - hakee reseptiä keittoajan perusteella Syötä komento: hae keittoaika Keittoaika korkeintaan: 30 Reseptit: Lihapullat, keittoaika: 20 Tofurullat, keittoaika: 30 Syötä komento: hae keittoaika Keittoaika korkeintaan: 15 Reseptit: Syötä komento: hae nimi Mitä haetaan: rulla Reseptit: Tofurullat, keittoaika: 30 Syötä komento: lopeta
Reseptien hakeminen raaka-aineen perusteella
Lisää lopulta ohjelmaan mahdollisuus reseptien hakemiseen raaka-aineen perusteella. Raaka-aineen perusteella hakeminen tapahtuu komennolla hae aine
, jonka jälkeen käyttäjältä kysytään merkkijonoa. Hakutoiminnallisuuden tulee toimia siten, että se tulostaa kaikki ne reseptit, joiden raaka-aineissa esiintyy käyttäjän antama merkkijono. Huomaa, että tässä annetun merkkijonon täytyy vastata täysin haettua raaka-ainetta (esim. "okeri" ei käy ole sama kuin "sokeri").
Mistä luetaan? reseptit.txt Komennot: listaa - listaa reseptit lopeta - lopettaa ohjelman hae nimi - hakee reseptiä nimen perusteella hae keittoaika - hakee reseptiä keittoajan perusteella hae aine - hakee reseptiä raaka-aineen perusteella Syötä komento: hae keittoaika Keittoaika korkeintaan: 30 Reseptit: Lihapullat, keittoaika: 20 Tofurullat, keittoaika: 30 Syötä komento: hae aine Mitä raaka-ainetta haetaan: sokeri Reseptit: Lettutaikina, keittoaika: 60 Syötä komento: hae aine Mitä raaka-ainetta haetaan: muna Reseptit: Lettutaikina, keittoaika: 60 Lihapullat, keittoaika: 20 Syötä komento: hae aine Mitä raaka-ainetta haetaan: una Reseptit: Syötä komento: lopeta
Tehtävään on olemassa esimerkkiratkaisu Test My Code -järjestelmässä. Esimerkkiratkaisua voi käyttää oman oppimisen tukena, ja se on tarkasteltavissa jo ennen kuin olet saanut tehtävän valmiiksi. Esimerkkiratkaisuun pääset käsiksi täältä. Huomaathan, että tehtävän voi ratkaista monella tapaa, ja tässä annettu esimerkkiratkaisu on näistä vain yksi.
Tehtävä vastaa kolmea yksiosaista tehtävää.
Tässä tehtävässä suunnittelet ja toteutat tietokannan lintubongareille. Tietokanta sisältää lintuja, joista jokaisella on nimi (merkkijono) ja latinankielinen nimi (merkkijono). Tämän lisäksi tietokanta laskee kunkin linnun havaintokertoja.
Ohjelmasi täytyy toteuttaa seuraavat komennot:
Lisaa
- lisää linnun (huom: komennon nimessä ei ä-kirjainta!)Havainto
- lisää havainnonTilasto
- tulostaa kaikki linnutNayta
- tulostaa yhden linnun (huom: komennon nimessä ei ä-kirjainta!)Lopeta
- lopettaa ohjelman
Lisäksi virheelliset syötteet pitää käsitellä. (Ks. Simo
alla). Tässä vielä esimerkki ohjelman toiminnasta:
? Lisaa Nimi: Korppi Latinankielinen nimi: Corvus Corvus ? Lisaa Nimi: Haukka Latinankielinen nimi: Dorkus Dorkus ? Havainto Mikä havaittu? Haukka ? Havainto Mikä havaittu? Simo Ei ole lintu! ? Havainto Mikä havaittu? Haukka ? Tilasto Haukka (Dorkus Dorkus): 2 havaintoa Korppi (Corvus Corvus): 0 havaintoa ? Nayta Mikä? Haukka Haukka (Dorkus Dorkus): 2 havaintoa ? Lopeta
Huom! Ohjelmasi rakenne on täysin vapaa. Testaamme vain että Paaohjelma
luokan main
-metodi toimii kuten tässä on kuvailtu. Hyödyt tehtävässä todennäköisesti ongelma-aluetta sopivasti kuvaavista luokista.
Tehtävään on olemassa esimerkkiratkaisu Test My Code -järjestelmässä. Esimerkkiratkaisua voi käyttää oman oppimisen tukena, ja se on tarkasteltavissa jo ennen kuin olet saanut tehtävän valmiiksi. Esimerkkiratkaisuun pääset käsiksi täältä. Huomaathan, että tehtävän voi ratkaista monella tapaa, ja tässä annettu esimerkkiratkaisu on näistä vain yksi.
Vertaisarviointi: hajautustaulut
Otetaan hetkeksi askel taaksepäin ja muistellaan hajautustaulujen käyttöä.
Ohjelmointikurssin kuudennessa osassa loimme taas omia tehtäviä. Nyt on hetki vertaisarviointiin! Anna vertaispalautetta kahdesta jonkun toisen kurssilaisen lähettämästä tehtävästä ja arvioi lopuksi itse tekemääsi tehtävää. Itse tekemäsi tehtävä näkyy vain jos olet tehnyt sen -- jos et tehnyt tehtävää, pääset arvioimaan yhden ylimääräisen tehtävän.
Alla on kolme Crowdsorcereriin tehtyä tehtävää: kaksi jonkun kurssitoverisi lähettämää ja yksi itsearviointia varten. Niiden yhteydessä on muistin virkistykseksi ohjeistus, jonka pohjalta kyseiset tehtävänannot on tehty.
Tarkastele jokaisen tehtävän eri osia: tehtävänantoa, tehtäväpohjaa ja malliratkaisua sekä testaukseen käytettäviä syötteitä ja tulosteita. Arvioi niiden selkeyttä, vaikeutta ja sitä, kuinka hyvin ne vastaavat ohjeistukseensa.
Voit vaihtaa näkymää tehtäväpohjan ja mallivastauksen välillä painamalla lähdekoodin yläpalkin painikkeita. Palautteenannon avuksi on annettu väittämiä. Voit valita kuinka samaa mieltä niiden kanssa olet painamalla hymiöitä. Annathan myös sanallista palautetta sille varattuun kenttään! Lisää vielä tehtävää mielestäsi kuvaavia tageja ja paina Lähetä.
Anna arvio kummallekin vertaispalautetehtävälle ja lopuksi vielä omallesi.
Muista olla reilu ja ystävällinen. Hyvä palaute on rehellistä, mutta kannustavaa!
Voit halutessasi ladata arvioitavan tehtävän tehtäväpohjan ja malliratkaisun koneellesi, ja testata niiden käyttöä. Molemmat tulevat ZIP-paketeissa, jolloin sinun täytyy purkaa ne, ennen kuin voit avata ne NetBeansissä.
Keksi tehtävä, jossa käytetään HashMappia. Tehtäväpohjassa on valmiina komennon kysyminen ja toistolause, joka jatkuu kunnes ohjelman käyttäjä kirjoittaa komennon "lopeta".
Huom! Tässä sinun täytyy syöttää jokaiselle testitapaukselle useampi syöte. Useamman syötteen saat annettua, kun laitat rivinvaihdon \n
jokaisen syötteen väliin. Lisäksi lopeta jokainen testisyöte tekstillä lopeta
, jotta testissä silmukan suoritus lakkaa.
Esimerkiksi jos haluat antaa testisyötteeksi "kissa", "koira", "lopeta", syötä input-kenttään teksti kissa\nkoira\nlopeta
.
Muista merkitä malliratkaisurivit ohjelmaan -- näin ratkaisu ei tule suoraan käyttäjälle näkyvään.
Tehtävien luomistehtävät vastaavat kurssin pisteytyksessä ohjelmointitehtävää.
Yhteenveto
Seitsemännessa osassa tutustuttiin käsitteeseen ohjelmointiparadigma ja vertailtiin proseduraalista ohjelmointia ja olio-ohjelmointia. Tutustuimme tiedon järjestämiseen sekä tiedon hakemiseen liittyviin algoritmeihin (valintajärjestäminen, binäärihaku) sekä otimme ensiaskeleet ohjelmien automaattiseen testaamiseen.
Vastaa vielä alla olevaan kyselyyn.