I’m taking a break from the Go spec for a day, to talk about some code I just found in a codebase I’m working on, which I think says some interesting things about the argument that Go’s error handling is too verbose. When I shared this code on a Slack community, someone asked for an email-lengthed explanation, so here it is!
Without further ado, here’s the code I ran across (edited slightly to be more general):
func checkConnection(ctx context.Context) (err error) {
defer func() {
if r := recover(); r != nil {
switch t := r.(type) {
case error:
err = t
default:
err = fmt.Errorf("%v", r)
}
}
}()
search.MustGetClient()
return err
}
Okay, so what’s going on here?
Well, the function is apparently meant to get a “search” client, which presumably validates the connection to the search service. If getting the client fails, we assume the connection is bad, and return an error.
To break it down, though, the first thing to note is that search.MustGetClient()
returns the client in question… or it panics. It’s convention in Go to prefix a function name with Must
if it panics in place of returning an error. Important for this example is that search.GetClient()
is equivalent, but returns a client and an error value.
So what’s that hairy defer function all about?
It’s there to recover from any panic raised by search.MustGetClient()
, and convert it to an error, so it can be returned as an error value. Cute, right?
I replaced the above function with this code:
func checkConnection(ctx context.Context) error {
_, err := search.GetClient()
return err
}
Which I then deleted entirely, to replace the single caller with an inline version of the same:
- return checkConnection(ctx)
+ _, err := search.GetClient()
+ return err
Moral of the story?
I understand the complaint that Go’s error handling is sometimes verbose. Repeating if err != nil { return err }
ad infinitum can seem tedious. On the other hand, recovering from panics is also tedious.