Refactoring: Byt ut felkod med exception och byt ut exception med Test
Förord
Johan Normén har tidigare skrivit om en refactoring ”Extract Method” som beskrev hur kod kan bli lättläsligare om koden separeras till flera metoder. Detta inlägg beskriver två andra refactorings nämligen ”Replace Error Code With Exception” och “Replace Exception with Test”Innehåll
Introduktion
Fel uppstår ofta, både i verkliga livet och i datorn. När saker går fel så behöver vi ofta göra något åt det. Ofta så får jag höra från andra utveckalre att exception ska undvikas pga prestanda skäl, och att det är bättre att returnera en felkod från en metod och hantera felkoden istället för att fånga en exception. Jag anser precis som Martin Fowler ”Att använda sig av metoder som att returerar felkoder, är ett programs motsvaighet till att ta livet av sig”. Exception är bra för att den skiljer på ”vanlig hantering” från ”felhantering”. Problemet med felkoder är att de måste dokumenteras för att andra utvecklare ska förstå vad varje kod betyder, de måste även skickas upp i anrops kedjan om så behövs (Excpetion skickas autmatiskt upp i anropas kedjan). Det är inte alltid lätt för ett progam att veta vad den ska göra med den felkod den får.
Många använder tyvärr exception på fel sätt. Exception ska användas då ett fel uppstår inte annars. Innan vi går vidare så vill jag göra dig uppmärksam om att exemplerna i detta inlägg används bara som exempel för att demonstrera när exception inte ska användas eller användas, metodens namn eller dess funktion har ingen betydelse här, så häng inte upp er på det, för exemplerna i denna artikel kan implementeras på olika sätt.
Replace Error Code With Exception
Replace Error Code With Exception ska användas när metoden returnerar en speciel kod för att indikera ett fel.
Här kommer ett exempel där vi ska dra av en summa från ett saldo. Metoden i exemplet kommer att returnera felkoden -1 om summan (amount) är större än saldot (_balance) annars 0 om allt har gått rätt till:
Problemet med denna metod är att den som använder sig av metoden måste veta att -1 betyder att summan är större än saldot och därför är det ett fel, och om metoden returnerar 0 så har allt gått rätt till. Eftersom metoden ska dra av en summa från ett saldo så får inte summa som skickas in som argument vara större än saldot. Detta ska inte inträffa om programmeraren har programmerat rätt. Med hjäp av ”Replace Error Code With Exception” så kan exmplet skrivas om till:
Om summan som skickas in som ett argument till metoden är större än saldot så resulterar det i ett fel. På så sätt får progrrammeraren reda på att hon har skickat in en summa som är större än sadot och bör ändra sin kod så att detta inte är möjligt.
Replace Exception with Test
Replace Exception with Test används när du kastar ett exception för ett tillstånd som anroparen skulle kunna testa först. Exceptions ska användas där ett fel kan uppstå. De ska inte agera som en ersättare för ett test av ett tillstånd. Om du kan förvänta dig att anroparen kontrollerar ett tillstånd innan en operationen anropas, så ska du tillföra ett test, och användare ska använda sig av det. Ett test används för att undvika att en exception ska uppså. Tex om vi dividerar med 0 så får vi DividedByZero exception, när en exception kastas så påverkar det prestandan. För att undvika onödiga exceptions så skulle vi i detta fall först göra ett test och se om värdet vi ska dividera med är 0, om så returnerar vi 0. Vi gör aldrig divitionen och på så sätt kommer inte ett exception att kunna uppså.
Låt oss se på ett exempel där en exception kan bytas ut mot ett test. Följande exempel använder sig av en array med värden för olika perioder (en period kan tex vara en månad), perioden vi anger är indexen för arrayen där värdet ligger lagrat. Om det finns ett värde för den angiven period så retunreras det det värdet, finns inte värdet så returnerar vi 0 vilket är ett standard värde för de perioder som inte har ett registrerat värde. Om den angiva perioden överstiger arrayens storlek så kommer vi få en IndexOutOfRange exception. Denna exception fångar vi om det skulle hända och retunerar värdet 0, annars så returnerar vi det värde den angivna perioden har:
Detta är ett typiskt fel många utvecklare gör. De använder sig av try och catch för att hantera fel som kan uppstå istället för att göra ett test innan för att undivka att en exception kastas. Om vi använder oss av ”Replace Exception with Test” på exemplet ovan så kommer resultatet att bli följande:
I detta example så om får vi inget exception om värder för perioden vi skickar in som argument inte finns registrerat, utan vi får värdet 0, detta för att vi gör ett test om den angivna perioden överstiger arrayen, om den gör det så returnerar vi 0.
Dett finns så många olika fall när exception ska använda och inte användas. Jag har beskrivit några fall och hoppas att utifrån dessa så har ni lättare för er att avgöra om en exception ska användas eller inte. Har ni frågor eller vill kommentara detta inlägg så är ni mycket välkommna att göra det. Jag vill höra alla era synpunkter och erfarenheter kring exceptions:
Fel uppstår ofta, både i verkliga livet och i datorn. När saker går fel så behöver vi ofta göra något åt det. Ofta så får jag höra från andra utveckalre att exception ska undvikas pga prestanda skäl, och att det är bättre att returnera en felkod från en metod och hantera felkoden istället för att fånga en exception. Jag anser precis som Martin Fowler ”Att använda sig av metoder som att returerar felkoder, är ett programs motsvaighet till att ta livet av sig”. Exception är bra för att den skiljer på ”vanlig hantering” från ”felhantering”. Problemet med felkoder är att de måste dokumenteras för att andra utvecklare ska förstå vad varje kod betyder, de måste även skickas upp i anrops kedjan om så behövs (Excpetion skickas autmatiskt upp i anropas kedjan). Det är inte alltid lätt för ett progam att veta vad den ska göra med den felkod den får.
Många använder tyvärr exception på fel sätt. Exception ska användas då ett fel uppstår inte annars. Innan vi går vidare så vill jag göra dig uppmärksam om att exemplerna i detta inlägg används bara som exempel för att demonstrera när exception inte ska användas eller användas, metodens namn eller dess funktion har ingen betydelse här, så häng inte upp er på det, för exemplerna i denna artikel kan implementeras på olika sätt.
Replace Error Code With Exception
Replace Error Code With Exception ska användas när metoden returnerar en speciel kod för att indikera ett fel.
Här kommer ett exempel där vi ska dra av en summa från ett saldo. Metoden i exemplet kommer att returnera felkoden -1 om summan (amount) är större än saldot (_balance) annars 0 om allt har gått rätt till:
public int WithDraw(int amount)
{
if( amount > this._balance)
return -1;
else
{
this._balance -= amount;
return 0;
}
}
Problemet med denna metod är att den som använder sig av metoden måste veta att -1 betyder att summan är större än saldot och därför är det ett fel, och om metoden returnerar 0 så har allt gått rätt till. Eftersom metoden ska dra av en summa från ett saldo så får inte summa som skickas in som argument vara större än saldot. Detta ska inte inträffa om programmeraren har programmerat rätt. Med hjäp av ”Replace Error Code With Exception” så kan exmplet skrivas om till:
public void WithDraw(int amount)
{
if( amount > this._balance )
throw new BlanaceException();
this._balance -= amount;
}
Om summan som skickas in som ett argument till metoden är större än saldot så resulterar det i ett fel. På så sätt får progrrammeraren reda på att hon har skickat in en summa som är större än sadot och bör ändra sin kod så att detta inte är möjligt.
Replace Exception with Test
Replace Exception with Test används när du kastar ett exception för ett tillstånd som anroparen skulle kunna testa först. Exceptions ska användas där ett fel kan uppstå. De ska inte agera som en ersättare för ett test av ett tillstånd. Om du kan förvänta dig att anroparen kontrollerar ett tillstånd innan en operationen anropas, så ska du tillföra ett test, och användare ska använda sig av det. Ett test används för att undvika att en exception ska uppså. Tex om vi dividerar med 0 så får vi DividedByZero exception, när en exception kastas så påverkar det prestandan. För att undvika onödiga exceptions så skulle vi i detta fall först göra ett test och se om värdet vi ska dividera med är 0, om så returnerar vi 0. Vi gör aldrig divitionen och på så sätt kommer inte ett exception att kunna uppså.
if( number == 0 )
return 0;
return 12/number
Låt oss se på ett exempel där en exception kan bytas ut mot ett test. Följande exempel använder sig av en array med värden för olika perioder (en period kan tex vara en månad), perioden vi anger är indexen för arrayen där värdet ligger lagrat. Om det finns ett värde för den angiven period så retunreras det det värdet, finns inte värdet så returnerar vi 0 vilket är ett standard värde för de perioder som inte har ett registrerat värde. Om den angiva perioden överstiger arrayens storlek så kommer vi få en IndexOutOfRange exception. Denna exception fångar vi om det skulle hända och retunerar värdet 0, annars så returnerar vi det värde den angivna perioden har:
public int GetValueFromPeriod(int period)
{
try
{
return _myPeriod[period];
}
catch(IndexOutOfRangeException e)
{
return 0;
}
}
Detta är ett typiskt fel många utvecklare gör. De använder sig av try och catch för att hantera fel som kan uppstå istället för att göra ett test innan för att undivka att en exception kastas. Om vi använder oss av ”Replace Exception with Test” på exemplet ovan så kommer resultatet att bli följande:
public int GetValueFromPeriod(int period)
{
if( period >= myPeriod.Length )
return 0;
return myPerisod[period];
}
I detta example så om får vi inget exception om värder för perioden vi skickar in som argument inte finns registrerat, utan vi får värdet 0, detta för att vi gör ett test om den angivna perioden överstiger arrayen, om den gör det så returnerar vi 0.
Dett finns så många olika fall när exception ska använda och inte användas. Jag har beskrivit några fall och hoppas att utifrån dessa så har ni lättare för er att avgöra om en exception ska användas eller inte. Har ni frågor eller vill kommentara detta inlägg så är ni mycket välkommna att göra det. Jag vill höra alla era synpunkter och erfarenheter kring exceptions:
0 Kommentarer