It’s a no Go: handling errors in Golang

Yuri Brito
Wildlife Studios Tech Blog
4 min readJun 29, 2020

One of the most ubiquitous things in Go are functions with two, or more, return values, where most often than not, the last one is an error. There is a limited set of courses of action for these errors, and I’ll cover them in this article.

Handling errors

When an error occurs, a developer can:

  • Return the error to the previous caller in the call chain
  • Log the error
  • Panic and crash the application

The above alternatives are meant to be mutually exclusive, meaning that one should only handle an error once. If you return it, don’t log, because the original caller or any other ascendant in the call chain might manage the same error by logging as well.

Return the error to the previous caller in the call chain

The most common scenario. There are many opportunities for a failure to surface, so a single function might have multiple early returns in case anything goes wrong.

GetPlayer is not the entry point, so any errors are just propagated to its caller

You should take this route when there’s nothing left to do besides give up on the happy path and let the caller know about it.

Log the error

There are mainly three situations where you want to log the error:

  1. It reached the very first function in the call chain
  2. The failure doesn’t block the happy path
  3. You’re in a background task

Ideally, you’ll use a structured logger flavor, like Uber’s zap library, so that you can add context in a pretty formatted way, through fields, and log at the right level — debug, info or error.

1. It reached the very first function in the call chain

When you have no function left to return to, and a failure occurs, it’s time to decide if it’s worth to ignore it silently or if you want to keep track of this kind of problem.

CreatePlayerHandler is an HTTP handler that serves as the entry point to create new players in a web service

2. The failure doesn’t block the happy path

An intermediate function call might fail, but it’s still possible to handle the original request gracefully (in some cases). For example, you tried to fetch information from a distributed cache that is offline for some reason, so you can take note of that by logging or through metrics and move on retrieving the data from another source.

3. You’re in a background task

This situation is very similar to the first one. On background processes, there’s no external caller to propagate an error. So significant problems must either be observed through metrics or logs.

Panic and crash the application

Besides during startup initialization, panicking should always be a last resort alternative. To panic is to signal the user that the application runtime is broken beyond repair. It’s in a tainted state, and it can’t either fulfill operations, or their results are not to be trusted.

To panic is to signal the user that the application runtime is broken beyond repair

When this principle is followed, a pattern arises in which most situations susceptible to panic should happen very close to the initialization code.

Panic is something that should be reserved for applications. Libraries must avoid it. Except when there’s an explicit contract that if a function would error, this means bad news for the application, and even then, it’s easy to misuse.

Error wrapping

Wrapping errors became a pattern in Go, where in addition to implementing the builtin error interface, Error() string, people also add Wrap and Unwrap methods to have more context available.

For this reason, starting in Go 1.13 fmt.Errorf has support for a new verb %w. So fmt.Errorf(“failed to parse request body: %w”, err) will return a new error wrapping the original err with a nice message with a hint of what happened.

Because the new error doesn’t have the same type as the wrapped one, we need a mechanism to figure out what was the original error for situations where treatment depends on it. So let’s go back to the CreatePlayerHandler code example from before.

Through the use of errors.Is we can confidently handle errors in a non-generic way to provide rich information to the API client

Errors are still a hot topic in the language. At the end of January 2020, a post in the official go blog started by highlighting that a try proposal found support and opposition, so they decided to abandon the idea for now. Still, they didn’t consider the current status quo is permanent.

While error handling in Go is criticized for its verbosity, we can see that it’s not a process with high entropy, the possibilities are not that many, and being reasonable about the ones we have should be enough to be happy with the state of our code.

--

--