undantag.xml

Denna kod är public domain. Om ni hittar fel eller vill ändra något i koden blir jag jätteglad om ni skickar dessa ändringar till jesper [at] fantasi [punkt] se.


<?xml version="1.0"?>
<article category="software" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="article.xsd">
  <keyword>Undantag</keyword>
  <keyword>Exception</keyword>
  <keyword hidden="true">try</keyword>
  <keyword hidden="true">catch</keyword>
  <keyword hidden="true">throw</keyword>
  <topic>Exceptions / Undantag</topic>
  <ingress>
    Exceptions, eller undantag som det heter på svenska, skapar man då
    man stöter på något som är så fel eller ovanligt att man inte kan
    fortsätta med det man skulle göra. I språk som Java säger man att
    man kastar undantag (throw), i andra språk kan det heta raise.
    Man höjer alltså en flagga för att signalera att metoden inte
    avslutades så som den normalt brukar avslutas.
  </ingress>
  <text>
    Om man till exempel ska skriva något i en fil men upptäcker att
    filen inte finns eller att disken är full så är det inte säkert
    att metoden som ska skriva till filen vet hur den ska hantera
    denna märkliga situation. Den kastar då ett undantag till den som
    anropade metoden och låter anroparen besluta vad som ska göras.
  </text>
  <text>
    Undantag skickas inte via de vanliga returvärdena från en metod,
    utan normalt finns en separat kanal för att hantera dessa.  När
    ett undantag kastas kommer koden därefter att hoppas över.  Man
    hoppar frammåt i programmet tills dess att man hittar en speciell
    kodbit som hanterar felet som uppstått. Det är vanligt att
    programmeringsspråk har ett speciellt sätt att kapsla in kod som
    kan generera undantag så att det vet direkt var någonstans koden
    som hanterar det finns. Om koden för att hantera undantaget finns
    i samma metod som det genereras så hanteras det lokalt, annars
    kommer metoden att avslutas och undantaget skickas vidare till den
    anropande koden.
  </text>
  <codebox title="Exempel i Java">
    try {
       FileOutputStream foStr = new FileOutputStream("foo");
    }
    catch (FileNotFoundException e) {
       System.err.println("File not found!");
    }
  </codebox>

  <subtopic>Hur och var undantag bör hanteras</subtopic>
  <text>
    Mitt generella tips när det gäller var man ska fånga undantag är:
    Fånga och hantera undantag så tidigt som möjligt. Om du kan
    hantera undantaget i samma metod som orsakar det så ska du fånga
    det där. Skicka inte vidare undantag som kod utanför metoden inte
    behöver känna till. Att läcka undantag är att avslöja
    implementationsdetaljer, och det ska man aldrig göra.
  </text>
  <text>
    Om du försöker öppna en fil och den till exempel inte finns kommer
    du få ett <code>FileNotFoundException</code> från Java.  Om detta
    inträffar i en metod där du har möjlighet att åtgärda felet (till
    exempel genom att skapa filen eller öppna någon annan fil) så är
    detta rätt ställe att ta hand om det. Fånga det då enligt exemplet
    ovan och gör rätt sak. Det är i princip aldrig rätt sak att
    ignorera undantag genom att fånga dem och lämna hanteringskoden
    tom. Undantag genereras för att något har gått snett, att ignorera
    detta är nästan alltid en bugg.
  </text>
  <text>
    Om det inte går att hantera undantaget på något vettigt sätt i den
    metod där det genereras så bör det skickas vidare till den
    anropande koden. I det fallet fångar man inte undantaget alls,
    utan deklarerar att metoden kan kasta ett undantag med
    <code>throws</code>.
  </text>
  <codebox>
    public void readFile() throws FileNotFoundException
    {
       FileOutputStream foStr = new FileOutputStream("foo");
       ...
    }
  </codebox>

  <text>
    Om man ska sätta en hel metod inom <code>try{}</code> eller bara
    det specifika anrop som genererar undantaget beror helt på vad
    metoden gör i övrigt och hur den ser ut rent estetiskt. Om man har
    flera anrop som kan generera undantag och alla dessa ska hanteras
    på samma sätt så är det ju vettigt att sätta alla dessa anrop inom
    samma <code>try{}</code> så att man bara behöver skriva
    hanteringskoden på ett ställe. Om det är flera anrop som genererar
    olika undantag som ska hanteras på olika sätt så tycker inte jag
    att de ska sitta i samma <code>try{}</code> även om det är möjligt
    i de flesta fall. Egentligen handlar det nog mest om en
    koddesign-fråga om man vill sätta hanteringskoden nära stället där
    undantaget genereras eller om man vill sätta all hanteringskod för
    en hel metod i slutet av metoden. Det viktigaste är ju att koden
    blir lättläst, så om det stökar till i koden att sätta en massa
    hanteringskod överallt så är det kanske bättre att sätta allt sist
    genom att sätta hela metoden i samma <code>try{}</code>.
  </text>

  <subtopic>När ska man använda undantag?</subtopic>
  <text>
    Att kasta undantag är som namnet antyder något man endast gör i
    undantagsfall. Fel som kan hanteras lokalt bör också hanteras
    lokalt, man ska inte lasta över ansvaret på någon annan metod om
    det inte är nödvändigt. Detta betyder även att man inte ska
    hantera andras fel. Får man till exempel felaktiga indata så är
    det högst motiverat att tala om att detta skedde, även om man kan
    hantera felet själv. Felaktiga indata är oftast något som den
    anropande metoden vill veta om eftersom det antyder att något är
    fel på riktigt. Att kasta ett undantag är ett utmärkt sätt att
    hantera felaktiga indata.
  </text>
  <text>
    Det finns två regler man aldrig bör bryta emot:
  </text>
  <formulabox>
    1. Fånga aldrig undantag om du inte vet varför de uppstår.
  </formulabox>
  <text>
    Om man inte vet varför ett undantag uppstår är det ganska troligt
    att man inte kan hantera det på ett vettigt sätt.  Om man får ett
    undantag tillbaka från ett metodanrop så betyder det att något
    gått fel där. En felaktig hantering av detta undantag (till
    exempel att ignorera det) är det samma som att gömma fel i
    programmet. Kan man inte hantera felet är det bättre att skicka
    det vidare.
  </text>
  <text>
    Som utvecklare är det ganska vanligt att man sitter längst ut i
    anropskedjan. Det vill säga man använder biblioteksfunktioner som
    kan generera undantag, men det finns ingen anropande kod utanför
    den egna som kan hantera felen. Regeln gäller naturligtvis även då
    - fånga inte undantaget om du inte vet varför det uppstår. Men då
    det inte finns någon att skicka vidare felet till blir du så illa
    tvungen att själv ta reda på vad som faktiskt händer och hantera
    undantaget på riktigt.  Den som ser detta som en dålig sak kan
    sluta läsa nu och bör överväga att låta bli att programmera.
  </text>
  <formulabox>
    2. Fånga rätt typ av undantag.
  </formulabox>
  <text>
    Den andra regeln är också till för att förhindra att man gömmer
    fel i sitt program. Säg till exempel att man (i Java) vill ta hand
    om <code>FileNotFoundException</code>. Då är det just detta man
    ska fånga, inte det mer generella <code>IOException</code> vilket
    kanske kan kännas lockande ibland.  Att fånga
    <code>IOException</code> och bara hantera felet att filen inte
    hittas gör att saker kommer att gå sönder om andra I/O-fel
    inträffar. Av samma skäl fångar man därför aldrig
    <code>RuntimeException</code> eller <code>Exception</code>.
  </text>
  <text>
    Regel två vill även förhindra att man fångar undantag av typen
    <code>NullPointerException</code> och
    <code>ArithmeticException</code>.  Dessa indikerar att man har ett
    fel i sitt program och man ska inte fånga dem, man ska se till att
    de aldrig uppstår! <code>NullPointerException</code> får man om
    man försöker följa en referens som är <code>NULL</code> och
    <code>ArithmeticException</code> får man till exempel vid division
    med noll.
  </text>
  <text>
    Man ska alltid se till att hantera de fel som man vet kan uppstå.
    Innan man utför en division ska man alltid försäkra sig om att man
    inte har noll i nämnaren. Om man upptäcker att det är en nolla så
    kan det mycket väl vara rätt sak att hantera detta genom att
    generera ett undantag. Men det ska då vara ett som talar om att
    den som skickade in nollan gjorde fel, inte
    <code>ArithmeticException</code> som säger att du gjorde fel som
    försökte använda nollan i en division.
  </text>
  <text>
    Ibland ser man kod där undantag används som ett sätt att styra
    kontrollflödet i programmet. Man kastar undantag i vanligt
    förekommande situationer enbart därför att man inte orkar tänka ut
    en riktig lösning på ett problem. Detta är (enligt mig) fel och är
    ett exempel på dålig programmdesign.
  </text>
</article>