Golang Error Handling Best Practices
Dave Cheney: “Don’t just log error, handle them gracefully.”
Table of Contents
- Handle error once
- Wrap errors from non-Circle libraries
- Avoid using Go’s native
errorslibrary - Avoid returning
nil, nilin functions - Do not discard errors using
_ - Use meaningful error messages
- Do not use error strings to control business logic
- Circle Error
- Error Wrapping
- Pre-defined Errors
Handle error once
- In practice, this usually mean only doing one of the two things: log the error or return it.
Wrap errors from non-Circle libraries
- Wrap errors from non-Circle libraries (e.g.,
json.Marshal) usingerrors.Wrap. can help establish stack trace and provide a more contextual error message.
Avoid using Go’s native errors library
- Go’s
errorslibrary does not establish stack trace.
Avoid returning nil, nil in functions.
- Keeping a general assumption that if err is not nil, then response should also not be nil can reduce unnecessary nil checks and keeps code leaner.
example.go
if err != nil {
return nil, err
}Do not discard errors using _.
- Check all returned errors to ensure the function succeeded as expected. See Go Code Review Wiki
Use meaningful error messages
- A clean error message provides clarity, makes it easier to read, and simplifies debugging.
- One easy template to follow: “failed to perform action A while doing action B for reason C.”.
Do not use error strings to control business logic.
- Error messages are for debugging and may change without notice, so it’s not a reliable condition
- If needed, use error code or error type to control business logic
Circle Error
CircleError is a wrapper implementing the Error interface, designed similarly to Circle’s Java CircleException. However, due to fundamental differences between Go’s error and Java’s Exception, a direct 1:1 mapping is not feasible. Key points to remember:
example.go
type CircleError struct {
error
CircleErrorDetail
}Error Wrapping
When to Use errors.Wrapf
-
Third-party Libraries:
- For native libraries (e.g.,
json,encoding), wrapping errors provides both stack trace and helpful messages.
- For native libraries (e.g.,
-
Adding Details:
- Use
errors.Wrapfto add context to the original error. Keep in mind, the error already includes a stack trace.
- Use
Where to Use cError.Wrap
- Business Context:
- Use where you understand the business context and expected actions by the caller.
Examples:
-
Resource Layer:
- For request validation, wrap errors as
TypeClientError.
- For request validation, wrap errors as
-
Service Layer (including Workers):
- Example 1: If a wallet is frozen, return a predefined
WalletIsFrozenError. - Example 2: If a transaction is not found in BM, handle with an appropriate predefined error.
- Example 1: If a wallet is frozen, return a predefined
-
Response Helpers:
- Use
response.CustomErrorin most cases, allowing the error originator to wrap the error. - Use
response.ClientErroras a convenience for wrapping JSON unmarshalling errors intoTypeClientError.
- Use
-
Provider and Repository Layer:
- Avoid wrapping errors as Circle errors. Instead, use pkg/errors .
- Example: If a repository query does not find a record, return a flag and let the service layer decide the error type.
Pre-defined Errors
- Define a new error when it needs to be exposed through the API.
- Use predefined errors to categorize and expose specific error categories with external messages populated.
- See examples in common/error.go for reference.
Last updated on