Observera att detta är en arbetsversion av sidan!
Dölj style3dev.css för att bli av med annoteringarna!

Lektion 8: Felsökning och felhantering.

Moment: Felsökning och felhantering.
Begrepp som introduceras: assert och undantag (eng. exceptions).
Arbetsform: Denna lektion skall bara läsas och begrundas. Den görs lämpligen ej på schemalagd datalaboration. Kom ihåg råden när du arbetar med kommande uppgifter!
Uppskattad arbetstid: 2 tim
Redovisning: Ingen obligatorisk redovisning.

Fel kan inträffa på olika nivåer:

De två första typerna är vanligtvis lätta att hitta och rätta medan den tredje kan vara betydligt svårare.

Kompileringsfel

DrJava visar med gul färg var dessa inträffar. DrJava visar oftast bara ett fel i taget varför det behövs många omkompileringar för att avslöja alla fel.

Några typiska fel är

Felmeddelandena är oftast väldigt tydliga. Läs och tänk efter vad meddelandet säger!

Exekveringsfel

Dessa upptäcks av javamaskinen. Java kastar då ett så kallat undantag som meddelar var felet har upptäckts (klass, metod och rad). (DrJava gör det med röd färg.) Även om felmeddelandena oftast är mycket tydliga så är det inte alltid enkelt att säga vad de ursprungligen beror på.

De två vanligaste som nybörjarna drabbas:

Igen: läs och tänk efter vad meddelandet säger

För att hitta den verkliga orsaken behöver man ofta använda samma tekniker som man gör vid felaktiga resultat.

Fel resultat

Programmet kör och avslutar utan diagnoser men resultatet är inte vad man tror att det skall vara. (Det kräver naturligtvis att man tror sig veta vad resultatet skall bli - om man inte vet det så är det nästan ingen mening att testa...)

Två sätt:

  1. Ta reda på vad programmet verkligen gör genom att lägga in utskriftssatser (System.out.println("...")) som talar om var programmet är och vilka värden kritiska variabler har.
  2. Använd en debugger. En debugger är ett hjälpmedel för att spåra metodanrop och se när och till vad variabler ändras.
    För att lära dig använda DrJavas debugger finns en minilektion
    Obs! Beroende på; hur din dator är inställd är det inte säkert att man kan använda DrJavas debugger. Om du bara har installerat Java 8 kan det ibland hjälpa att installera den äldre versionen av Java JDK 7.

Den första punkten är enkel eftersom man inte måste lära sig något nytt men investeringen i att lära sig att använda en debugger betalar sig fort!

Det krävs mycket övning för att bli bra på felsökning.

Observera: En genomtänkt struktur, tydlig och konsekvent namngivning, klasser och metoder med väldefinierade uppgifter minskar mängden fel och underlättar felsökningen!

Skriv kontroller i programmet!

Ju tidigare man får en felindikation desto lättare är det att hitta orsaken. Program bör alltså skrivas så att de själva kontrollerar när något "konstigt" inträffar. Konstruktorer och metoder bör t ex alltid kontrollera att parametrarna har tillåtna värden. I vissa fall fångar ju Java sådana fel (typ NullPointerException) men i andra fall måste man göra det själv.

Exempel: Klassen Measurements från lektion 7

  1. Parametern max till konstruktorn (Measurements(int max)) måste rimligen ha ett positivt värde. Java kommer att kontrollera att värdet inte är negativt (man får ett NegativeArraySizeException om man försöker skapa en array med negativ storlek) så det kanske vi inte själva behöver kontrollera men vad händer om vi ger storleken 0 till konstruktorn? Prova med din egen kod! Kommer din add-metod att fungera?
  2. Parametern index till metoden get(int index) måste ligga inom det använda området av arrayen. Java kontrollerar att värdet inte går utanför arrayens storlek (mindre än 0 eller större än eller lika med arrayens längd) men vad händer om den är inom arrayen men utanför den använda delen?

    Det här är ett allvarligt fel eftersom vi kommer få ett felaktigt värde tillbaka (troligen 0) men ingen signal om att det är fel!

Hur ska koden signalera att något är fel?

Det finns några olika sätt:
  1. Returnera en felkod.
  2. Ge en felutskrift med System.out.println("...").
  3. Använda assert.
  4. Kasta ett undantag.

Diskussion av dessa alternativ:

  1. Returnera felkod: Man kan göra om en void till en metod som returnerar en felindikation (int eller boolean).
    Detta sätt kan användas för att returnera extra information till anroparen men det är ett mycket dåligt sätt att rapportera att metoden har misslyckats med sin uppgift.
    Problemet är att det inte finns någon garanti för att anroparen bryr sig om att undersöka felkoden. (Typisk programmeraruttalande: "Ja, jag vet, men jag hinner inte göra det just nu.").
  2. Felutskrifter: Enkelt att göra men det kan vara svårt att veta exakt var felet har inträffat (vilken rad i programmet, hur anropskedjan ser ut) samt vad programmet ska göra därefter. Den anropande koden får ju ingen information att något gått snett.
  3. Använda assert: Ett enkelt och effektivt sätt att kontroller att ett visst förhållande råder.

    Exempel: Konstruktorn till Measurements kan inledes på detta sätt:

    public Measurements(int max) {
        assert max>0;
        ...

    Om villkortet (max>0) är falskt så kommer programmet avbrytas med information om var felet har inträffat (metod, radnummer och anropskedja).

    Man kan lägga till en förklaring till felet. Exempel:

    public Measurements(int max) {
        assert max>0: "The parameter to Measurements must be > 0";
        ...

    Denna text kommer då skrivas ut om felet inträffar.

    Assertion-funktionen kan slås av och på. I DrJava är den automatiskt påslagen men i andra system kan man behöva ge det specificera det med flaggan -ea till javamotorn.

    En nackdel med denna metod är att programmet ovillkorligen avbryts. Den passar alltså bäst under programutvecklingen när man vill att programmet ska stoppa så fort ett fel har hittats.

  4. Kasta undantag: Att använda undantag (eng. exceptions) är det mest sofistikerade sättet. Då kan man få information om var felet inträffat och, beroende på vad som har hänt, välja var programmet ska fortsätta eller om det ska avbrytas.

    Denna mekanism behandlas utförligare i fortsättningskursen. Här påpekar vi bara några saker som ni är tvungna att använda i denna kurs:

    I vissa situationer måste man deklarera att undantag kan uppstå. Så är det t ex när man gör Thread.sleep() i de första lektionerna. Metoder som gör detta måste då i metodhuvudet deklarera throws InterruptedException vilket du gjorde i main-metoden.

    I nästkommande lektion ska du läsa indata från en fil. Då kommer du behöva deklarera throws IOException på vissa metoder.

    Det finns en mängd olika sorters undantag varav vissa måste deklareras med throws medan andra (som t ex NullPointerException och ArrayIndexOutOfBoundsException) inte behöver deklareras.

    De undantag vi hittills har nämnt har alla skapats av Java men vi kan också skapa egna. I stället för assert-satsen i konstruktorn till Measurements ovan kan man göra
    if (max <= 0) {
      throw new IllegalArgumentException("Measurements must have a positive size!");
    }

    (throw kastar ett undantag medan throws deklarerar att ett undantag kan kastas av metoden.)

    Eftersom man inte kan veta om assert-mekanismen är påslagen eller ej, rekommenderar Oracle detta sätt framför assert för att hantera felaktiga värden på parametrarna till publika metoder.

    I brist på kunskap om hur definierar egna undantagsklasser kan man, om man vill kasta ett undantag alltid använda klassen RuntimeException. Exempel: Metoden mean i klassen Measurements borde börja med koden

    if (this.stored() == 0) {
        throw new RuntimeException("Can't do a mean calculation of 0 elements");
    }
    och på motsvarande sätt i metoderna min, max och stdDev.

    Undantaget RuntimeException behöver inte deklareras med throws.

Viktiga råd

  1. Utveckla programmet stegvis!
  2. Skriv, kompilera och testa en metod i taget!
  3. Se till att du har körbara enheter så fort som möjligt även om inte alla metoder finns eller fullständigt gör sin uppgift!
  4. Skriv toString-metoder tidigt - de är användbara vid testning!
  5. Skriv ett testprogram parallellt med att du skriver din kod. Det finns ett enkelt ramverk kallat JUnit som stöds bl a av DrJava. Se testmetoden som användes i Measurements-klassen!
  6. Använd inte "kopiera och klistra" för att återanvända kod! Det är lätt att glömma att göra alla ändringar som skulle göras i den inklistrade koden. Om man upptäcker fel i den kopierade koden så glömmer man att ändra på alla ställen man klistrat in den.

    Skriv generella metoder och använd dessa i stället för att kopiera och klistra!


Gå till nästa lektion eller gå tillbaka

Valid CSS!