Live data from Hacker News

Gopher Wrangling: Effective error handling in Go

stephenn.com

301–310 of 310 posts

Re: Gopher Wrangling: Effective error handling in Go

#301

Earlier quoted context omitted.

If your original error is `fmt.Errorf("failed to frobulate")` or even `errors.New("failed to frobulate")`, even if it's bubbled up with `fmt.Errorf("foo() failed: %w", err)`, errors.Is and errors.As are useless for checking it (each error in the chain, including the original, will be a new instance of fmt.stringError or something like that). This is equivalent to the problem of throwing new Exception("some message")…

You are wrong: https://go.dev/play/p/6PTQk0xWAAl

That's not how people commonly use errors, with global variables.

The way code usually looks like is this:

  func foo(arg int) error {
    if arg 0, got %d", arg)
    }
    if arg > 8 {
      return fmt.Errorf("expected >8, got %d", arg)
    }
    //do stuff
  }
or

  func foo(arg int) error {
    if arg 0, got %d", arg))
    }
    if arg > 8 {
      return errors.New(fmt.Sprintf("expected >8, got %d", arg))
    }
    //do stuff
  }
In either of these cases, a caller of foo() can at best parse err.String() to see why foo() complained. But there is no way that errors.As or errors.Is help with the majority of errors returned by Go programs.

Re: Gopher Wrangling: Effective error handling in Go

#302
post #275

Earlier quoted context omitted.

> Uh? If you use `%w` in fmt.Errorf(), it should still work with .Is and .As? You still need a stable error to compare against in errors.Is. fmt.Errorf is not going to provide that if the message is dynamic - which is almost always.

I think what you're talking about with "stable" and "dynamic" are error constants (like `io.EOF`) and instances of error types (like `url.Error` https://pkg.go.dev/net/url#Error ). You use `Is` for the former and `As` for the latter; fmt.Errorf's %w directive doesn't inhibit either case.

The problem is that the first-level error in most Go functions is fmt.Errorf(some error message, some args). The idea of pre-allocating an exported global error object (like io.EOF) is very rarely useful, and error structs are very rarely used either, because they require much more ceremony than just returning a fmt.Errorf() (or even an errors.New()) on the spot. But errors.Is is only useful if you take the first option, and errors.As is only useful if you take the second option.

So, in practice, neither errors.Is nor errors.As are terribly useful, and %w just gives you a false sense of usefulness.

Re: Gopher Wrangling: Effective error handling in Go

#303
post #186

Earlier quoted context omitted.

> I really dislike exceptions because there's no documentation for how a function can fail. For this reason I prefer go style errors, which are an improvement on the C error story. Yes it has warts, but it's 80% good enough. I’m not a go developer. How does go document how a function can fail? A Java developer can use checked exceptions so that some information is in the signature. For unchecked exceptions the docume…

> I’m not a go developer. How does go document how a function can fail? There's no magic to it. Errors are values, so it's a part of the function signature that there's an error code to check. In C++ any function can throw an exception and there's no way of knowing that it wont. It's true that go doesn't document what _kinds_ of errors it can throw, but at least I know there's something to check.

But that doesn’t document _how_ a function can fail. Just that it _can_ fail.

Re: Gopher Wrangling: Effective error handling in Go

#304

Earlier quoted context omitted.

You are wrong: https://go.dev/play/p/6PTQk0xWAAl

That's not how people commonly use errors, with global variables. The way code usually looks like is this: func foo(arg int) error { if arg 0, got %d", arg) } if arg > 8 { return fmt.Errorf("expected >8, got %d", arg) } //do stuff } or func foo(arg int) error { if arg 0, got %d", arg)) } if arg > 8 { return errors.New(fmt.Sprintf("expected >8, got %d", arg)) } //do stuff } In either of these cases, a caller of foo()…

I guess it's the same in most of languages. If you raise a general exception with a custom message you'll have to check the message not it's type.

Re: Gopher Wrangling: Effective error handling in Go

#305
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.…

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

[deleted]

Re: Gopher Wrangling: Effective error handling in Go

#306

Earlier quoted context omitted.

The error handling is second nature to anyone who has done C or Unix programming. It just feels dirty not to check for an an error. This is one part I like about Go.

Unfortunately, at scale a feeling of dirtiness doesn't prevent a lot of really bad code from being written. Looking across the Go landscape, inadequate error handling is present in almost all projects I come across.

There's other opinions out there, that discuss the direction taken by Go on error handling, for example this Vlang related article[1] that touched on it (from someone who appears to be a gopher).

However, part of the issue is the tolerance or maturity necessary for being able to handle a different opinion. That someone gives a different preference, for instance Vlang or Odin (that has its own views), can make evangelists or "corporate machinery" upset. Then we can witness mob or anger downvoting. This then limits the debate or creates more of a barrier for different opinions to want to contribute or ever be seen.

[1]: "Is Vlang better than Golang in error handling?" (https://towardsdev.com/is-vlang-better-than-golang-in-error-...).

Re: Gopher Wrangling: Effective error handling in Go

#307

Earlier quoted context omitted.

chaining means combining a sequence of expressions that each take the same input as they give as output ; doesn't do this, afaict when you're writing imperative code it's important that control flow (return) is explicitly visible

> chaining means combining a sequence of expressions that each take the same input as they give as output > ; doesn't do this, afaict It's chaining together transitions in the state machine that is your program. > when you're writing imperative code it's important that control flow (return) is explicitly visible Control flow is visible with `?` or other do-notation variants. If I want to error out in a `Result` conte…

bad:

    first()?.second()?.third()?
good:

    a = first()
    if a failed, handle that error
    b = second(a)
    if b failed, handle that error
    c = third(b)
    if c failed, handle that error
    yield c

Re: Gopher Wrangling: Effective error handling in Go

#308

Earlier quoted context omitted.

You are wrong: https://go.dev/play/p/6PTQk0xWAAl

That's not how people commonly use errors, with global variables. The way code usually looks like is this: func foo(arg int) error { if arg 0, got %d", arg) } if arg > 8 { return fmt.Errorf("expected >8, got %d", arg) } //do stuff } or func foo(arg int) error { if arg 0, got %d", arg)) } if arg > 8 { return errors.New(fmt.Sprintf("expected >8, got %d", arg)) } //do stuff } In either of these cases, a caller of foo()…

That’s not the fault of fmt.Errorf(); if you aren’t giving people a type or a value to compare against there’s nothing the standard library can do to help you. You need to try string matching or maybe reflection. The standard library can’t fix bad code. Thankfully I don’t share your experience with people commonly returning those kinds of errors (at least not where one needs to match against an error).

Re: Gopher Wrangling: Effective error handling in Go

#309

Earlier quoted context omitted.

I think what you're talking about with "stable" and "dynamic" are error constants (like `io.EOF`) and instances of error types (like `url.Error` https://pkg.go.dev/net/url#Error ). You use `Is` for the former and `As` for the latter; fmt.Errorf's %w directive doesn't inhibit either case.

The problem is that the first-level error in most Go functions is fmt.Errorf(some error message, some args). The idea of pre-allocating an exported global error object (like io.EOF) is very rarely useful, and error structs are very rarely used either, because they require much more ceremony than just returning a fmt.Errorf() (or even an errors.New()) on the spot. But errors.Is is only useful if you take the first opt…

This isn’t my experience. Most popular libraries seem to give an error type or value at least when someone might reasonably want to match on it. But yeah, if your coworkers don’t do this then Go can’t stop them (and in any case, I don’t see how fmt.Errorf() is to blame).

Re: Gopher Wrangling: Effective error handling in Go

#310
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…

I like your second variant - some DRY, moved to the top of the function. This would reduce fiddling during preliminary development, with loggers and Printf's and whatevers, until an error architecture stabilizes.
Post reply on HN