3 ms·
In terms of control flow, this: try { someCall(); } catch (ErrorICanHandle err) { //handle } perfectly equivalent to this: err = someCall();
by simiones 6y ago
In terms of control flow, this:
try {
someCall();
} catch (ErrorICanHandle err) {
//handle
}
perfectly equivalent to this:
err = someCall();
if canHandle(err) {
//do something
} else {
return err
}
I don't see why so many people think exceptions make code harder to analyze. In my opinion, it is errors themselves that make code hard to analyze, regardless of implementation strategy.
The only difference between exceptions and error returns/error codes is in human readability: one makes the bubbling behavior explicit, which clutters the code but makes it obvious; the other makes the bubbling behavior implicit, which keeps the code cleaner, but also less obvious.
After writing professionally in both languages with Exceptions (Java/C#/Python) and in Go, I much prefer Exceptions to error values/codes, but I can appreciate that it may be different in other domains or for other people.
- xpressvideoz 6y agoI mean, Go's error handling is one of the worst implementations of the "error code" idea, so it is a bit unfair to disregard the idea just because one particular implementation is bad. You should try Rust or Swift, at least the error handling part, to fully appreciate what a good error code implementation could be.
- simiones 6y agoI'd love to when I get a chance. I did see that Rust seems to favor a ? macro that seems to make error codes behave essentially like exceptions, so I am curious to try it out at some point and see if that ends up being any different from exceptions in practice.
- sensitive-ears 6y agoThe "?" operator doesn't turn them into exceptions, it's just a "return-early-if-error" shortcut. The main difference being that the caller still has to handle the error of a function using "?". (even if it's by punting further up the call stack with more "?", which you could argue is exactly how exceptions work, but it is at least explicit in what functions can fail and which don't)
- simiones 6y ago> even if it's by punting further up the call stack with more "?" Yes, this is exactly what I meant. > it is at least explicit in what functions can fail and which don't So is Java with exceptions (at least as long as developers are even slightly disciplined).
- sensitive-ears 6y agoYeah, I think java had the right idea, but the ergonomics didn't turn out great, so they didn't get the buy in they'd hoped for. Having both checked and unchecked exceptions (ie, NPEs) meant it doesn't offer the same safety guarantees as something like rust does. The implementation of exceptions can also have some implications for the runtime, whereas the enum method of error handling allows you to run in very minimal environments and easily see what's going on. (Exceptions could be implemented this way under the hood, but they often aren't)
- metreo 6y agothis issue is knowing whether you are catching the right errors
- simiones 6y agoYou have the same problem with error codes, don't you? The bigger difference is knowing whether you should expect errors at all - with exceptions, you can forget to handle an error and you may screw up an important assumption, like not unlocking a Mutex if an Exception is raised. With error codes, you have the opposite problem: you may forget to check for an error, and continue to execute in a bad state.
- gmfawcett 6y agoExceptions make code harder to analyze because they implement non-local control flow. Your two example statements aren't really equivalent. `ErrorICanHandle` may be a kind of exception that the `someCall` function has no idea about whatsoever -- it could be an exception propagated from much farther down the call stack -- while your if statement assumes the error was explicitly returned from the function.
- simiones 6y agoIn an exception-based language, assuming all functions potentially throw, code like this: someCall() is equivalent to code in a language without exceptions that looks like this: err = someCall() if err { return err } (ignoring for a moment non-error returns, which don't change the point significantly) Having functions that are guaranteed not to throw / return errors doesn't significantly change this shape. So basically it's no more non-local than the code you'll end up writing with error codes, in most cases, any way. The only difference is implicit vs explicit. > `ErrorICanHandle` may be a kind of exception that the `someCall` function has no idea about whatsoever -- it could be an exception propagated from much farther down the call stack -- while your if statement assumes the error was explicitly returned from the function. someCall doesn't need to know about the meaning of the error in either case. It only needs to propagate any error it receives from any functions it calls (that it can't handle itself). Think about the function I wrote myself: did it need to know the precise type of err? Nope - it just needed to know that it implements some kind of Error interface (which may be as simple as being a negative number, like a POSIX error code). Edit to give a more complete example: extern void bar(); void foo() { bar() doOtherStuff() } void someCall() { foo() doMoreStuff() } void main() { try { someCall() doFinalStuff() } catch (BarSpecificException e) { doErrorStuff() } } Is equivalent to this code: extern err bar() err foo() { err = bar() if err { return err } err = doOtherStuff() if err { return err } } void someCall() { err = foo() if err { return err } err = doMoreStuff() if err { return err } } err main() { err = someCall() if err is BarSpecificException { err = doErrorStuff() if err { return err } } else if err { return err } err = doFinalStuff() if err is BarSpecificException { err = doErrorStuff() if err { return err } } else if err { return err } } If I ask you in the second piece of code what will be executed after the call to foo(), is it any easier to reply than in the first version? Personally, I don't believe so.
- BlackFly 6y agoExceptions are harder to analyze because they create scopes from which you are already "handling" errors, but if you add code to the scope that throws a new error of that type your handler may not actually be able to handle it. try { someCall(); someOtherCall(); } catch (ErrorType err) { //handle } Which method throws? If they both throw ErrorType but one of them cannot be handled, then you have to put additional logic into the catch and rethrow. This is a common source of faulty error handling.
- simiones 6y agoYes, this is an easier mistake to make with exceptions than with error values. Still, the fix is to do exactly the same thing as in the error values case: try { someCall() } catch (ErrorType err) { //handle one way } try { someOtherCall() } catch (ErrorType err) { //handle another way } It's no less verbose than the error values way, though again, it is easier to make the mistake in the first place. I'm not trying to claim that exceptions are ultimately better than error values, just that the difference isn't so much "non-local control flow" as it is explicitness vs implicitness. Exceptions are implicit, error codes are explicit. Both have benefits and drawbacks.
- _ek94 6y agoFor those of us who don’t like exceptions I think the concern is more that the exception might not be caught right there, so an exception may potentially transfer control to an arbitrary point above the current function in the stack.