581325-0 Java-ohjelmointi, koe 14.12.2004/AW 3. tehtävän arvosteluperiaatteet Jaakko Nenonen --------------------------------------------------------------------------------------------------------- Tehtävänanto --------------------------------------------------------------------------------------------------------- 3. Käytössäsi on tekstitiedostojen lukemiseen luokka Syottotiedosto. Luokalla on konstruktori Syottotiedosto(String nimi). Parametrina annetaan tiedoston nimi. Rivejä luetaan aksessorilla public String lueRivi() Kun tiedosto on loppunut, metodi palauttaa null-arvon. Sekä syöttötiedoston luonti että lukuoperaatio voivat aiheuttaa poikkeuksen Exception. Laadi keskusteleva eli interaktiivinen ohjelma KumpiTiedostoOnJouluisampi, joka kysyy ensin kaksi syöttötiedoston nimeä. Sitten ohjelma tutkii, kummassa tiedostossa on enemmän rivejä, joilla esiintyy yksi tai useampia seuraavista jouluaiheisista sanoista: "joulu", "kuusi", "pukki", "puuro". Lopuksi ohjelma tulostaa tiedon, kumpi syöttötiedostoista sisälsi enemmän yllä määritellyssä mielessä "jouluisampia" rivejä. Rivejä voi toki olla myös yhtä monta. Ohjelman KumpiTiedostoOnJouluisampi pitää itse käsitellä järkevällä ja käyttäjäystävällisellä tavalla syöttötiedostojen luomisen ja lukemisen mahdollisesti aiheuttamat Exception-poikkeukset. (15 pistettä) ------------------------------------------------------------------------------------------------------ Yleistä ------------------------------------------------------------------------------------------------------ Tehtävä osattiin melko hyvin vaikka se oli vaikea ja tehtävänantokin oli ymmärretty pääasiassa oikein. Mielestäni arvostelin melko hellämielisesti eikä pienistä syntaksivirheistä menettänyt pisteitä. Täysiin pisteisiin vaadittiin kuitenkin moitteeton ratkaisu. Ohjelmassa piti siis luoda kaksi syöttötiedosto-oliota ja tämän jälkeen kumpaakin tiedostoa kohti käydä toistolauseella läpi tiedoston rivit ja tutkia kuinka moni oli niistä "jouluisia". Käytössä siis oli Syottotiedosto-luokka eikä sitä pitänyt koodata itse. Jos koodasi, pisteitä sakotettiin roimasti. Lisäksi Lue-luokka oli käytössä vaikka tehtävänannossa sitä ei erikseen mainittu. Jos Lue-luokan toimintoja oli tehnyt uudelleen, siitä myös sakotettiin. Tehtävässä ei tavoiteltu myöskään mitään olioihin liittyvää. Jos alkoi koodaamaan KumpiTiedostoOnJouluisampi-oliota, ei saanut kuin säälipisteitä. Tehtävän voi ratkaista usealla tavalla. Koska tutkittavia tiedostoja oli kaksi, helpointa oli tehdä metodit syöttötiedoston luomiseen ja lukuun sekä myös jouluisen rivin tarkistukseen. Metodeja ei ollut pakko tehdä vaan sai myös kirjoittaa saman käsittelyn molemmille tiedostoille. Jos kuitenkin ajan loppumisen vuoksi tai laiskuuttaan kirjoitti vain kommentin että toinen tiedosto käsitellään samalla tavalla, menetti 1-2 pistettä. Jouluisen rivin tarkistukseen tuli käyttää indexOf-aksessoria tai vaihtoehtoisesti sai koodata oman metodin, joka teki saman oikein. Aika moni ei muistanut aksessorin nimeä. Väärästä nimestä ei kuitenkaan menettänyt pisteitä. Kuitenkin jos omanimetty aksessori toimi eritavalla kuin indexOf, tuli selostaa kuinka se toimi. Muuten menetti pisteitä. Eniten virheitä tehtiin poikkeuskäsittelyssä. Koska käsiteltiin kahta Syottotiedosto-oliolla, joilla sekä konstruktori-kutsu että lueRivi()-aksessori saattoivat aiheuttaa poikkeuksen, tuli try-catch-lohkoja olla ratkaisussa yhteensä 4 kappaletta (paitsi jos käytti metodeja)! Mikäli laittoi koko main-metodin yhden pitkän try-catchin sisään, menetti puolet pisteistä. Samaten jos laittoi tiedoston luonnin ja käsittelyn samaan try-catchiin, menetti tapauksesta riippuen 1-4 pistettä (tarkemmat perustelut löytyvät alla). Tällaiset ratkaisut eivät ole käyttäjäystävällisiä koska käyttäjälle ei kerrota missä virhe tapahtui. Käyttäjäystävällisyyteen määritelmään laskettiin myös System.out.println:n käyttö. Mikäli tulosteita ei käyttänyt ollenkaan, sakotettiin muutama piste. Myös catchien sisällä tuli olla tulosteita. Toinen hyvin yleinen ja paha virhe oli että poikkeus kuitattiin catchin sisällä pelkällä s.o.p.-virheilmoituksella, jonka jälkeen ohjelman suoritus sai iloisesti jatkua. Esimerkiksi tiedoston lukua saatettiin jatkaa poikeuksen jälkeen ikään kuin mitään ei olisi tapahtunut tai siirryttiin jälkimmäisen tiedoston käsittellyyn, jolloin tuloksista tuli virheellisiä. Poikkeuksista ei ollut tarkoitus toipua vaan ohjelman suoritus tuli keskeyttää! Tästä menetti 1-8 pistettä riippuen tapauksesta. Seuraavassa on eräs esimerkkiratkaisu, jossa on käytetty metodeja. Ilmankin metodeja siis sai täydet pisteet eikä silläkään ollut merkitystä missä järjestyksessä tiedostot loi ja käsitteli (kunhan luonti edelsi käsittelyä). --------------------------------------------------------------------------------------------------------- Esimerkkiratkaisu --------------------------------------------------------------------------------------------------------- public class KumpiTiedostoOnJouluisampi { // Käytetään taulukkoa, johon on kerätty kaikki jouluiset sanat // Näin ohjelmaan on helppo lisätä uusia joulusanoja tai poistaa vanhoja. private static final String[] JOULUSANAT = { "joulu", "kuusi", "pukki","puuro" }; // main-metodissa on vain ohjelman runko public static void main(String[] args) { System.out.println("Tervetuloa tiedostojen jouluisuuden testausohjelmaan!"); // luodaan syöttötiedostot erillisessä metodissa. Parametrina annetaan // tiedoston järjestysnumero. Syottotiedosto st1 = luoSyottotiedosto(1); Syottotiedosto st2 = luoSyottotiedosto(2); // tiedostossa olevien joulurivien laskemiseen käytetään erillistä // metodia. Parametrina annetaan tarkasteltava Syottotiedosto-olio. int summa1 = laskeJouluisuus(st1); int summa2 = laskeJouluisuus(st2); System.out.println("Ensimmäisessä tiedostossa on " + summa1 + " jouluista riviä"); System.out.println("ja toisessa tiedostossa " + summa2); // Lopuksi vertaillaan kummassa oli enemmän jouluisia rivejä ja annetaan // asiaankuuluva tulostus. if (summa1 > summa2) System.out.println("Ensimmäinen tiedosto on jouluisampi kuin jälkimmäinen!"); else if (summa1 < summa2) System.out.println("Jälkimmäinen tiedosto on jouluisampi kuin ensimmäinen!"); else System.out.println("Tiedostot ovat yhtä jouluisat!"); } //////////////////////////////////////////////////////////////////////////////// // Metodi, jossa luodaan syöttötiedosto. Tiedoston nimeä pyydetään // käyttäjältä. Parametrina annettavan tiedoston järjestysnumero //////////////////////////////////////////////////////////////////////////////// private static Syottotiedosto luoSyottotiedosto(int jarjNro) { Syottotiedosto st = null; boolean ok; // Tehdään toistolause, jossa käyttäjältä pyydetään tiedoston nimeä // niin kauan kunnes käyttäjä antaa sellaisen tiedoston joka on olemassa // Tämän sai tehdä myös ilman do-whileä jos catch-osiossa ohjelman // suoritus lopetettiin do { System.out.println("Anna " + jarjNro+ ". testattavan tiedoston nimi:"); try { // luodaan syöttötiedosto-olio. Tiedoston nimi annetaan Lue-luokan // aksessorilla rivi() st = new Syottotiedosto(Lue.rivi()); ok = true; } catch (Exception e) { // tapahtuu poikkeus jos tiedostoa ei ole olemassa System.out.println("Tiedostoa ei ole olemassa! Yritä uudelleen."); ok = false; } } while (!ok); // edellisessä poikkeuksessa hyväksyttiin myös ohjelman suorituksen lopettaminen // lopuksi palautetaan saatu syöttötiedosto. return st; } //////////////////////////////////////////////////////////////////////////////// // Metodi, joka laskee monta jouluista riviä parametrina saadussa // syöttötiedostossa on //////////////////////////////////////////////////////////////////////////////// private static int laskeJouluisuus(Syottotiedosto st) { int summa = 0; String rivi; // lueRivi()-aksessori voi aiheuttaa poikkeuksen try { // toistolause, jossa käydään tiedoston rivit läpi. while ((rivi = st.lueRivi()) != null) { // kutsutaan metodia, joka kertoo onko parametrina saatu rivi // jouluinen vai ei. if (onJouluisa(rivi)) summa++; } } catch (Exception e) { // Poikkeuksen tapahtuessa koko ohjelman suoritus keskeytetään System.out.println("Tapahtui tiedoston lukuvirhe! Ohjelma keskeytetään"); System.exit(0); } // palautetaan jouluisten rivien lukumäärä return summa; } //////////////////////////////////////////////////////////////////////////////// // Metodi, joka kertoo sisältääkö parametrina saatu rivi // joulusanoja vai ei //////////////////////////////////////////////////////////////////////////////// private static boolean onJouluisa(String rivi) { // käytetään alussa määritettyä JOULUSANAT-taulukkoa apuna. // indexOf-aksessorilla terkastetaan sisältääkö // rivi joulusanan vai ei. Jos ei muistanut aksessoria // sai tehdä oman vastaavan metodin. for (int i = 0; i < JOULUSANAT.length; i++) { if (rivi.indexOf(JOULUSANAT[i]) != -1) { return true; } } return false; } //////////////////////////////////////////////////////////////////////////////// // toinen vaihtoehtoinen tapa jouluisuuden tarkistukseen (Muitakin oli!): //////////////////////////////////////////////////////////////////////////////// private static boolean onJouluisa2(String rivi) { if (rivi.indexOf("joulu")!=-1 || rivi.indexOf("kuusi")!=-1 || rivi.indexOf("pukki")!=-1 || rivi.indexOf("puuro")!=-1) return true; return false; } } --------------------------------------------------------------------------------------------------------- Pisteytyksen runko --------------------------------------------------------------------------------------------------------- Ratkaisu pisteytettiin seuraavasti: * Syottötiedoston luonti (2 pistettä) * Try-Catchiä käytetty syöttötiedoston luonnissa (3p) * Oikeanlainen toistorakenne tiedostojen rivien lukemiseen (3p) * Rivin jouluisuuden tarkistus (3p) * try-catch toistorakenteen ympärillä (2p) * ohjelman käyttäjäystävällisyys (2p) * Yhteensä 15 pistettä Edelläolevaa pisteytystä käytettiin vain jos ratkaisu oli keskeneräinen tai muuten selvästi puutteellinen. Yleisesti ottaen käytettiin ns. "vähentävää" laskutapaa eli lähdettiin siitä että ratkaisu on 15 pisteen arvoinen ja katsotaan sitten mitä puutteita ratkaisussa on ja vähennettiin pisteitä. Samanlaisesta virheestä vähennettiin vain kerran vaikka niitä olisi ollut useita samassa ratkaisussa. Seuraavassa on jotain eniten esiintyneitä virheitä ja niistä tehtyjä vähennyksiä: ------------------------------------------------------------------------------------------------------ Vähennykset ------------------------------------------------------------------------------------------------------ yleiset: ======== - oliomuuttujien määrittelyjä -1p - Ei ole käytetty static-määrettä metodin otsikossa -1p - luokasta on unohtunut main(..)-metodin määritys tai luokan otsikko -1p - syötteet komentoriviparametreina -1p (epäkäyttäjäystävällistä) - Turhaa tiedostokäsittelyä -2p...-4p esim. File f = new File(..); f.exists()... BufferedReader(...) jne - Lue.rivi()-aksessoria ei käytetty vaan yritetään tehdä se itse BufferedReaderilla -2p - Lue.rivi()-metodin ympärillä try-catch vaikka se ei aiheuta poikkeusta -2p Syöttötiedoston luonti ======================== - Parametria ei saada käyttäjältä tai se ei ole String-tyyppinen -1p - Poikkeuksen sattuessa kysytään tiedostonnimeä uudelleen niin että jää ikuiseen luuppiin -1p - Vain toinen tiedosto luodaan ja toisesta kommentoidaan että se tehdään samoin -1p Tiedoston läpikäynti ==================== - Toistorakenne on virheellinen ja suoritus päättyy NullpointerExceptioniin -2p esim: do { rivi = syotto.lueRivi(); if (rivi.indexOf("joulu"))..... // ^ nullpointerexception kun rivi == null while (rivi != null); - Useita lueRivi()-kutsuja per yksi toistokierros -2p esim: while(tiedosto.lueRivi() != null) { rivi = tiedosto.lueRivi(); if (rivi.indexOf("..") ... } - Vain toinen tiedosto läpikäydään ja toisesta kommentoidaan että se tehdään samoin -1p Rivin jouluisuustarkistus ========================= - Jouluisten rivien lukumäärän sijaan lasketaan jouluisten sanojen lukumäärä, kuitenkin toimivasti -2p - lasketaan sanojen määrää ja sekin menee väärin -3p - jouluisen rivin tarkistamiseen käytetään jotain keksittyä Stringin aksessoria, eikä kerrota miten se toimii -2p Esim: rivi.includes("joulu") rivi.substring()=="joulu" rivi.equals("pukki") rivi.exists("kuusi") rivi.compareTo("pukki")>-1 - if (rivi on jouluinen).... -4p - Seuraavassa esimerkissä tarvitaan break-käsky keskeyttämään for-toisto. Miksi? for (int i = 0; i < JOULUSANAT.length; i++) { if (rivi.indexOf(JOULUSANAT[i]) >=0) { jouluRivit++; break; // jos break puuttui -1p } } Tulosten vertailu ================= - Tulostetaan montako jouluista riviä kussakin tiedostossa on, mutta ei tehdä vertailua kummassa on enemmän -1p - Unohdetaan se vaihtoehto että molemmissa voi olla yhtä monta jouluista riviä -1p Poikkeuskäsittely ================= - Ei poikkeuskäsittelyä ollenkaan -8p - Main-metodin otsikossa Throws Exception tai yksi try-catch koko ohjelman rungon ympärillä -6p - Tiedoston luonnin ympärillä ei ole try-cathiä -4p - Tiedoston läpikäsittelyn ympärillä ei ole try-catchiä -4p - Tiedoston luonnin epäonnistuessa suoritusta jatketaan -4p. esim: try { luo eka tiedosto... } catch(Exception e) { S.o.p("Ei onnistunu, jatketaan silti"); } try { luo toka tiedosto... } catch(Exception e) { S.o.p("Ei onnistunu, jatketaan silti"); } - Tiedoston lukemisen epäonnistuessa yritetään jatketaan lukemista -4p esim: do { try { rivi = tiedosto.lueRivi(); if (joulutarkistus)... ... } catch(Exception e) { S.o.p("Tuli virhe! luetaan seuraava rivi"); } } while(rivi!=null) - Tiedoston lukemisen epäonnistuessa siirrytään seuraavan tiedoston lukemiseen, jolloin jouluisten rivien vertailutulokset voivat olla virheellisiä -3p esim: try { while((rivi=tiedosto1.lueRivi())!=null) { if (joulutarkistus)... rivit1++; } catch(Exception e) { S.o.p("Jokin meni pieleen, luetaan seuraava tiedosto"); } - Jos kuitenkin ilmoitetaan että tulokset ovat virheellisiä tai rivien lukumäärä tulkitaan negatiiviseksi, tulee vain -1p - Tiedostot luodaan saman try-cathin sisällä -1p - Tiedostot käsitellään saman try-cathin sisällä -2p - Yhden Tiedoston luonti ja käsittely saman try-cathin sisällä -3p - Poikkeukset suoritetaan mallikkaasti mutta catcheissä ei ole tulostuksia -2p - Paha syntaksivirhe catchissä (parametri puuttuu tms) -1p ---------------------------------------------------------------------------- seuraavista moitittiin mutta ei menettänyt pisteitä ---------------------------------------------------------------------------- - import-määreen käyttö turhaan - pienet syntaksivirheet, puolipisteen tai sulkeen puuttuminen - lohkon (esim Try{...}) sisällä määritettyä muuttujaa käytetään lohkon ulkopuolella, jolloin ei mene kääntäjästä läpi. - muuttujalla ei ole alkuarvoa, ja arvo asetetaan vasta lohkon sisällä jolloin ei mene kääntäjästä läpi. ---------------------------------------------------------------------------- Arvosanajakauma ---------------------------------------------------------------------------- 0 ********* (9kpl) 1 ***** (5) 2 ******* (7) 3 ******* (7) 4 ***** (5) 5 ************** (14) 6 ************** (14) 7 ***** (5) 8 ******* (7) 9 ********** (10) 10 ******** (8) 11 *********** (11) 12 ****** (6) 13 ********* (9) 14 ********** (10) 15 ****************** (18) 145 vastausta joiden keskiarvo on 8,24 / 15 pistettä Jos nollan pisteen vastaukset jätetään pois, keskiarvo on 8,79 / 15 pistettä