Felhantering & undantag

Inledning

En nybörjare inom programmering är oftast glad och nöjd när programmet fungerar och uppgiften är löst. Men fungerar verkligen allt eller finns det något som kan gå fel?

Skrapar man på ytan så kan man oftast hitta många situationer där programmet faktiskt kraschar. En erfaren programmerare kan oftast förutse situationer som kan orsaka problem och vidtar lämpliga förebyggande åtgärder. En viktig strategi är att sköta felhantering.

Vi ska i denna artikel lära oss att göra stabila program genom två strategier: felhantering med hjälp av undantag samt att planera för fel.

Ett oskyldigt exempel

Exempel 1

class Program
{
	static void Main(string[] args)
	{
		Console.Write("Ange din inkomst: ");
		int inkomst  = int.Parse(Console.ReadLine());
		Console.Write("Ange antal timmar: ");
		int timmar = int.Parse(Console.ReadLine());

		Console.WriteLine("Din timlön blev: " + (inkomst / timmar) + " kr/h");

		Console.ReadKey();
	}
}

What could possibly go wrong...?

En hel del faktiskt. Det mest uppenbara är att vi inte matar in något som kan tolkas som ett heltal. Vi får då ett undantag eller exception som det heter på engelska. Just i detta exempel får vi ett undantag av typen FormatException och programmet kraschar på ett ofint vis.

bild

Om din känsla nu är att Parse är en känslig metod så har du helt rätt! Men vad mer kan gå snett när vi använder Parse?

Genom att använda hjälpen (F1 vid metoden) och läsa dokumentationen för Parse så hittar vi att följande undantag kan kastas.

bild

  • ArgumentNullException
  • FormatException
  • OverflowException

Undantag kastas som ett rop på hjälp då något oväntat inträffar och som inte kan hanteras på den nivå där felet inträffar. Parse kan ju inte veta hur vi vill hantera dessa fel. Alltså kastas det undantag vidare till vår kod.

Innan vi går in på hur undantag kan fångas och hanteras så ska vi göra felhantering utan undantag.

Att planera för fel

Vid fri inmatning kan vi förvänta oss att det kan komma vad som helst. Istället för Parse kan vi använda TryParse.

TryParse behöver returnera två saker: lyckades det (true / false) samt resultatet. Detta brukar vara första gången man stöter på out på en inparameter. out är en konstruktion för att kunna returnera data via inparametrar. Den variabel som anges som out kommer efter anropet att få ett värde och det är upp till TryParse att se till att ett värde tilldelas.

Vi rekommenderar att använda TryParse vid tolkning av inmatad text. Vår regel är att om det inte är en speciell händelse så ska vi i koden förutse att det kan hända. För speciella händelser så kan vi använda undantag.

Skriver vi om koden med TryParse så måste vi göra något om inmatningen blir fel. Vad sägs om att informera användaren om att det blev fel och sedan försöka igen?

Efterom felaktig inmatning kan upprepas så behövs en loop för varje inmatning.

Felhantering TryParse

class Program
{
	static void Main(string[] args)
	{
		bool inmatat = false;
		int inkomst = 0;
		int timmar = 0;

		while (!inmatat)
		{
			Console.Write("Ange din inkomst: ");
			inmatat = int.TryParse(Console.ReadLine(), out inkomst);
			if(!inmatat)
				Console.WriteLine("mata gärna in ett heltal tack!");
		}

		inmatat = false;
		while (!inmatat)
		{
			Console.Write("Ange antal timmar: ");
			inmatat = int.TryParse(Console.ReadLine(), out timmar);
			if (!inmatat)
				Console.WriteLine("mata gärna in ett heltal tack!");
		}

		Console.WriteLine("Din timlön blev: " + (inkomst / timmar) + " kr/h");
		Console.ReadKey();
	}
}

Vid en outmåste ett värde tilldelas. Vad får en int-variabel för värde om inmatningen skulle gå snett med TryParse? Jo den får värdet 0.

Eftersom vi har loop'ar nu så går programmet inte vidare om inte användaren matar in ett korrekt heltal. Här kan man också ana att vi skulle kunna göra en metod för inmatning av heltal som sköter själva loop'en och felrapporteringen för att till slut returnera ett heltal.

Det oväntade

För att jobba med undantag behöver vi lära oss de reserverade orden: try, catch, finally och throw. Om vi direkt går på ett exempel som använder alla dessa:

Try catch finnaly

	try
	{
		// Placera kod här som KAN ge ett oväntat undantag
	}
	catch (Exception e)
	{
		Console.WriteLine(e.Message);
		throw;
	}
	finally
	{
		// Om något går fel så hamnar vi till sist alltid här
	}

Med try-blocket "ramar" du in den kod som kan orsaka ett undantag. "Try" innebär att vi försöker köra detta block. Det är inte säkert vi lyckas eftersom ett undantag, Exception, kan inträffa vilket gör att vi hamnar i catch-delen.

I catch-delen kan vi nå undantaget via en variabel, e i exemplet. Undantaget innehåller information om vad som inträffat, t.ex. beskrivning, stack trace, etc. Genom att skriva ut e.Message så kan vi låta användaren få ta del av den tekniska beskrivningen av felet.

Genom att upprepa flera olika catch-block med olika sorters Exception-klasser kan vi agera olika på olika fel. Detta är styrkan i felhantering med undantag. Vi ska strax se ett exempel på detta.

throw kastar det aktuella undantaget vidare. Finns det ingen högre nivå som fångar undantaget så kraschar programmet. Ibland vill man kanske vidta någon åtgärd men ändå skicka felet vidare. Det går också att skicka (throw) ett annat sorts exception eller ett egendefinierat exception.

finally är ett block som alltid körs sist oavsett vilket fel som inträffade. Detta block behöver inte deklareras och används ganska sällan.

Vi kikar på ett exempel som agera olika på de tre sorters undantag som Parse kan kasta.

Exempel flera catch

	try
	{
		int tal = int.Parse(Console.ReadLine());
	}
	catch (ArgumentNullException e)
	{
		//om inparameter skulle vara null
	}
	catch (OverflowException e)
	{
		//om talet är för stort eller för litet att 
		// rymmas i variabeltypen
	}
	catch (FormatException e)
	{
		//om formatet, inmatningen, är felaktig
	}
	catch (Exception e)
	{   
		// "default" stället om inget annat matchaar
	}

Undantaget hamnar i det kodblock som har den mest specifika undantaget. Med det menas den sortens undantag som har den längsta arvskedjan till klassen Exception. Om inget matchar så är det Exception som fångar allt.

Du kan också skriva en catch utan att deklarera någon Exception alls! Det är inte säkert du behöver veta något om felet.

Eftersom Parse endast kan kasta tre sorters undantag, och vi har täckt in alla, så kommer inte Exception blocket att köras.

TryParse vs Try Catch

Finns det alternativ till undantag så bör man oftast välja dessa. Att förlita sig på undantag kallas to code by exception och anses fel. Vi listar här några för- och nackdelar med båda strategier.

TryParse

  • + Snabb hantering.
  • + Tydligare intentioner om vad koden vill uppnå.
  • - mindre info om själva orsaken till felet.

Undantag

  • + Mer detaljerad info om felet.
  • + Felmeddelanden som ibland skulle kunna vara begripliga för användaren.
  • + Att förutse och kolla efter alla olika sorters fel går oftast inte. Undantag är en lösning då.
  • - Hög "overhead" och extremt dålig prestanda.

Undantag ska användas restriktivt och bara tillämpas när något oväntat kan inträffa. Att en användare gör en felaktig inmatning är förväntat beteende och därför bör du använda TryParse.

Saker att tänka på

Ett tomt catch-block

Ett tomt catch-block fångar tyst alla fel. Ingen kommer någonsin att få reda på vad som gått snett. En huvudregel är oftast att rapportera eller logga fel när de inträffar. Att tysta ned dem kan vara förödande i en produktionsmiljö!

Kasta egna exceptions

Undvik att skapa egna klasser baserade på Exception. Vanligtvis finns det redan klasser som matchar ditt behov i .NET.

Returnera hellre en felkod än att kasta ett undantag om du kan.

Avslutning

Undantag är något centralt som finns i många andra språk. Behovet att kunna signalera fel är viktigt.

Har vi glömt något?

I exempel 1 ovan så finns det fortfarande en möjlighet att krascha programmet.

Övningar

Övning 1

Skriv om exempel 1 i artikeln till att hantera fel genom att fånga undantag.

Övning 2

I exempel 1 ovan så finns det fortfarande en möjlighet att krascha programmet.

Vad är det som kan gå fel? Hur löser vi bäst problemet?

Övning 3

Vilka exceptions kan du få vid en vanlig Console.WriteLine()?

Kolla i hjälpen med F1 och läs dig till svaret.

Övning 4

När kan finally vara riktigt användbar? Googl'a och se om du hittar ett bra exempel.

Övning 5

Skriv ett program som endast slänger ett undantag där du själv har skrivit felmeddelandet. Alltså undersök hur man skriver för att kasta ett undantag av valfri sort.

Övning 6

I artikeln nämns att en metod skulle kunna införas i exemplet "Felhantering TryParse". Skriv denna metod och för in i lösningen.

Övning 7

Konstruera ett program som ber om inmatning för "start", "stop" och "steg". Programmet skall sedan baserat på inmatningen göra en for-loop och skriva ut de tal som styr for-loop'en enligt bild nedan.

bild

Programmet ska vara tåligt mot fel och klara felaktiga inmatningar.

Lämna ett svar

Din e-postadress kommer inte publiceras. Obligatoriska fält är märkta *

Scroll to top