Felhantering

I teorin så är det ganska enkelt att hantera fel, men som vanligt så är det inte alltid det lättaste att översätta teori i praktik. Jag har sett otaliga sätt och tillämpningar på hur felhantering har implementerats i allehanda programspråk, och jag måste säga att jag inte är helt övertygad om att något sätt som något programspråk erbjuder är det mest optimala.

Problemet jag har är tvåfalt: Det är dels en ren syntaxfråga – vilken syntax krävs av språket för att indikera och hantera fel och den andra delen handlar om den mer rena abstrakta observationen vad som skall felhanteras och hur.

Syntax

I Java har vi s.k. Try…Catch…Finally satser som är ett helt ok att hantera fel och som har blivit mer eller mindre en de facto standard. Det fungerar men för mig är det väldigt mycket som stör mitt öga. Det blir väldigt mycket ”klutter”. Tyvärr har ju även .Net implementerat denna syntax.

Ruby använder andra ord men det är samma syntax som Java/.Net Try/Catch; ”Begin…Rescue…End”.

I LotusScript så är vi tvingade att använda det förhatliga ”On Error Goto” som inbjuder till spaghetti-kod.

I C så har vi ingen syntax för felhantering – vi använder svarskoder (”return codes”) från våra metoder för att indikera hur det resulterade där int 0 vanligtvis betyder att metoden lyckades.

Jag inte tycker om try…catch-syntaxen. Den stör mitt öga. Om nu varenda funktion/metod har felhantering så kommer varenda en att innehålla en try-catch sats och då kommer du ha mycket kod för ingenting. Det blir väldigt mycket text som ditt öga måste ignorera för att se vad koden faktiskt gör. Jag gillar inte principen att man skriver något som inte behöver läsas.

Det finns ett sätt att slippa try..catch men det är inget sätt jag rekommenderar. Det är att använda CHECKED EXCPTIONS enbart. Det är bara ett sätt att dölja felhanteringen och flytta problematiken. Dessutom så är checked exceptions ett brott mot Open/Closed principle. Tyvärr så finns det vissa bibliotek som tvingar oss att använda CHECKED EXCEPTIONS, så då är vi tvingade att skriva Wrappers för dessa för att få bort otyget.

Hur borde det se ut då? Hmm. Det är en svår nöt att knäcka. Personligen så skulle jag vilja kunna göra något i stil med att deklarera i början av en funktion vilka fel som har felhantering och hur dessa skall hanteras, men att koden för att hantera dessa fel INTE LIGGER I SAMMA METOD som resten av koden:


public void myTestFunction() {
 Declare Error IllegalFunctionCallException informUser(IllegalFunctionCallException);
 ...
 Throw new IllegalFunctionCallException("Test");
 ...
}
public void informUser(Exception e) {
 //Handle the error caused...
}

Problemet med detta är att det är bara minimal skillnad från klassiska ”On Error Goto” syntaxen och kan potentiellt göra spaghetti av din kod. Dock har det här tillvägagångssättet den fördelen att båda metoderna blir mer lättlästa. Du skulle också kunna återanvända metoder för felhantering så att du slipper repetera felhanteringen. Samma sak kan iofs uppnås med try-catch men med någon slags ”declare” sats så kan du skriva allt på en rad och det upptar inte nämnvärd utrymme i din metod.

Men hur skall koden se ut då??!! Ja du… om jag hade svar på den frågan så vore jag glad och kanske skulle kunna tjäna en slant. Så tills vidare får jag leva med den näst bästa lösningen och det är att använda try-catch-satser.

Så problemet kvarstår. Detta får mig att fundera vidare på vad vi egentligen använder Exceptions till.

Hur använder vi Exceptions?

Det finns ju i princip två typer av undantag/exceptions: Rena run-time fel såsom I/O error osv. dvs sådana fel som vi som programmerare inte har kontroll över. I normala fall skall detta inte inträffa, men möjligheten finns. Alltså måste detta hanteras på något sätt.

Den andra typen av undantag/exception som finns är när vi använder Exceptions istället för den gamla C-varianten som RETURKOD! Vi låter anropande metod veta att vi misslyckades genom att sända tillbaka ett Exception. Detta anser jag vara förkastligt! Skillnaden mellan returkod och exception är i dessa fall endast semantisk och bidrar inte till att koden blir bättre. Semantisk? Ja, koden blir i princip densamma: Anropande kod måste fortfarande analysera om metoden lyckades eller ej: Med returkod så sker analysen omedelbart:
If returnValue==0 Then...
Med felhantering så sker den indirekt:

try {
 callThisMethod();
catch (Exception e) {
 //Didn't succeeded with call to callThisMethod();
}

Det andra problemet med denna typ av felhantering är att det blir mycket mer komplicerat om vi i try-satsen skulle anropa 3-4 olika funktioner. Hur vet vi då i catch-sektionen exakt vilken metod som fallerade? Detta sätt att felhantera känns fel. Rakt upp och ner fel på alla sätt och vis.

Det borde gå att konstruera din kod så att du inte anropar en metod innan du vet att den kommer att lyckas. Undantag blir då endast Runtime-errors såsom I/O problem mm som inte kan förutses och som ligger utanför din applikations kontroll.

Ta t.ex. ett exempel om vi har en metod som gör resize på en JPG-bild, men som av någon anledning inte hanterar något annat bildformat. Då är det vanligt att man i metoden skriver en if-sats som evaluerar vad det är för bildformat på bilden och sedan genererar en Exception om det inte är en JPG. Exempelvis:


public void resizeJPG(String filePath) {
 if (getFilepathExtension(filePath).equalsIgnoreCase(".jpg")) {
  //Process image
 } else {
  Throw new IllegalFiletypeException("Unsupported filetype!");
 }
}

Borde man inte göra tvärtom? Se till att man inte anropar funktionen om bilden inte är en JPG? Då blir resizeJPG() metoden renare och mer lättläst. Visst; du flyttar if-satsen uppåt i kedjan, men med rätt design bör inte det bli problem. En sådan sak borde kanske till och med avhjälpas med att tillämpa Factory Design Pattern och lägga hela evalueringen i Factory-klassen. Tydligare än så kan det inte bli.

Kvar att hantera i resizeJPG() metoden blir att hantera I/O-fel och fel som genereras från grafik-biblioteken. Dessa typer av fel kommer man ”tvingas” att hantera på samma sätt som tidigare, dvs i resizeJPG() metoden och skicka uppåt med en ny Throw för att meddela anropande funktion att det blev ett fel som inte gick att hantera. Fördelen är att try-catch satsen i anropande metoden blir lite mindre komplex:


public void resizeAll(...) {
 try {
  resizeJPG(...);
 } catch(ImageCreationException e) {
  //Process error; possibly show error to user... and/or create a log entry
 }
}
public void resizeJPG(...) {
 try {
  //Process image
 } catch(IOException e) {
  Throw new ImageCreationException("Unable to create image...",e);
 } catch(Exception e) {
  Throw new ImageCreationException("Unable to create image...",e);
 }
}

Eftersom vi använder Factory Design Pattern så kommer funktionen ”resizeJPG()” bara att utföras när Factory har skapat en klass som innehåller den här metoden när käll-filen är av typen JPG. Om filen inte är en JPG så kommer en annan klass att skapas som rapporterar ett fel till användaren att han bara får använda JPG-filer… resizeJPG() kommer då aldrig att anropas/användas.

Varför kodar inte folk på det här sättet? Kanske för att de inte ser skillnad på Exception och Exception. De betraktar alla exceptions som runtime och man använder svångrem och hängslen: ”Måste säkerställa att det inte blir fel i den här metoden, så vad kan gå fel… bäst att koda in alla tänkbara scenarior…” istället för att jobba sig uppåt i abstraktionen.

En tanke slog mig…

En liten tanke dök upp: Vad skulle hända om man kunde definiera flera fel på samma catch-rad:

...
} catch(IOException, NullPointerException, Exception e) {
...
}

Då skulle det bli mindre kod då vi på flera typer av fel skulle utföra samma kod. Lättare att läsa, mindre kod och framförallt mindre duplicerad kod. Duplicering är en av alla de saker som orsakar buggar i programkod. Frågan är bara om detta skulle få några andra konsekvenser än vad jag kan förutse just nu.

Oj… det blev ett långt inlägg men jag tycker hela konceptet med felhantering är stort och komplext. Dock är det av stor vikt att man gör det rätt. Vad är rätt? Jag vet inte, jag provar mig fram fortfarande.

Är det någon som har någon bra bok/artikel/tips på det här med felhantering så mottar jag det med tacksamhet!

Annonser

Kommentera

Fyll i dina uppgifter nedan eller klicka på en ikon för att logga in:

WordPress.com Logo

Du kommenterar med ditt WordPress.com-konto. Logga ut / Ändra )

Twitter-bild

Du kommenterar med ditt Twitter-konto. Logga ut / Ändra )

Facebook-foto

Du kommenterar med ditt Facebook-konto. Logga ut / Ändra )

Google+ photo

Du kommenterar med ditt Google+-konto. Logga ut / Ändra )

Ansluter till %s

%d bloggare gillar detta: