Skip to content

Repository files navigation

Felhantering i C#

Detta är underlag till en presentation om felhantering i C#.

Presentationens syfte är att gå igenom olika verktyg för att hantera fel — från enklare null-returer till Result<TSuccess, TError> och hur dessa kan kombineras med Bind och användas med LINQ.

Studera kodexempel i varje fil genom webbläsaren eller clone koden och använd din IDE eller editor.

Förslag på prompt starters

Nedan är några frågor du kan använda för att börja prompta:

  • Kan du förklara hur Bind fungerar i CSharpFunctionalExtensions med Result<T, E>?
  • Hur kan jag i CSharpFunctionalExtensions returnera ett Result<T, E> med ett discriminated union error record?
  • Vad är fördelarna med att i CSharpFunctionalExtensions returnera ett Result<T, E> med ett discriminated union error record?

Koden hanterar fel genom enkla null-kontroller, men detta är varken robust eller tydligt. Det saknas riktig felhantering, exempelvis med try/catch eller specifika feltyper. Att använda null som signal för fel gör felsökning svår, och returvärden används inte konsekvent.

Koden fungerar i enklare scenarier men bör förbättras för att bli mer tydlig, tillförlitlig och underhållbar.

Metodsignaturen visar tydligt att den returnerar en User eller null. Men vi är begränsade i den information som null kan förmedla.

User? ValidateUser(string username)

I denna kod kastar metoderna exceptions. Genom att titta på metodsignaturen går det inte att avgöra vilka exceptions som kan kastas. Utvecklaren måste gräva i implementationen för att förstå detta. Det försvårar skapandet av abstraktioner och ökar den kognitiva belastningen.

I metoden Run vet vi inte vilken av metoderna som misslyckades.

User ValidateUser(string username)

Denna kod har samma problem som i föregående exempel: det framgår inte från metodsignaturen vad som kan gå fel eller vilka exceptions som kan kastas. Men i detta fall kastar varje metod ett eget Exception som ärvs från en gemensam basklass. På så sätt kan felen hanteras mer granulärt.

User ValidateUser(string username)

Nu använder vi Result<T, E> från CSharpFunctionalExtensions. Detta gör det möjligt att deklarera att metoden ValidateUser returnerar en User eller en string. string representerar i detta fall ett felmeddelande.

Det viktiga här är att vi tydligt kan uttrycka att metoden returnerar antingen en User eller ett fel (som en string).

Result<User, string> ValidateUser(string username)

När vi kan resonera om ett värde som antingen det ena eller det andra får vi tillgång till ett användbart mönster: Bind.

Bind används för att kedja ihop operationer som returnerar Result<T, E> på ett säkert sätt. Om ett fel uppstår i någon del av kedjan returneras det direkt — utan extra logik. Kodflödet blir rent och tydligt. Du slipper if-satser och try/catch i varje steg. Felhantering blir tydlig, automatisk och konsekvent.

Om någon av operationerna returnerar ett Result.Failure kortsluts kedjan och efterföljande operationer körs inte.

//     TSuccess, TError 
Result<int, string> a = ValidateUser("A")
                           .Bind(SaveUser)
                           .Bind(SendEmail);

I just denna kod returneras fel som en sträng. Det är kanske inte optimalt, och i kommande exempel visas hur vi kan returnera mer detaljerad information.

I detta exempel använder vi samma koncept som ovan — varje metod returnerar Result<T, E>. Men här utnyttjar vi LINQ-syntaxen för att göra koden ännu mer läsbar och få tillgång till variabler från tidigare metoder i kedjan.

Result<int, string> a = from validUser in ValidateUser("A") 
                        from savedUser in SaveUser(validUser)
                        from emailReturnCode in SendEmail(savedUser)
                        select emailReturnCode;

Nu har vi kommit till sista delen.

Result<User, Error> ValidateUser(string username)

Här har vi en metodsignatur som returnerar en User eller ett Error där Error är en hierarki av record-typer.

record Error(string msg);
record ValidationError(string msg) : Error(msg);
record SavedUserError(string msg) : Error(msg);
record SendEmailError(string msg) : Error(msg);

Genom att returnera ett Error kan vi hantera fel på ett centralt och tydligt sätt:

public static string HandleError(Error error) => error switch
{
    ValidationError ve => $"Validation error: {ve.msg}",
    SavedUserError sue => $"Saved user error: {sue.msg}",
    SendEmailError see => $"Send email error: {see.msg}",
    _ => "An unknown error occurred."
};

Här har vi tydlighet mot utvecklaren vad som retuneras vid success eller failure. Det är enkelt att resonera om felen och att ta hand om dem.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages