A cursory look at the article, shows that the most important observation about error handling in Go is missing.
Errors should be "decorated" (wrapped, contextualized...) in 99% of the cases.
In the end you get errors that describe step by step what your program tried to do and why it failed, for example:
* could not load profile: could not open file: permission denied.
* could not download profile image: could not open URL: HTTP GET failed: network is down.
This has many advantages:
1. Much more readable than stack traces (especially if they include source file and line information or exception class names: users don't care about those.)
2. Errors are still easy to grep in code to work out the program flow (the stack trace, basically.)
3. When reading the code, you can see from the error context strings what the code is actually doing. Basically it serves a function of comments and (unlike comments) error strings remain up to date.
It is definitely verbose, especially the not equal nil part, as it's a result of Go attempt not to have special cases. Also it's a pity that errors can be silently ignored: maybe Go2 could be stricter here.
Overall, I think this is one of the best approaches at error handling.
In my experience, this just becomes arbitrarily close to "re-implement your stack trace by hand with space-delimited words instead of camelCaseFunctionNamesOrWhatever".
I'll overwhelmingly prefer an always-correct stacktrace over a hand-recreated one that sometimes collapses multiple branches into a single ambiguous on. At least then the devs can help me when it fails. And stack traces and concatenated strings are in no way appropriate error responses for humans unless you're expecting them to be able to navigate the source code, so neither does anything for the "provide a helpful error message for non-programmers" problem.
---
this is why stuff like https://github.com/pkg/errors exists. wrap at the deepest level / where the error originates, and it's relatively rare that you need to add context at higher levels. If you want user-friendly errors, you need something dramatically more sophisticated.
I appreciated the article, but the error handling in Go just bugs me. So verbose...
The built-in tool does not even warn about unused errors... not `go build`, and not `go vet`. What's more important, an ignored error or an unused import?
I wish more developers could do "investigation" like this for a new languages they learn.
For me, the main difference between Go's way of handling language and the rest of mainstream languages is that it makes error handling unmagical.
It literally says – errors are just like any other return values. Let's say, if you have function `sqrt` and return a value, and then call this function – you probably is interested in this return value and should handle it somehow (or mute with `_`). Now, the same applies for errors - if function returns the error, you likely to think how to handle it – do something in place or propagate up the stack.
There is also a cultural moment to this. As we mostly learn by examples, and most Go code has proper error checks (not equal to "proper error handling" but nevertheless), it makes newcomers to do the same as well, even while disagreeing with Go's way. I've heard from many devs that Go was the reason that made them appreciate proper error handling.
And honestly, I feel this too, and I think the reason is that in Go it's too easy to "handle errors properly". I never had this feeling with languages with exceptions, where I had to read whole books (!) just to learn how to properly use them and be confident in the way how I handle errors. (this is just an example, not the spark to start return values vs exceptions battle, just in case)
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.
> I've heard from many devs that Go was the reason that made them appreciate proper error handling.
I agree that in some way, this is actually a good reason to support Go's tedious way of handling errors.
Yet, it's akin to avoiding functions (because stackframes are magical), or "for" loops (because their condition block is magical), or threads (because: magic). There is only so much a non-toy programming language should compromise in order to accomodate beginners. People may draw different lines here, but Exceptions (in garbage collected languages) are so completely unmagical, that the line should definitely not be drawn here. In fact, they do exactly what return nil, err does, thousands of times, over and over. In Go you can enjoy writing that code yourself. Also, Go's language designers accepted defeat when they had to add panics. I bet that beginners now just make the mistake of ignoring them instead.
What is not covered here, and what I'm still searching for a good pattern for, is being able to return different errors depending on the type of failure.
Suppose you have a function that fetches a model from your database. It can return an error if the given user doesn't have permission to fetch this model, or it can return an error if your db connection barfs for some reason. The calling function needs to be able to differentiate between the two errors. Most of what I've read on the subject makes it seem like people prefer to only ever check if err != nil.
The two options I've seen in the wild are:
1. Create a constant for a given error, like:
var ErrFetchForbidden = errors.New("FETCH_FORBIDDEN")
this has the benefit that the errorer can put more specific info into the error besides the Error() string.
var err ErrFetchForbidden = "error retrieving the user object"
return err
and then the caller can switch on type
switch v := err.(type) {
case ErrFetchForbidden:
return 403
case ErrFetchNotFound:
return 404
default:
return 500
}
We've gone with option 2 for now, (wrapping them with the pkg/errors package) because it seems simpler. Anyone else have good patterns for handling this?
> 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`?
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)`...
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 quality control point if view, this becomes unsafe.
Error/Either monads are the perfect middle ground IMHO. You get errors as data types and an efficient way to abstract away the boilerplate associated with it.
[+] [-] dullgiulio|7 years ago|reply
Errors should be "decorated" (wrapped, contextualized...) in 99% of the cases.
In the end you get errors that describe step by step what your program tried to do and why it failed, for example:
* could not load profile: could not open file: permission denied.
* could not download profile image: could not open URL: HTTP GET failed: network is down.
This has many advantages:
1. Much more readable than stack traces (especially if they include source file and line information or exception class names: users don't care about those.)
2. Errors are still easy to grep in code to work out the program flow (the stack trace, basically.)
3. When reading the code, you can see from the error context strings what the code is actually doing. Basically it serves a function of comments and (unlike comments) error strings remain up to date.
It is definitely verbose, especially the not equal nil part, as it's a result of Go attempt not to have special cases. Also it's a pity that errors can be silently ignored: maybe Go2 could be stricter here.
Overall, I think this is one of the best approaches at error handling.
[+] [-] Groxx|7 years ago|reply
I'll overwhelmingly prefer an always-correct stacktrace over a hand-recreated one that sometimes collapses multiple branches into a single ambiguous on. At least then the devs can help me when it fails. And stack traces and concatenated strings are in no way appropriate error responses for humans unless you're expecting them to be able to navigate the source code, so neither does anything for the "provide a helpful error message for non-programmers" problem.
---
this is why stuff like https://github.com/pkg/errors exists. wrap at the deepest level / where the error originates, and it's relatively rare that you need to add context at higher levels. If you want user-friendly errors, you need something dramatically more sophisticated.
[+] [-] tln|7 years ago|reply
The built-in tool does not even warn about unused errors... not `go build`, and not `go vet`. What's more important, an ignored error or an unused import?
https://play.golang.org/p/j-oXsZz51ki
[+] [-] pstuart|7 years ago|reply
[+] [-] divan|7 years ago|reply
For me, the main difference between Go's way of handling language and the rest of mainstream languages is that it makes error handling unmagical.
It literally says – errors are just like any other return values. Let's say, if you have function `sqrt` and return a value, and then call this function – you probably is interested in this return value and should handle it somehow (or mute with `_`). Now, the same applies for errors - if function returns the error, you likely to think how to handle it – do something in place or propagate up the stack.
There is also a cultural moment to this. As we mostly learn by examples, and most Go code has proper error checks (not equal to "proper error handling" but nevertheless), it makes newcomers to do the same as well, even while disagreeing with Go's way. I've heard from many devs that Go was the reason that made them appreciate proper error handling.
And honestly, I feel this too, and I think the reason is that in Go it's too easy to "handle errors properly". I never had this feeling with languages with exceptions, where I had to read whole books (!) just to learn how to properly use them and be confident in the way how I handle errors. (this is just an example, not the spark to start return values vs exceptions battle, just in case)
[+] [-] erik_seaberg|7 years ago|reply
[+] [-] groestl|7 years ago|reply
I agree that in some way, this is actually a good reason to support Go's tedious way of handling errors.
Yet, it's akin to avoiding functions (because stackframes are magical), or "for" loops (because their condition block is magical), or threads (because: magic). There is only so much a non-toy programming language should compromise in order to accomodate beginners. People may draw different lines here, but Exceptions (in garbage collected languages) are so completely unmagical, that the line should definitely not be drawn here. In fact, they do exactly what return nil, err does, thousands of times, over and over. In Go you can enjoy writing that code yourself. Also, Go's language designers accepted defeat when they had to add panics. I bet that beginners now just make the mistake of ignoring them instead.
[+] [-] macrael|7 years ago|reply
Suppose you have a function that fetches a model from your database. It can return an error if the given user doesn't have permission to fetch this model, or it can return an error if your db connection barfs for some reason. The calling function needs to be able to differentiate between the two errors. Most of what I've read on the subject makes it seem like people prefer to only ever check if err != nil.
The two options I've seen in the wild are:
1. Create a constant for a given error, like:
Then the calling function can do: 2. Create a custom type for your error like so: this has the benefit that the errorer can put more specific info into the error besides the Error() string. and then the caller can switch on type We've gone with option 2 for now, (wrapping them with the pkg/errors package) because it seems simpler. Anyone else have good patterns for handling this?[+] [-] cube2222|7 years ago|reply
Create a custom error type, for example DB Error:
Now you can provide functions like IsTemporary(err).Otherwise, you can use 2# with a twist, instead of matching on a type, you can do:
or even:[+] [-] mdwhatcott|7 years ago|reply
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).
You can always choose to call an error-returning function without declaring any placeholder (`_`): There are several commonly used functions that return errors that are regularly ignored. How about `io.Writer`? 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)`...[+] [-] ecnahc515|7 years ago|reply
[+] [-] arendtio|7 years ago|reply
https://dave.cheney.net/2016/04/27/dont-just-check-errors-ha...
[+] [-] rco8786|7 years ago|reply
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 bubbling does painfully by hand.
[+] [-] chmike|7 years ago|reply
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 quality control point if view, this becomes unsafe.
[+] [-] BreakfastB0b|7 years ago|reply
[+] [-] m_sahaf|7 years ago|reply
https://blog.golang.org/errors-are-values
[+] [-] Sidnicious|7 years ago|reply
https://github.com/s4y/go-exc
I’m not sure if I’d use it again today, but it was a fun exercise.