Felhantering I de flesta program finns eller uppstår ett antal olika fel. Man behöver en strategi för att hitta och rätta till sådana fel. Olika typer av fel som kan inträffa: syntaktiska fel Olika form av felaktig syntax, dvs brott mot de regler som beskriver hur språket ska se ut. Ex: dobble x = 2; // dobble finns inte som typ int b = true; // true är inte ett heltal b = a! b 2; //! och felaktiga Andra typiska sådana här fel är att glömma att deklarera variabler, glömma att ge dem startvärden och felstavningar av olika namn. Andra brott mot språkets regler Man kan också i programmet bryta mot ett antal olika regler som gäller. Exempelvis: double a = -2; System.out.println(Math.sqrt(a)); // a < 0!! int [] arr = new int[100]; for (int i=1; i <=100; i++) arr[i] = 0; // fel index! int p = 1; // beräkna 20! for (int i=1; i<=20; i++) p = p*i; // för stora tal double tal= 4.5, n = 0; System.out.println( kvot =, a/n); // nämnare 0! De flesta av dessa (men inte alla) avbryter programmet med en felutskrift. Sådana här fel upptäcker kompilatorn (javac) och ger ett felmeddelande vid kompilering. Felhantering 21 March 2002 1 Felhantering 21 March 2002 2 Logiska fel (tankefel) Programmet är tekniskt sett korrekt men gör fel saker, t. ex. räknar fel eller gör nåt annat som det inte borde göra, exempelvis skickar vattenräkningar på 2 miljarder kronor. Exempelvis: Fel på kompilatorer, javamaskinen eller datorn Händer ibland men inte nåt förstahandsproblem. Såna fel kan man ju inte göra så mycket åt annat än att byta dator, systemversion, javaversion eller nåt sånt. // // kontrollerar inte en inmatings giltighet. // här accepteras tex en längd av -7 meter // int längd; Std.out.println( Huru lång är min Herre? ); längd = Std.in.readInt(); Std.out.println( Längd: + längd); // hitta största värdet i en array // int [] arr = new int[200]; // ge värden till alla element i arr int max = arr[0]; for (int i=1; i < 200; i++) { if (arr[i] < max) max = arr[i]; Std.out.println( Största värde är: + max); // ger minsta värde, inte största Felhantering 21 March 2002 3 Felhantering 21 March 2002 4
Vad göra åt felen? Syntaxfel rättas till det gäller att lära sig att förstå vad kompilatorn meddelar och kunna relatera det till sitt program. import extra.*; public class AddSub { public static void main(string [] args) { double a; // fråga efter ett värde på a Std.out.proint("Ge a s värde ); a = Std.in.readDouble(); // fråga efter ett värde på b Std.out.print("Ge b s värde "); b = Std.in.readDouble(); // räkna ut summa och skillnad double sum,diff; sum = a + b; diff = a - b; // skriv ut Std.out.printnl("Summan blev " + sum + " skillnaden blev " + diff); Kompilera detta >Javac AddSub.java AddSub.java:9: unclosed string literal Std.out.proint( Ge a s värde ); AddSub.java:10: ) expected a = Std.in.readDouble(); AddSub.java:15: cannot resolve symbol symbol : variable b location: class AddSub b = Std.in.readDouble(); AddSub.java:20: cannot resolve symbol symbol : variable b location: class AddSub sum = a + b; AddSub.java:21: cannot resolve symbol symbol : variable b location: class AddSub diff = a - b; AddSub.java:25: cannot resolve symbol symbol : method printnl (java.lang.string) location: class extra.extendedwriter Std.out.printnl( Summan blev + sum + 6 errors > Felhantering 21 March 2002 5 Felhantering 21 March 2002 6 De flesta andra brott mot språkets regler leder till av programmet avbryts med en felutskrift. Man kan få en hel del information ur denna utskrift. Exempel: public class Noll { private int a, b; public Noll() { a = 0; b = 0; public Noll(int a, int b) { this.a = a; this.b = b; public int kvot() { return a/b; public static void main(string [] args) { Noll n = new Noll(4,0); System.out.println(n.kvot()); Hur lagar jag mina trasiga program? Generell metod som ofta fungerar: I mån av tillgång, använd huvudet. Andra bra metoder: Titta på programmet och förklara för någon annan vad det gör. Lägg in utskrifter i programmet som hjälp att förstå vad det gör Använd mer avancerade felsökningshjälpmedel, avlusare av olika slag. javac Noll.java java Noll Exception in thread main java.lang.arithmeticexception: / by zero at Noll.kvot(Noll.java:13) at Noll.main(Noll.java:19) Felhantering 21 March 2002 7 Felhantering 21 March 2002 8
Varför producerar man felaktiga program? Programspråk är ganska abstrakta, kan vara svårt att formulera sig på ett naturligt sätt Komplicerade algoritmer kan vara svåra att få korrekta Komplexiteten växer snabbt med programstorleken. Det gör att problemen snabbt växer dig över huvudet. Inmatning är svår att hantera på ett säkert sätt Slutsatser av detta? Arbeta med moduler som är små och som har en väldefinierad uppgift. Då är felmöjligheterna färre och storleken gör det hela hanterbart. Testa varje del innan du fortsätter med nästa. Eftersom varje modul i Java är en eller annan klass så innebär detta att skriva ett huvudprogram i klassen. Felhantering 21 March 2002 9 Felhantering 21 March 2002 10 Var ska jag börja? Börja med de enklaste klasserna, exempelvis kan vi i partikelproblemet börja med en vektorklass. Utveckla den och testa den. Bygg sedan nästa klass om använder denna och fortsätt att testa. Detta sätt att arbeta nedifrån och upp kallar vi på god svenska bottom-up. Fördelen är att det är enkelt att bygga en bit i taget och enkelt av testa. Nackdelen är att det kan vara svårt att ha en vision om hur det färdiga systemet ser ut. Alternativt kan man börja på en övergripande nivå. I partikelexempet börjar vi då med en simulering av det hela. Skapa en box full med partiklar, skapa sedan en simuleringsloop. När vi gjort detta måste fundera över hur det ska gå till att skapa boxen m. m. Fördelen med detta är att vi får en klar bild av hur det hela är tänkt att fungera i grova drag. Svårigheten är att testa på ett bra sätt. För att kunna göra detta måste man tillhandahålla dummies för detaljerna tills dess man skrivit skarpa moduler. Denna metod kallas på lika god svenska top down. Felhantering 21 March 2002 11 Felhantering 21 March 2002 12
Tänk först, programmera sedan! När man bygger ett hus så börjar man inte spika lite nåt hörn på måfå för att sen se vad det kan bli för något, man startar med att göra en ritning. När man skriver ett program så utgår man från ett problem som ska lösas. Innan kod skrivs måste en lösning av problemet skapas och skrivas ned. En analys av lösningen och uppdelning i olika moduler ska göras. Modulernas innehåll ska skissas och en kontroll av lösningens korrekthet måste göras. Det finns formella metoder att göra detta OOADmetoder, det finns också speciella verktyg att dokumentera detta. I enkla problem duger enklare metoder. Felsökning: Om vi nu tömt ut alla intellektuella möjligheter för felsökning så återstår mera tekniska metoder. Att lägga in utskrifter är en bra metod. Det gäller att veta vad som är viktigt och relevant för problemet. Ingen blir lycklig av att få hundratals sidor med lustiga siffror. Man kan studera programflödet genom att helt enkelt lägga in små testutskrifter på olika ställen i programmet.då kan man spåra datorns framfart i koden och se hur programmet utförs. Man kan studera vissa variabler och skriva ut deras värde på olika ställen för att se när och varför den får det värde som den har. Felhantering 21 March 2002 13 Felhantering 21 March 2002 14 Vid mer avancerade felsökningar använder man olika hjälpmedel. Det enkla JDK innehåller en enkel avlusare jdb som kan användas i enklare fall. För mer avancerat arbete behöver man en riktig utvecklingsmiljö (ex Forte, Visual J++, Visual Cafe el dylikt) public class Hello { public static void main(string [] arg) { System.out.println( Welcome to Java ); >javac -g Hello.java >jdb Hello Initializing jdb... 0xfd2c0ca8:class(Hello) > stop in Hello.main Breakpoint set in Hello.main > run run Hello Breakpoint hit: Hello.main (Hello:3) main[1] running... main[1] next Välkommen till Javamain[1] Breakpoint hit: Hello.main (Hello:2) main[1] next Felhantering 21 March 2002 15 Felhantering 21 March 2002 16
Breakpoint hit: main[1] sun.tools.debug.mainthread.run (MainThread:56) main[1] next main[1] Breakpoint hit: sun.tools.debug.mainthread.run (MainThread:54) main[1] next main[1] Hello exited Hur hantera då Java fel som uppstår under arbetet? Genom att skapa undantag. Vad är en undantag? När ett fel uppstår skapas ett objekt som beskriver felet. Detta objekt är av typ Exception, på svenska undantag. Java avbryter programmet vid den punkt där felet inträffade och signalerar att ett undantag kastats. Det kan sedan finnas en hanterare som fångar undantaget, hanterar det och fortsätter programmet. Om ingen hanterare finns används en standardhanterare som skriver ett felmeddelande och avbryter programmet. Felhantering 21 March 2002 17 Felhantering 21 March 2002 18 Du måste i princip alltid i Java tala om vad som ska hända med eventuella fel som kan inträffa. Du har alltid två alternativ: Ta hand om felet. Skicka det vidare till den som anropade mig. Om du är befinner dig i main så innebär alternativ två att programmet avbryts. Man kan tänka sig kombinationer av ovanstående också. T. ex. att man släpper någon resurs, stänger en fil eller nåt sånt och sedan skickar fel vidare. Det finns undantag från den obligatoriska felhateringen. Javas undantag utgår från basklassen Throwable. Den har sedan två härledda klasser Error och Exception. Undantag av typ Error kommer från Javamaskinen eller från någon annan basdel och kan vara saker som slut på minne och sådant. Kan inte hanteras av dig. Undantag av typ Exception kan ytterligare finindelas med flera härledda klasser. En sådan är RuntimeException. De undantag som hör dit är alla vanliga såsom felaktigt arrayindex, division med noll, kvadratrot ur negativt tal etc. Dessa kan hanteras men måste inte. Felhantering 21 March 2002 19 Felhantering 21 March 2002 20
Övriga typer måste hanteras. Varje metod som skapar ett undantag som den inte själv hanterar måste tala om detta i deklarationen så att omvärlden är beredd. Exempelvis har metoden readline som finns i klassen BufferedReader en deklaration som ser ut som: public String readline() throws IOException {... Vill jag använda denna metod måste jag ange vad jag ska göra med eventuella fel. Jag kan t. ex. nu göra public class Person { private String namn; private BufferedReader b = new BufferedReader( new InputStreamReader(System.in)); public Person() { public läsnamn() { // hantera fel själv try { namn = b.readline(); catch (IOException e){ System.out.println( I/O-fel ); Felhantering 21 March 2002 21 Felhantering 21 March 2002 22 Nyckelordet try används för att skapa ett block av satser som är beredda att hantera alla typer av fel. I slutet av detta block finns en eller fleracatch-block, ett för varje typ av fel. Här har vi bara ett catch, för undagtaget IOException. Det betyder rent generellt att något fel vid ineller utmatninginträffat. Om ett sådant fel inträffar här så skrivs ett felmeddelande ut och programmet fortsätter efter catch-blocket. Om ett fel inträffar tilldelas inget nytt namn. Jag kan alternativt göra public class Person { private String namn; private BufferedReader b = new BufferedReader( new InputStreamReader(System.in)); public Person() { public läsnamn() throws IOException { namn = b.readline(); Nu tar jag inte själv hand om felen utan skickar det vidare. Den som anropar denna metod måste ta hand om felen. Felhantering 21 March 2002 23 Felhantering 21 March 2002 24
Nu kan vi t. ex. göra en metod som läser ett heltal och som inte avbryter bara för att vi skriver saker som inte är giltiga heltal. Antag att vi har samma BufferedReader som tidigare. public int readint() { int tal=0; try { tal = b.readline().parseint(); catch (IOException e) { System.out.println( Ellol ); tal = 0; catch (FormatNumberException e) { System.out.println( Fel ); tal = 0; return tal; En bättre variant är ju att se till att det alltid blir ett giltigt svar. Det gör vi genom att försöka läsa igen. Vi kan ALDRIG återvända till den plats där felet uppstod. Vi fortsätter alltid efter det catch-block som hanterar felet. Det är inget som hindrar att jag lägger en repetition runt alltihopa dock. Exempelvis kan jag göra en oändlig repetition som avbryts om det gått bra. Det kan bli yngefär så här: När det blir fel ges ett meddelande och noll returneras. Felhantering 21 March 2002 25 Felhantering 21 March 2002 26 public int readint() { int tal=0; while (true) { try { tal = b.readline().parseint(); break; // alles gut catch (IOException e) { System.out.println ( Fel, gör ett nytt försök ); catch (FormatNumberException e) { System.out.println ( Fel, gör ett nytt försök ); return tal; Nu läser vi en rad och försöker konvertera den till ett heltal. Om det går bra så lämnar vi repetitionen. Annars blir det en felutskrift och vi försöker igen. Kan jag använda detta själv på nåt vis? Du kan själv kasta undantag när felaktiga saker inträffar. Exempelvis: import extra.*; // en klass om innehåller en del egenskaper hos en person public class Person { protected String namn = "NoName"; protected String adress = ""; protected int födelse_år; protected int inkomst; // konstruktor där alla data anges public Person(String n, String a, int år, int ink) { if (n.equals("nisse")) throw new IllegalArgumentException("Otillåtet namn"); namn = n; adress = a; födelse_år = år; inkomst = ink; // standardperson public Person() { public static void main(string [] args) { Person p2, person; // skapa en personreferens // skapa en ny slumpmässing person person = new Person("Göran Person", "Rosenbad", 1949, 10); p2 = new Person("Nisse", "Rosenbad", 1949, 10); // skrivut data om denne person person.skrivut(); ; Felhantering 21 March 2002 27 Felhantering 21 March 2002 28
java Person.java java Person Exception in thread main java.lang.illegalargumentexception: Otillåtet namn at Person.<init>(Person.java:15) at Person.main(Person.java:127) Nu utlöstes ett undantag i konstruktorn. Eftersom det inte finns någon catch i main så avbryts programmet. Om du vill hantera detta så blir det try och catch vid skapandet av personer. Om du vill ha en finare kontroll över felen kan du skapa dina egna undantag. Du gör dessa genom att bygga ut lämplig basklass. Exempvis så här: public class FelaktigtNamn extends RuntimeException { ; public FelaktigtNamn() { public FelaktigtNamn(String s) { super(s); IllegalArgumentException är av typen RuntimeException som inte kräver hantering. Felhantering 21 March 2002 29 Felhantering 21 March 2002 30 Ger dig en ny felklass som du kan använda, t. ex. så här: import extra.*; // en klass om innehåller en del egenskaper hos en person public class Person { protected String namn = NoName ; protected String adress = ; > javac Person.java > java Person Exception in thread main FelaktigtNamn: Otillåtet namn: Nisse at Person.<init>(Person.java:15) at Person.main(Person.java:127) Hanteras precis som tidigare. protected int födelse_år; protected int inkomst; // konstruktor där alla data anges public Person(String n, String a, int år, int ink) { if (n.equals( Nisse )) throw new FelaktigtNamn( Otillåtet namn: + n); namn = n; adress = a; födelse_år = år; inkomst = ink; Felhantering 21 March 2002 31 Felhantering 21 March 2002 32