Probably just me, but I am less concerned with how good my error messages are, and more concerned with trying very very hard to make the errors happen closer to the cause of the problem, rather than further away.
"Fail early, fail hard"
i.e. if I can make the error message happen near the beginning of a process, I can get away with making it a hard error.
Hard errors in the middle of a multi-hour operation tend to annoy people.
This is an attitude I really try to build up in junior devs. Soooo many people seem to default to writing code like, "if input is null return null" (when input should never be null) or "if valueThatShodBePositive < 0 silently skip the code that was going to use the value". If the app detects that something is in an invalid state _I want it to break_. The worst problems to debug are the ones where you have to work backwards through miles of strange behavior and corrupted data to find the root cause, because the program tried valiantly to soldier on long after it had been shot through heart with bad data.
I guess this is because no one really teaches error handling. I assume a lot of students end up with a mindset of just make the errors go away instead of, deal with the errors effectively.
Agreed; I've often wondered if this is a result of early CS classes usually expecting students to handle weird/bad inputs. It's only natural for a programmer to want to write a program that gracefully handles all reasonably bad inputs, like nulls. So we're taught early on to write defensive code that handles those. And that's fine when you're writing short, academic programs. But when the complexity goes up by a few orders of magnitude trying to gracefully handle that null value 10 levels deep in some parsing logic maybe isn't the best thing to do. Old habits die hard, however.
Yeah, this is a great point. Both overly defensive programming and (my personal least favorite) overly-commented code are instilled in students at a very early point in their careers by irresponsible teachers trying to find something to grade students on (Didn't handle negative values? 5 points off! Didn't leave a comment on every line? 1 point off per line!)
I think this is a symptom of using weakly typed languages as well. If your argument types are declared to be options/eithers, then you need to handle the empty case, but usually it's easier and better to just move that optional handling further up the callstack or type system.
A lot of `if (input == null)` checks are because you're just not sure whether the argument being passed in will have a value, and it's too much work for your small feature PR to refactor the whole codebase to resolve it.
Use typescript/python-with-mypy/haskell/rust/whatever and this problem mostly disappears.
> A lot of `if (input == null)` checks are because you're just not sure whether the argument being passed in will have a value, and it's too much work for your small feature PR to refactor the whole codebase to resolve it.
Null checks are totally fine, but it should be clear whether or not null is a valid input to the method. If the answer is 'no' then you should throw ArgumentNullException (or whatever's appropriate for the language), not silently ignore the bad input.
When I was a jr dev, getting exceptions was a synonymous of ”me messing something up”. Null exceptions were specially annoying, so the naive approach is to check for nulls and avoid the code that will cause the exception. And it “works”! You don’t get exceptions and your code keeps running. It’s just when you need to fix difficult bugs while you go through logs when you understand the value of having the right exception with the right message. And you learn to love them and start caring about them.
I think the point is that the higher up you fail, the harder it is to identify why you errored in order to give the user clear and actionable feedback.
When you have nested exceptions being caught by other exceptions, how do you determine what level is correct to show the user? Especially when it's a service class or something that is used by a lot of calling code.
It's implied that it would be the upper top-most exception handlers in that code path but those are gonna be more generic in their messages, and anything more detailed has to be manually wrapped to add useful description (that's not some internal developer exception).
Error codes may be the least bad solution, to fallback on.
It's hard to give a generic answer to this. I just see way too many bad error messages that could be solved with a little more thought and copywriting skills.
Error messages are part of the user experience and they should not be an afterthought.
If errors are nested, list them all. Give a generic feedback then, and also provide a technical explanation that would help debugging. Most importantly, we should make the user feel safe and in control as much as possible.
I actually do like "collecting" the errors when possible, and having them return in the API response (for example). Instead of the common pattern where there's just singular "error:" in the top-level json.
Fail Fast means your logging infrastructure is going to report to you more quickly to get the problem fixed.
As opposed to 6 months down the road when someone finally notices an uptick in complaints by customers and now the potential problem sites is literally the entire software stack.
fail fast is how stable software is made, the question is whether or not you think customers appreciate stable software.
"Fail early, fail hard"
i.e. if I can make the error message happen near the beginning of a process, I can get away with making it a hard error.
Hard errors in the middle of a multi-hour operation tend to annoy people.