Live data from Hacker News

Gopher Wrangling: Effective error handling in Go

stephenn.com

51–60 of 310 posts

Re: Gopher Wrangling: Effective error handling in Go

#51

Go error "handling" blocks don't seem like error handling, when it's 3+ visual polluting LOC that just return the error up the call stack, occasionally with context like tip #4 of the blogpost. I've tried to like go's verbose error handling (follow the “happy path”) but the error handling signal to noise ratio is skewed in a way that makes developing in go feel slow and boring.

the idea that error handling "pollutes" code is a misunderstanding which go addresses the "sad path" of error handling is equally as important as the "happy path"

How does it address it? By making it painstakingly verbose (not to mention error prone) to deal with errors?

Not referring to you personally, but I've heard that sentiment several times now, and I have not seen anything to back it up (as with several other golang claims).

Re: Gopher Wrangling: Effective error handling in Go

#53

Go error "handling" blocks don't seem like error handling, when it's 3+ visual polluting LOC that just return the error up the call stack, occasionally with context like tip #4 of the blogpost. I've tried to like go's verbose error handling (follow the “happy path”) but the error handling signal to noise ratio is skewed in a way that makes developing in go feel slow and boring.

the idea that error handling "pollutes" code is a misunderstanding which go addresses the "sad path" of error handling is equally as important as the "happy path"

[deleted]

Re: Gopher Wrangling: Effective error handling in Go

#54
post #29

Earlier quoted context omitted.

In rust the type system enforces you check the error. In go, it doesn't. (Because rust error types are enums/sums and go error types are structs/products). This seems like a huge difference. > In many cases it's arguably worse because now 1 token is potentially representing two very different types I want to deal with. Yes, that's what an enum/sum type is? That's the whole point.

In rust it is not possible to use incorrectly, and in go it is, sure. But whether it is possible or not is only one dimension. Does it matter that it’s possible to misuse errors in Go if it virtually never happens? I just don’t find the point about what is possible interesting. The other trade offs around readability, ergonomics, and so on seem more impactful.

> virtually never happens

Ah yes, like it "never happened" in the Kubernetes project?

- https://github.com/kubernetes/kubernetes/pull/60962

- https://github.com/kubernetes/kubernetes/pull/80700

- https://github.com/kubernetes/kubernetes/pull/27793

- https://github.com/kubernetes/kubernetes/pull/110879

I can find tons of these, just by searching any larger Go project's Github.

Here's one from docker too: https://github.com/moby/moby/pull/10321

What about from CockroachDB? https://github.com/cockroachdb/cockroach/pull/74743 Even the linter missed this one!

Re: Gopher Wrangling: Effective error handling in Go

#55

Earlier quoted context omitted.

the idea that error handling "pollutes" code is a misunderstanding which go addresses the "sad path" of error handling is equally as important as the "happy path"

How does it address it? By making it painstakingly verbose (not to mention error prone) to deal with errors? Not referring to you personally, but I've heard that sentiment several times now, and I have not seen anything to back it up (as with several other golang claims).

with robust and potentially high volume code, the most important feature is good behavior in failure domains. disk full, do you abort or continue once the cron job frees some space; cant alloc memory, do you abort or return a static 503 page? bad contents in some file, do you exit or log the error and carry on? does a bad pyc file generate a good error message or crash python. this robustness is the famed second 90% of the project. normal go looks a cerain way when it is handling these errors.

Re: Gopher Wrangling: Effective error handling in Go

#56
post #17

I've mostly evolved to making err a named return parameter, and inverting the err != nil check. For example: func foo() (err error) { var x any if x, err = bar(); err == nil { err = baz(x) } if err == nil { err = bat() } if err != nil { err = fmt.Errorf("%w doing foo ", err) } return } This feels somewhat cleaner to me, in particular by combining error handling (in this case just a simple wrap) in a single place at t…

please don't do this it obfuscates the control flow, specifically the value that is actually returned early returns on errors are good, not bad edit you want func foo() error { x, err := bar() if err != nil { return fmt.Errorf("bar: %w", err) } if err := baz(x); err != nil { return fmt.Errorf("baz: %w", err) } if err := bat(); err != nil { return fmt.Errorf("bat: %w", err) } return nil }

Yes, I absolutely agree with this. I think there is great value in returning early on error; think of them as guards checking that you have the values you need for the next logic step. In the original version you may have to read the whole function to understand why it failed.

Re: Gopher Wrangling: Effective error handling in Go

#57
post #11

Earlier quoted context omitted.

One thing I don't get (and would honestly appreciate if was explained to me) is how the Result monad differs significantly from Go's error handling, other than being a "true" monad. Most Rust code I see does things like (from the docs): let greeting_file_result = File::open("hello.txt"); let greeting_file = match greeting_file_result { Ok(file) => file, Err(error) => // handle err }; It isn't much different from: fil…

The biggest difference is in rust you have to handle the error case, but in go you can accidentally ignore it.

This seams silly and nitpicks to my. In go, you have to assign the error, and if you assign it you have to use the variable. I've never seen this mistake before in my years of using go.

Re: Gopher Wrangling: Effective error handling in Go

#58

Earlier quoted context omitted.

The Result approach believes that the producer knows what is best for the caller regardless of who the caller is. The Go approach believes that the producer shouldn't assume it knows the caller. I'm not sure one is better than the other, just different tradeoffs.

Doesn't the producer know best whether the producer failed?

Does the caller care?

By day I work with a team in a language that sees errors ride on the exception handling system. Staying within the original example, I see code like this all the time (too often, even, but that's another topic for another day):

    try {
        file = getFile()
    } catch(/* ... */) {
        fileUnavailable()
    }
Here, the assumption of getFile that the caller wanted an error was incorrect. A Result-using language would end up in a similar place.

Idiomatic Go says leave it to the caller. Like above, when only wants to know if there is "file or no file" without concern for why there is no file, then:

    file, _ := getFile() // The second return argument is an error.
    if file == nil {
        fileUnavailable()
    }
I doubt either way makes much difference in this contrived example, but the difference shows up when it extends out into real code. There are plusses and minuses to each way of seeing the world. Tradeoffs, as always.

Re: Gopher Wrangling: Effective error handling in Go

#59
post #54

Earlier quoted context omitted.

In rust it is not possible to use incorrectly, and in go it is, sure. But whether it is possible or not is only one dimension. Does it matter that it’s possible to misuse errors in Go if it virtually never happens? I just don’t find the point about what is possible interesting. The other trade offs around readability, ergonomics, and so on seem more impactful.

> virtually never happens Ah yes, like it "never happened" in the Kubernetes project? - https://github.com/kubernetes/kubernetes/pull/60962 - https://github.com/kubernetes/kubernetes/pull/80700 - https://github.com/kubernetes/kubernetes/pull/27793 - https://github.com/kubernetes/kubernetes/pull/110879 I can find tons of these, just by searching any larger Go project's Github. Here's one from docker too: https://githu…

Of course it happens, I’ve done it. But it’s obvious, it’s the least interesting aspect of the debate. And I think it is less impactful than e.g making exceptions easy for devs to ignore.

Re: Gopher Wrangling: Effective error handling in Go

#60
post #17

I've mostly evolved to making err a named return parameter, and inverting the err != nil check. For example: func foo() (err error) { var x any if x, err = bar(); err == nil { err = baz(x) } if err == nil { err = bat() } if err != nil { err = fmt.Errorf("%w doing foo ", err) } return } This feels somewhat cleaner to me, in particular by combining error handling (in this case just a simple wrap) in a single place at t…

please don't do this it obfuscates the control flow, specifically the value that is actually returned early returns on errors are good, not bad edit you want func foo() error { x, err := bar() if err != nil { return fmt.Errorf("bar: %w", err) } if err := baz(x); err != nil { return fmt.Errorf("baz: %w", err) } if err := bat(); err != nil { return fmt.Errorf("bat: %w", err) } return nil }

I’m all for generating that. I don’t want it in source where rereading it wastes expensive developers’ time and mistakes become possible.
Post reply on HN