4 ms·
> Errors as values is a far superior approach to exceptions. Why is that? I have never seen a cogent explanation for why this is the case. I can tell you why
by flowerlad 5y ago
> Errors as values is a far superior approach to exceptions.
Why is that? I have never seen a cogent explanation for why this is the case.
I can tell you why exceptions (as implemented in Java) are cool: You can write code as if every function call is successful, as opposed to adding a line (or more) of error handling code after every function call, which makes it harder to follow the logic.
- tacitusarc 5y agoI think it's a little bit complicated to explain but mostly it boils down to this: errors are real. Java methods kind of let you ignore them through declaring exceptions, with this idea that, well, somebody else will deal with it. Golang functions make errors feel more present. They force you to think about how you're going to handle the errors up front, and to question whether or not you even should error in a particular instance. It's actually helped change the way I think about functions. There are many behaviors that in Java I would not have even considered making them idempotent but in golang making them idempotent is both easier and, as it turns out, more robust. The patterns for error handling the golang have introduced are admittedly verbose, but they do lend a certain element of confidence that once the code is written, the errors should be handled. Of course a programmer can ignore the errors explicitly but doing so is different than forgetting to catch a thrown exception, because the programmer must go out of their way to write code ignoring the error. It feels like there's more agency around the decision.
- flowerlad 5y ago> Java methods kind of let you ignore them through declaring exceptions, with this idea that, well, somebody else will deal with it. Sure, you can write sloppy code in Java, but I am sure you can write sloppy code in Go too.
- Mawr 5y agoThe difference is that with exceptions sloppy code is the default. As you write code in a language with explicit errors, the language makes you acknowledge that the code you call can error out. This makes you stop and think what to do about the error. You can choose to ignore it, but that's a conscious decision that the language forces you to make. With exceptions, there's no such feedback mechanism from the language/compiler. In order to write robust code you yourself must have the discipline to add exception handlers around the appropriate calls. In short, defaults matter. It's simply easier to write correct, robust code when you don't have to go out of your way to do it. See https://devblogs.microsoft.com/oldnewthing/20050114-00/?p=36693 https://devblogs.microsoft.com/oldnewthing/20050114-00/?p=36...: > It’s really hard to write good exception-based code since you have to check every single line of code (indeed, every sub-expression) and think about what exceptions it might raise and how your code will react to it. ... which leads us straight to favorite exceptions-caused bug: https://nedbatchelder.com//blog/202001/bug_915_solved.html https://nedbatchelder.com//blog/202001/bug_915_solved.html: > Inside tempfile.NamedTemporaryFile, the error handling misses the possibility that _TemporaryFileWrapper will fail.
- takeda 5y agoI haven't used Java in a while, but didn't Java require you to declare all exceptions your method will throw? You could ignore those, but similarly you could ignore err in Go as well.
- masklinn 5y agoOnly checked exceptions. And the integration of checked exceptions is so bad, and the split between checked and unchecked so arbitrary, that most codebases have sworn off of them.
- fierro 5y agoThese are great readings regarding errors in Go. https://dave.cheney.net/2014/11/04/error-handling-vs-exceptions-redux https://dave.cheney.net/2014/11/04/error-handling-vs-excepti... https://dave.cheney.net/2015/01/26/errors-and-exceptions-redux https://dave.cheney.net/2015/01/26/errors-and-exceptions-red...
- lolinder 5y agoI write Java, but I prefer errors as values exactly because you can't only consider the happy path. It really makes you think about the appropriate response to this specific failure. You can do that with exceptions, but in practice it's exactly like you say: all error handling is delegated to some generic catch-all, which in a web app usually just gives a generic 500 Internal Server Error. If I encode my errors as values (usually with Either), I have to decide how to gracefully fall back if a failure occurs before I'm even allowed to use the successful result. Maybe I just hide the part of the view that needed that data. Maybe I show an appropriate error message. Maybe I email operations to let them know about the problem. Whatever I do, I have to actually think about it and not just assume that the error will be caught somewhere by someone. The result is usually a dramatically improved user experience when failures inevitably occur. Exceptions tend to pass the buck so far down the line that there's no context to make an appropriate decision. Values tend to force a decision early, when you still have enough context. (Obviously both can be used against the grain, but the question is which pattern is easier.)
- flowerlad 5y ago> Exceptions tend to pass the buck so far down the line that there's no context to make an appropriate decision. This is not true, and therefore your explanation is unsatisfying.
- lolinder 5y agoCan you please elaborate how the pattern that you describe doesn't pass the buck to a portion of code that has less context for the cause of the failure? > You can write code as if every function call is successful How does writing code as if every function call is successful (when you know that some functions will fail) not lead to some other bit of code further up the call stack having to make a decision about an exception that it doesn't have the context for? A bit of code which probably was written by a different developer who didn't anticipate what you were going to do?
- flowerlad 5y ago
- fierro 5y agothe fundamental tenet in Go is that every error should be handled. Here is an excerpt from Dave Cheney that clarifies this: "For the truly exceptional cases, the ones that represent either unrecoverable programming mistakes, like index out of bounds, or unrecoverable environmental problem, like running out of stack, we have panic. For all of the remaining cases, any error conditions that you will encounter in a Go program, are by definition not exceptional — you expect them because regardless of returning a boolean, an error, or panicing, it is the result of a test in your code"
- flowerlad 5y ago> the fundamental tenet in Go is that every error should be handled. That's true in Java as well.
- lolinder 5y agoI think the point is that in Go, you expect failure. Failure is not exceptional. It's a first class part of the logic. Java conditions you to view failure as an exception to the rule, and the happy path as the real code (which is the attitude you express in your first comment). Lots of us have since observed that this approach to failure leads to errors because programmers ignore failures, allowing them to be handled by a catch-all at the top of the stack, which typically just dumps a stacktrace and calls it a day. The paradigm espoused by Go sees errors as just another program state, and one whose implications are just as important to consider as the desired behavior. This forces programmers to consider all the implications of a failure, rather than just adding another `throws` clause.
- flowerlad 5y ago> Lots of us have since observed that this approach to failure leads to errors because programmers ignore failures, allowing them to be handled by a catch-all at the top of the stack, which typically just dumps a stacktrace and calls it a day. That's not typical Java code. You could do that in quick & dirty code, but I haven't seen such code in production code.
- puzz 5y agoLook at any piece of Java code and try to guess the cyclomatic complexity of it. It's not simple. Because every line, and every function call can fail. And that failure is one more place where the execution tree of your code branches. You can't see it unless you check every method call in your code. In Go -- every error is obvious, and you get a sense of a cyclomatic complexity of any piece of code by just going through it quickly. So, that's it for me. The complexity of the code is visible.
- Buttons840 5y agoI'm working with some Python that follows this error as value convention and it's awful. The code likes to catch exceptions as soon as possible and replace them with strings like "not found". Exceptions are useful things, they tell me what broke and where. Instead I get a "not found" error value, and find out that happened because the endpoint called a service that returned a "item not found" error value, and then a few layers of obscure string error values later I find out some logic did division by zero or something. In other words, I also don't understand the hype around error values. So, back to Python, what error value is suitable for replacing an exception? I mean, Python is flexible enough to do Go-style error values, but what does a good error value look like? Maybe I just haven't seen good error values? If an error happens way down in the weeds and I initially return a very specific error value, do I just pass it up the stack forever? If so, how is that different than traditional exceptions? Do I replace the specific error value with more general error values as I move up the stack to higher-level code? If so, then I am throwing valuable information away and am likely to end up with something like "not found" as my error value at a the top -- not very useful.
- randomswede 5y agoIn Go, I would do one of: * Pass the error value up unchanged * Wrap the error inside another error * Pass a new error * Swallow the error and do something else to cope with failure I would probably also do some error logging, capturing the state of relevant local variables, especially if it is one of those cases where "see an error" is unexpected.
- Buttons840 5y agoThanks for the answer. It sounds like pretty much the same thing good exception handling would do. I suppose they're two sides of the same coin. Both can be done well or poorly. I do resent the idea that error values are automatically better or easier to understand, because I'm dealing with some poorly done error values now and wishing for traditional exception handling.
- randomswede 5y ago