Live data from Hacker News

Exploring Error Handling Patterns in Go

8thlight.com

61–70 of 80 posts

Re: Exploring Error Handling Patterns in Go

#61
> The other bit of good news is that you can't unknowingly ignore a returned error, like you can with an unchecked exception. The compiler will force you at a minimum to declare the error as _, and tools like errcheck do a good job of keeping you honest.

Actually, we unknowingly ignore returned errors much more often than we think, like when we call a function and opt out of assigning any of the return values to variables. Consider this function, which returns a single value (being an error).

    func Failure() error {...}
You can always choose to call an error-returning function without declaring any placeholder (`_`):

    Failure()
There are several commonly used functions that return errors that are regularly ignored. How about `io.Writer`?

    writer.Write([]byte("Hello")) // returns (n int, err error)
It's quite common to call that function without feeling a need to check on the bytes written or a possible error. Or, consider whether you consistently check the return values of `fmt.Println()`, which also returns `(n int, err error)`...

Re: Exploring Error Handling Patterns in Go

#62
post #8
post #4

if err != nil return err if err != nil return err if err != nil return err https://github.com/docker/cli/search?q=%22if+err+%21%3D+nil%... https://github.com/kubernetes/kubernetes/search?q=%22if+err+... https://github.com/coreos/etcd/search?q=%22return+err%22&uns... https://github.com/influxdata/influxdb/search?q=%22if+err+%2... The reality of Go's error handling is that you just implement exactly what exception bubb…

The pattern is the programmer's choice. Here's a contrary example/approach: https://blog.golang.org/errors-are-values

Claiming "errors are values" is as useless as claiming anything in computing is "a value" (because it is).

Errors are not just values, however, they're something much more specific: An uncaught error is a specific circumstance where the programmer's mental model was insufficient to account for all the state possibilities. Literally the introduction of the unexpected. And this should be treated as a very bad (or at least a very special) thing, as soon as possible. Hence, runtime exceptions. (Hence, disclaimer alert, I'm not a fan of Go.)

Re: Exploring Error Handling Patterns in Go

#63

Earlier quoted context omitted.

A little duplication is better than the wrong abstraction, and I've seen far more subtly wrong or obfuscating abstractions than duplication in code I have to manage. Go is definitely not perfect, and sometimes it's plain wrong about this (I don't particularly like the go error handling and hope it improves), but there is a reason for discouraging certain types of abstraction and encouraging verbosity and boring code…

Abstractions like map and filter that have decades of use and countless pages of research behind them are not the wrong abstraction. You are more likely to get the wrong abstraction by forcing programmers to create their own abstractions instead of letting them use well-known ones that have been refined over many years.

I'm not sure anyone was opposed to map (that wasn't under discussion), not all abstractions are bad, however a flexible language makes code easier to write but harder to read, a rigid language makes code harder to write but easier to read. I prefer ones that are easier to read, even at the expense of a little verbosity.

I'm not saying Go is the best of all possible worlds (I would like to see generic functions like map too, or things like sum types for errors), just that there are good reasons for the decision to exclude some opportunities to build abstractions (for example I'm happy go eschews inheritance), and abstraction is not an unmitigated good. I've seen far more bad abstractions built than code duplicated when reading code in any language, so limiting abstractions is not always a bad thing.

Re: Exploring Error Handling Patterns in Go

#64
post #54
post #44

Earlier quoted context omitted.

The parent is correct. Returns also unwind the stack. > One important benefit of Go's error handling pattern is readability I beg to differ, Go's approach is similar to checked exceptions, Java's original sin. And just like checked exceptions, forcing the invoker of a function to handle the error directly is the wrong approach in the vast majority of cases. It just produces code noise and catch/wrap/throw style code,…

Seriously, exceptions are very different from returning an error. Confusing error return with checked exception tells it all. A checked exception is just an exception type specification. When you read code with a call to a function returning an error, you see how the error is handled. With exception, unless there is a try/catch close surrounding the call, you don't know where and how an exception is handled. To me, t…

> […] function returning an error, you see how the error is handled.

In practice you only see that errors get returned immediately. Most functions rightly give up rather than trying to handle errors because they don't know exactly how or where they're being (re)used.

Re: Exploring Error Handling Patterns in Go

#65
post #54
post #44

Earlier quoted context omitted.

The parent is correct. Returns also unwind the stack. > One important benefit of Go's error handling pattern is readability I beg to differ, Go's approach is similar to checked exceptions, Java's original sin. And just like checked exceptions, forcing the invoker of a function to handle the error directly is the wrong approach in the vast majority of cases. It just produces code noise and catch/wrap/throw style code,…

Seriously, exceptions are very different from returning an error. Confusing error return with checked exception tells it all. A checked exception is just an exception type specification. When you read code with a call to a function returning an error, you see how the error is handled. With exception, unless there is a try/catch close surrounding the call, you don't know where and how an exception is handled. To me, t…

> A checked exception is just an exception type specification.

A checked exception _requires_ an exception type specification. If you don't handle the exception locally, that is. It's this quality I refer to, when I say checked exceptions are similar to error codes: an API designer, without knowing the full context, requires the call site to do something about it, even if 9/10 call sites could not care less, _especially_ in big projects. Some other commenter linked to this interview [0], which elaborates on that problem.

> With big projects, this strategy is unmanageable.

Clearly, it's possible to have mantainable projects both with and without exception handling.

> You know that programs are not only http handlers, right ?

I find this somewhat condescending, but yes, I do know that. Most programs have system boundaries though, and might recover from quite severe error conditions there.

[0] https://www.artima.com/intv/handcuffs.html

Re: Exploring Error Handling Patterns in Go

#66
post #7
post #4

if err != nil return err if err != nil return err if err != nil return err https://github.com/docker/cli/search?q=%22if+err+%21%3D+nil%... https://github.com/kubernetes/kubernetes/search?q=%22if+err+... https://github.com/coreos/etcd/search?q=%22return+err%22&uns... https://github.com/influxdata/influxdb/search?q=%22if+err+%2... The reality of Go's error handling is that you just implement exactly what exception bubb…

This is not correct. Exceptions do different things than report an error. They unwind the stack. That's why they are called exceptions and not errors. One important benefit of Go's error handling pattern is readability. With exceptions, it's not easy to see who handles it and where. There is indeed less code, and that's nice for the writer, but from the reader perspective, error handling becomes obscure. And from the…

> They unwind the stack.

That's my point. returning the err until some caller above you handles it is unwinding the stack. You're just forced to do it manually at every single level of the stack.

Re: Exploring Error Handling Patterns in Go

#67
post #7
post #4

if err != nil return err if err != nil return err if err != nil return err https://github.com/docker/cli/search?q=%22if+err+%21%3D+nil%... https://github.com/kubernetes/kubernetes/search?q=%22if+err+... https://github.com/coreos/etcd/search?q=%22return+err%22&uns... https://github.com/influxdata/influxdb/search?q=%22if+err+%2... The reality of Go's error handling is that you just implement exactly what exception bubb…

This is not correct. Exceptions do different things than report an error. They unwind the stack. That's why they are called exceptions and not errors. One important benefit of Go's error handling pattern is readability. With exceptions, it's not easy to see who handles it and where. There is indeed less code, and that's nice for the writer, but from the reader perspective, error handling becomes obscure. And from the…

> One important benefit of Go's error handling pattern is readability.

Maybe; personally I find it increased clutter that obscures readability (much as do checked exceptions.)

> With exceptions, it's not easy to see who handles it and where.

Who handles it and where is the one thing that is explicit and readily apparent with unchecked exceptions. What can be harder to see with unchecked exceptions than with error returns or checked exceptions is who (other than the original source) throws it and requires consideration of handling it or ignoring/rethrowing it in the caller.

Re: Exploring Error Handling Patterns in Go

#68
post #45

Earlier quoted context omitted.

If something "errors", you usually don't want to handle it either. Other that at the http handler level.

Actually, you often do. That's the point really. You should decide if it's an operation you might want to retry, you might also want to just flat out error and do nothing more, maybe you want to provide degraded functionality, like provide some default answer. I think errors as values cause you to always think about this, which makes you handle errors in a more sensible way, instead of just bubbling up. Sure, 90% of…

You basically said so in your last paragraph, and I agree it's a tradeoff, I just want to reiterate that in my experience, bubbling up and retrying/degrading/failing at the top level (system boundary, client side) makes for a robust (distributed) system you can reason about. Having hundreds of easter eggs in the project where somebody tried to do something smart when encountering an error seems more like a nightmare scenario to me. Old Java enterprise applications are filled with this, and they are rightfully frowned upon. Checked exceptions are the culprit.

Re: Exploring Error Handling Patterns in Go

#69

> The other bit of good news is that you can't unknowingly ignore a returned error, like you can with an unchecked exception. The compiler will force you at a minimum to declare the error as _, and tools like errcheck do a good job of keeping you honest. Actually, we unknowingly ignore returned errors much more often than we think, like when we call a function and opt out of assigning any of the return values to vari…

errcheck is a good tool to help with this.

Re: Exploring Error Handling Patterns in Go

#70
post #19

Earlier quoted context omitted.

The flipside of easy-to-learn is there's no payoff for getting better with the language. Your code will always be exactly as tedious as novices' code because they'd rather conserve compiler cycles than spend them to amplify programmers' work.

On the bright side, your code will always be as easy to understand as a novice's code too, so it conserves other programmer's mental cycles as well.

> your code will always be as easy to understand as a novice's code too

Yeah, that seems about perfectly correct. Have you looked at novice's code? Is it easy to understand?

Post reply on HN