C
C#9mo ago
Florian Voß

❔ Exception Handling

I wonder whether I shall implement differentiated exception handling with detailed custom exceptions such as DataRecordNotFoundException only for displaying a more detailed error message in the UI, besides the error message in UI the handling would be the same for all exceptions. Is that reason enough tho? I'm sure the user would be thankful to know as detailed as possible what went wrong, but at the end of the day it wouldn't really change how he interacts with my app, my concern is just about being as transparent as possible what went wrong 🤷‍♂️ . What do you guys think?
60 Replies
Pobiega
Pobiega9mo ago
The exception type is information for the developer The message inside it can sometimes be shown to the user, if you know it's safe
Florian Voß
Florian Voß9mo ago
I know that
Pobiega
Pobiega9mo ago
Well, it answers the question you asked in your thread. So... if you already know it... why ask? 🙂
Florian Voß
Florian Voß9mo ago
my question is whether slightly more detailed error message in UI is reason enough for doing diferentiated exception handling with different exception types and messages for different scenarios. How did you answer that?
Pobiega
Pobiega9mo ago
If the only reason for adding new exceptions is to provide a more detailed error to the user, the new exception types are not needed. They don't make your program behave differently. Exceptions are for exceptional things etc Can the user reasonably act on the messages they get shown?
Florian Voß
Florian Voß9mo ago
okay but what about Update method where no record with matching PK value can be found? I was gonna do something like throw new DataRecordNotFoundException("the record to update does not exist") so that caller can then display that message
Pobiega
Pobiega9mo ago
Is this an API or a desktop app?
Florian Voß
Florian Voß9mo ago
throwing other existing exception type with this message doesn't match, which one would I do? MAUI with xaml, not with blazor
Pobiega
Pobiega9mo ago
I'd personally not throw exceptions if an update failed because of missing record, I'd handle that as an expected error - but sure, that information is safe to show.. but probably with less technical speak
Florian Voß
Florian Voß9mo ago
what does handle as an expectred error mean? how handle it?
Pobiega
Pobiega9mo ago
so, exceptions are these magical things that can travel up the call-stack right?
Florian Voß
Florian Voß9mo ago
we dont wanna handle here within Update method. We want caller / viewmodel to handle this by dispalying X or displaying Y. Hence I throw ye
Pobiega
Pobiega9mo ago
Doing that to update a UI is... using exceptions as control flow and thats bad I'd rather have your Update(...) method return a result that says if it succeded or not thats what I mean with "expected error" if we can expect the record to not exist, we should let a caller of our method know that "hey, sometimes when you call me, I will fail and I show that this way"
Florian Voß
Florian Voß9mo ago
okay so. Should do something like:
public class Result<T> where T : Entity{
public bool Success { get; set; }
public T Data { get; set; }
public string ErrorMessage { get; set; }
}
public class Result<T> where T : Entity{
public bool Success { get; set; }
public T Data { get; set; }
public string ErrorMessage { get; set; }
}
or is there some commonly used lib or smth to use instead of defining my own simple type like that?
Pobiega
Pobiega9mo ago
yeah that would be a pretty common way to do that
Florian Voß
Florian Voß9mo ago
so I define my own type okay
Pobiega
Pobiega9mo ago
if you want, sure There is OneOf, a popular library that lets you specify very strictly what types of errors can happen
Florian Voß
Florian Voß9mo ago
so what do you personally use? your own type or lib?
Pobiega
Pobiega9mo ago
I personally quite like Remora.Results as an in-between, it doesnt expose error types, but it has strictly typed errors Remora is a pretty small and lightweight result object it has an abstract base error that all errors inherit from, and some implicits to make it easy to work with a method that is declared to return Result<T> can safely just return someT; and it just works. Same with errors return new RecordNotFound(...); "just works"
Florian Voß
Florian Voß9mo ago
this is actually really nice
Pobiega
Pobiega9mo ago
this is how error handling is done in languages without exceptions btw
Florian Voß
Florian Voß9mo ago
I can even use this for multiple things, say my repos and my typed http clients
Pobiega
Pobiega9mo ago
Go uses this, Rust uses this
Florian Voß
Florian Voß9mo ago
thx
Pobiega
Pobiega9mo ago
etc yes! absolutely its a fantastic pattern to use when things "can fail" and you need to communicate that
Florian Voß
Florian Voß9mo ago
mhm but one concern
Pobiega
Pobiega9mo ago
yup?
Florian Voß
Florian Voß9mo ago
I thought this would be really nice cuz we managed to replace try catches with if elses. But here is the thing, we might still run into exceptional issues, so try catch is still needed for things like I/O ain't it?
Pobiega
Pobiega9mo ago
sure, yep
Florian Voß
Florian Voß9mo ago
which means we end up doign both try catch AND if else which makes me not a fan of Result type or do I missunderstand
Pobiega
Pobiega9mo ago
they are for different things thou, if you ask me Results are for business failures exceptions are for technical failures
Florian Voß
Florian Voß9mo ago
I see
Pobiega
Pobiega9mo ago
a database connection broke because the user unplugged their internet while it was querying, thats not something your program can fix, so thats an exception if you can find a place to add a try-catch to handle it so your program doesnt enter a fail-state, then do so but having a global "catch all" exception handler can be dangerous you might catch the exception and ignore it, but what if your program is now in a bad state like, an operation stopped half-way through and now some of your variables are "out of sync"
Florian Voß
Florian Voß9mo ago
oh wait I think I can answer myself why Result type is still very nice. Let me try to answer: We can handle exceptions in our Repository layer, Fill our Result type with exception's message so we handle both exceptional and unexceptional issues with the Result type and the caller would only ever have to use If else, no more try catches 🙂 so only one try catch in repo but not all the calling stack
Pobiega
Pobiega9mo ago
yep, we can do that! sure. If you catch any and all exceptions related to the database query in your repo, the repo can safely return the result or an ExceptionError(ex) not the worlds nicest thing, and only do this for things like IO where you know its still fine to continue
Florian Voß
Florian Voß9mo ago
why not the nicest thing?
Pobiega
Pobiega9mo ago
well because "ExceptionError" isnt really a business thing 🙂 the failure itself can be, but the details about it are... unexpected
Florian Voß
Florian Voß9mo ago
oh that one I thought you meant result
Pobiega
Pobiega9mo ago
oh yeah it is Result<T> will either be "success, T" or "failure, here is my error" and if its an unexpected exception, that means we couldnt forsee it, so we must wrap it in a general "ExceptionError"
Florian Voß
Florian Voß9mo ago
I would do:
try{
//code
if(await command.ExecuteNonQueryAsync() == 1){ return new Result<Contact>(true, null, "");}
else{
return new Result<Contact>(false, null "Contact does not exist");
}
}catch(Exception ex){
return new Result<Contact>(false, null, ex.Message);
}
try{
//code
if(await command.ExecuteNonQueryAsync() == 1){ return new Result<Contact>(true, null, "");}
else{
return new Result<Contact>(false, null "Contact does not exist");
}
}catch(Exception ex){
return new Result<Contact>(false, null, ex.Message);
}
in an update method for example
Pobiega
Pobiega9mo ago
yeah kinda this would be for an untyped error result type thou where you dont have a value object for the error, only a string message which is fine, its an active choice to make here is a snippet from a web api that uses results
if (!result.IsSuccess)
{
return result.Error switch
{
NotFoundError notFoundError => NotFound(notFoundError.Message),
ValidationError validationError => BadRequest(validationError.ValidationFailures),
_ => Problem(detail: result.Error.Message, title: "Internal Server Error", statusCode: 500)
};
}
if (!result.IsSuccess)
{
return result.Error switch
{
NotFoundError notFoundError => NotFound(notFoundError.Message),
ValidationError validationError => BadRequest(validationError.ValidationFailures),
_ => Problem(detail: result.Error.Message, title: "Internal Server Error", statusCode: 500)
};
}
so I map my errors to http return codes like this thats why I like having typed errors
Florian Voß
Florian Voß9mo ago
I see, makes sense we wanna share the message for every error of this kind @Pobiega why does ArgumentException exist?! if thats not abusing exceptions for an expected issue and for control flow I don't know what is @Pobiega and same concern for other exceptions. FileNotFoundException is not really an unexpected issue, thats the most common problem when reading a file and I can easily check it with File.Exists()
Pobiega
Pobiega9mo ago
Well, the result pattern isnt really used in the BCL itself and remember, most of these are from C# 1.0 which is over 20 years old at this point
Florian Voß
Florian Voß9mo ago
BCL?
Pobiega
Pobiega9mo ago
the "functional" wave that has hit C# in the past 10 or so years wasnt a thing back then
Florian Voß
Florian Voß9mo ago
ah base core lib?
Pobiega
Pobiega9mo ago
base class library ye
Florian Voß
Florian Voß9mo ago
so they just exist cuz they are relicts from old times? I should not be using them?
Pobiega
Pobiega9mo ago
When I first started coding C# I was like "wow exceptions make error handling so easy!" No, you should, in a way
Florian Voß
Florian Voß9mo ago
yeah same. I learned programming in java and they commonly use exceptions for control flow
Pobiega
Pobiega9mo ago
IO operations throw FileNotFound, you cant change that, so you either check that the file exists before reading it (I prefer this) or you try/catch it argumentexception is actually a good exception imho remember, exceptions are for the programmer so if argument exception is ever thrown, it means a programmer fucked up "oh I passed -1 and this thing only ever supports positive values, my bad"
Florian Voß
Florian Voß9mo ago
but if we strictly follow "exceptions are for unexpected issues" than this violates it. invalid arguments is the most expected thing, we could just sanitize it if we wanted to. Or return our result type indicating whats wrong
Pobiega
Pobiega9mo ago
I don't agree a USER passing invalid data, thats expected
Florian Voß
Florian Voß9mo ago
ohhhh fair
Pobiega
Pobiega9mo ago
a developer using a method wrong by passing the wrong arguments, thats not expected
Florian Voß
Florian Voß9mo ago
fairrr thx
Pobiega
Pobiega9mo ago
so yeah, always validate user input a related thing is ArgumentNullException there is a warning when you have nullable reference types on in public methods that take MyClass myClass style arguments "You flagged this as not nullable, but someone might still try to pass null, so you should verify that its not null" seems silly, but because nullable annotations are just... annotations, you can still be given a null value. Since you've said "this class/method doesnt work if you pass null", you should verify that it isnt null
Florian Voß
Florian Voß9mo ago
sure, that makes sense
Pobiega
Pobiega9mo ago
thats why you often see ArgumentNullException.ThrowIfNull(parameterName); in constructors even if the parameter is not specified as accepting null
Accord
Accord9mo ago
Was this issue resolved? If so, run /close - otherwise I will mark this as stale and this post will be archived until there is new activity.
Want results from more Discord servers?
Add your server
More Posts