Live data from Hacker News

Gopher Wrangling: Effective error handling in Go

stephenn.com

241–250 of 310 posts

Re: Gopher Wrangling: Effective error handling in Go

#241
post #70
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…

Some other variants I've played with: func foo() (err error) { var x any if x, err = bar(); err != nil { goto fooError } if err = baz(x); err != nil { goto fooError } if err = bat(); err != nil { goto fooError } return fooError: return fmt.Errorf("%w doing foo ", err) } Or: func foo() (err error) { defer func() { if err != nil { err = fmt.Errorf("%w doing foo ", err) } }() var x any if x, err = bar(); err != nil { re…

> The goto pattern in particular is found all over the stdlib.

in generated code, sure -- that's why it exists, to support codegen

it's sometimes abused to manage for loop control flow

but the stdlib is definitely not some platonic ideal -- it's a decade+ old code base which has suffered all of the indignities of organic growth

it's full of bad code and terrible anti-patterns

(good stuff, too!)

Re: Gopher Wrangling: Effective error handling in Go

#243
post #236

Earlier quoted context omitted.

> You have to go out of your way to actually use a special character to do it. Only if the function returns more than the error. You can happily do this without errors: fh = os.Create("/some/file") defer fh.Close() Needless to say, this is a terrible idea if the underlying filesystem can give you an error at close time, e.g. on NFS. The correct way to write the above code would be: fh = os.Create("/some/file") defer…

Oh you're right. I had forgotten about that. I think it's mostly an API legacy mistake. Close should probably return (bool, error). Probably a remnant of coding in C wrt sentinel values.

You could still ignore both returned values. Go shouldn't allow ignoring returns without explicit dogsleds (underscore) at all if it were to stay "in character".

Re: Gopher Wrangling: Effective error handling in Go

#244

Earlier quoted context omitted.

convention prevents it and in the case where returning both is OK, then documentation makes that clear this is not difficult

Convention in no way prevents anything. Convention is simply that. People are free to not follow convention when nothing is enforcing it. You frequently see juniors, who may be brand new to the language, making mistakes with conventions. If I'm supposed to depend on the vagaries of some accepted standard that is only documented in text then it is less than useless in the real world

if we say a language "addresses" a given concern, is it necessary that this is accomplished in the compiler, and that the rules for that concern, whatever they are, are enforced at compile-time?

(spoiler: no)

Re: Gopher Wrangling: Effective error handling in Go

#245

Earlier quoted context omitted.

Less than I thought I would. I work with a very large Go codebase, and I don't remember the last time I had problems because I needed a stack trace. Just grepping for the error message is enough to show me exactly where it happened. Still, this doesn't mean that Go does not have stack traces. It does have stack traces for panics, and you can create stack traces by wrapping errors.

I also worked on a very large golang codebase, and error traces were sorely missed. Searching for the error string is not sufficient. What happens when the string changes in the master branch, which is different from the deployed version? What about when there are different code paths to get to the same error message? I'm aware that it has stack traces for panics, but those should be rare in practice. Day to day debu…

You will only have problems with the same error message if they are in the same function, otherwise the wrapped errors will show a different path. It is possible, but they will be close to each other.

Stack traces can also point to code that is not in the master branch anymore, so it's not like they are immune from it. In both cases (Java and Go), you can git-checkout the deployed commit and then locate the error.

I guess we just have very different experiences. I worked with a large Java codebase in the past, and there is no way in the world I am going back to Java now that I tried Go.

Re: Gopher Wrangling: Effective error handling in Go

#246
post #54

Earlier quoted context omitted.

> 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.

Maybe it is unpopular opinion, but I think the go-compiler in `go build`, `go install`, `go test` commands should check for all unhanded errors and not compile the program if there are any.

It happens in personal projects, small teams and big ones too. The linter errors are too often ignored by developers. Also, it should help embrace in developer the need to explicitly name some error to be ignored.

Re: Gopher Wrangling: Effective error handling in Go

#247

Earlier quoted context omitted.

It sounds like you think about error handling a lot. Is there a language that has error handling "done well " that you like?

Error handling is a difficult topic. Generally, the more you can catch in the compiler, the less you have to write runtime checks and the obligatory unit tests that everyone likes to forget. So if you are on the lookout for a language, I'd look for something that has explicit nullable/non-nullable types, as well as strict and static typing. However, I wouldn't pick a language purely based on its error handling capabi…

> Over 10k lines of code it becomes really hard to keep things straight. However, that's more due to its very limited scoping abilities.

Could you please elaborate more on this?

Re: Gopher Wrangling: Effective error handling in Go

#248
post #116

Go's error handling is a horrible mess: 1. It's easy to ignore returned errors without any compiler warnings. You have to rely on third party tools such as golangci-lint to report missing error handling. 2. Errors don't carry stack traces with them, you have to rely on third party libraries or custom errors to get that functionality and you will only get it for your own code, not in other libraries you are using. 3.…

I agree that there is room for improvement, but I don’t mind Go’s errors that much. Using a linter to make sure errors are checked doesn’t seem like a major problem (you have to run a linter anyway, so what’s the harm?); most Go developers reflexively check errors for everything besides fmt.Println anyway. It would be better to put this in the compiler I suppose, but not a major deal. Also worth noting that Rust does…

Unfortunately, fmt.Errorf makes errors.Is/As useless. In fact, errors.Is is mostly useless in general, since very few Go libraries have any error types at all. You're usually stuck with parsing error messages if you actually want to handle errors programmatically, even for much of the standard library.

Re: Gopher Wrangling: Effective error handling in Go

#249

This feels like it has been written by someone who recently started using the language, considering that the code in many places simply doesn't compile and has syntax errors or logical errors in it. Many people coming into Go as a new language immediately start bickering about how they want their previous language features in Go rather than accept what Go has to offer and at least try to understand it. This is the eq…

> CreateMyFriggingObjectFactorySingletonBuilderFactoryBuilders 2005 called, they want their Enterprise Java™ jokes back.

I _WISH_ this would be 2005. Did you ever work at a bank? They are still using 1.6-1.9 maybe.

I still know modern Java codebases where long descriptive class names are a must. So sadly, while I understand your sarcasm, it is not the case.

Re: Gopher Wrangling: Effective error handling in Go

#250

Earlier quoted context omitted.

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 w…

> 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()
  }
That very often doesn't work in Go. Most functions which return errors offer no guarantee whatsoever about the return value if there is an error. No one would consider it a breaking change to modify the return in case of error. And many functions return a struct, where Go offers no way to compare two arbitrary struct values for equality, or to check if an arbitrary struct value is that struct's "zero value".

So no, Go does not recommend (or even endorse) this pattern.

Post reply on HN