Skip to Content
DocumentationCode ReviewBest PracticesGolangError Handling

Golang Error Handling Best Practices

Dave Cheney: “Don’t just log error, handle them gracefully.”

Table of Contents

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) using errors.Wrap. can help establish stack trace and provide a more contextual error message.

Avoid using Go’s native errors library

  • Go’s errors library 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 _.

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.
  • Adding Details:

    • Use errors.Wrapf to add context to the original error. Keep in mind, the error already includes a stack trace.

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.
  • 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.
  • Response Helpers:

    • Use response.CustomError in most cases, allowing the error originator to wrap the error.
    • Use response.ClientError as a convenience for wrapping JSON unmarshalling errors into TypeClientError.
  • 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