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…
Curious why you're using fmt.Fprintf and not fmt.Errorf? Or is that a typo? And I think you're going to have problems with this pattern if you join a team using Go in an organisation. The `if err != nil` pattern is the norm, and everyone's used to it (and the regular cadence of Go code; "do the thing, check the error, do the thing, check the error" is very readable).
Gopher Wrangling: Effective error handling in Go
61–70 of 310 posts
Re: Gopher Wrangling: Effective error handling in Go
#62Earlier quoted context omitted.
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.
Re: Gopher Wrangling: Effective error handling in Go
#63For the love of all that is good in the world, this is a solved problem, I don't understand why languages like Go, Kotlin, Python etc etc etc continue to insist on not having sane Option, Either, Try etc types.
Re: Gopher Wrangling: Effective error handling in Go
#64Earlier 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…
let _ = fs::mkdir_all() // Error ignored, Rust will not complain because you explicitly assigned to _
Or if the function returns something I need but I don't care about the error: let Ok(file) = get_file() else {
file_unavailable();
return
}
upload_file(file);
Or this: let file = get_file().unwrap_or_default()Re: Gopher Wrangling: Effective error handling in Go
#65Earlier quoted context omitted.
I’m all for generating that. I don’t want it in source where rereading it wastes expensive developers’ time and mistakes become possible.
Copilot is pretty good at recognizing and generating the error check ; it will even propose error messages for you. Clearly you may want to change it to your specific case. So I don’t think dev time will be significantly slower. My experience has been positive. I think the go plugin also has some helpers for this pattern, but I don’t recall exactly how they work.
Re: Gopher Wrangling: Effective error handling in Go
#66For the love of all that is good in the world, this is a solved problem, I don't understand why languages like Go, Kotlin, Python etc etc etc continue to insist on not having sane Option, Either, Try etc types.
Kotlin is deliberately trying to stay closer to Java and more approachable than, say, Scala. It does have sum types and a generic Result, but the builtin special cases for nullability and exceptions are a little more ergonomic (and simplify interop with JVM APIs).
Re: Gopher Wrangling: Effective error handling in Go
#67I'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 }
Re: Gopher Wrangling: Effective error handling in Go
#68Earlier quoted context omitted.
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
#69Earlier quoted context omitted.
Result is better because it actually encodes the correct situation. You either get a file or an error. Not neither, not both. Go's encodes instead "you may or may not have a file" and "you may or may not have an error". Not the same thing, and extremely rarely what you want, IME. Other languages also do a better job of helping you verify that you actually handled both cases too. By the way I wouldn't say we need Mona…
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.
The caller trying to pretend that the success object is there isn't a freedom the caller gets in the current system, it's an artifact of the type system not being powerful enough to encode the situation accurately.
In practice (for the success object) it means you need to check for a nil pointer, make sure you don't use a zombie object, or just rely on an assumption that it's not nil, depending on which poor choice the producer function went for.
If you have a function that can return both an object and an error, there still should be a way to represent that (exactly the current way). Having Sum types would just allow a way to represent the common case accurately.
Re: Gopher Wrangling: Effective error handling in Go
#70I'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…
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 {
return
}
if err = baz(x); err != nil {
return
}
err = bat()
return
}
Seeking out different patterns is obviously most applicable in cases where error handling is actually doing something useful or more complicated than just wrapping the error.(My comment was meant to spur first principles discussion from intellectually curious folks, not "nobody does it that way" or "don't do that" edicts. Much of the argument against adding additional language features for error handling is that many of them aren't any better than what can be accomplished already, using existing syntax but different code style conventions. The goto pattern in particular is found all over the stdlib.)