Build your own stacktrace
Go developers sometimes have difficulty working out where errors came from in their code. This is usually a symptom of bubbling up errors with no decoration.
1
2
3
if err != nil {
return err
}
Failure to wrap errors where appropriate produces errors that seem to have “come out of nowhere,” with little to no diagnostic information provided.
Software developers are often used to having this diagnostic information provided automatically, most commonly by stacktraces. But Go does not have automatically generated stacktraces - except in the case of an escaped panic()
, which exists outside the normal error-handling flow.
Instead, Go invites you to build the stacktrace yourself. How? By decorating errors with relevant context.
1
2
3
4
f, err := os.Open(path)
if err != nil {
return fmt.Errorf("could not open %v: %w", path, err)
}
Provided that error strings are unique, it’s easy enough to grep
through a codebase, even a large one, to work out where an error came from. Errors deep in the call stack will be decorated multiple times: could not a: because b: bad c: failed to d: e not found
.
In fact, that nested decoration is a stacktrace. It’s better than a stacktrace: it’s an explanation of what went wrong that can be understood by both the programmer and the end user.
The difficulty with stacktraces is that they only make sense to the programmer. They might understand the file API, or the HTTP library, or the null/nil/None concatenation error, but to the user, it’s a sudden and unpleasant wall of text.
For this reason, great care is usually taken to prevent a stacktrace from appearing externally. This typically manifests as a try
/catch
somewhere near to the user-visible parts of the code, along with a best-effort re-interpretation of the error into something acceptable to display. This re-interpretation is typically vague, inaccurate, or both.
But in Go programs, while the stacktraces need to be manually managed, there is usually no need to hide them from an application user. In fact, surfacing them to the user may empower them to fix the problem themselves, even if they are not a programmer. could not open file foo.txt: file does not exist
doesn’t require a computer science degree to work out.
This is why Go errors are stringly typed. They are meant to be interpreted by humans, not by other code. Most of the time, sentinel errors aside, programs only need to know if an error happened or didn’t happen. Rather than taking great pains to try to mask every last exception class with a human-readable error, Go errors are human-readable by default. With the caveat, of course, that the programmer does their best to keep them that way.