main
Raw Download raw file

Golang Error Formatting Style Guide

Core Principles

1. Separate Error Wrapping from Return

Always put the error wrapping on its own line, then return on the next line.

Don’t do this:

repo, err := git.PlainOpen(".")
if err != nil {
    return fmt.Errorf("failed to open repository: %w\nMake sure you're in a git repository", err)
}

Do this:

repo, err := git.PlainOpen(".")
if err != nil {
    err = fmt.Errorf("opening repository: %w", err)
    return err
}

2. Error Message Format

Follow the pattern: action - context: %w

  • Always include the action (what was being attempted)
  • Add " - context" when additional context helps
  • Never use newlines (\n) in error messages

Examples:

// Simple action only
err = fmt.Errorf("opening repository: %w", err)

// Action with helpful context
err = fmt.Errorf("opening repository - ensure current working directory is a git repository: %w", err)

// Action with specific context
err = fmt.Errorf("parsing config - check YAML syntax: %w", err)

3. Multi-Level Error Wrapping

Be selective when wrapping errors through multiple levels:

  • Always add context at the top level
  • Only add context at lower levels if the error lacks necessary details
  • Avoid redundant wrapping
// Lower level - only wrap if adding value
func readConfig() error {
    data, err := os.ReadFile("config.yaml")
    if err != nil {
        // Only wrap if the error needs more context
        err = fmt.Errorf("reading config.yaml: %w", err)
        return err
    }
    // ...
}

// Higher level - add user-facing context
func initialize() error {
    err := readConfig()
    if err != nil {
        err = fmt.Errorf("initializing application - check configuration: %w", err)
        return err
    }
    // ...
}

4. Variable Reuse

Prefer reusing the err variable rather than creating new variables:

Preferred:

err = fmt.Errorf("action: %w", err)
return err

Avoid:

wrappedErr := fmt.Errorf("action: %w", err)
return wrappedErr

5. Capitalization

Follow Go convention - error messages should start with lowercase:

Correct:

err = fmt.Errorf("opening file: %w", err)
err = fmt.Errorf("parsing JSON: %w", err)

Incorrect:

err = fmt.Errorf("Opening file: %w", err)
err = fmt.Errorf("Failed to parse JSON: %w", err)

6. When Not to Wrap

  • Don’t wrap if there’s no error to wrap
  • When creating new errors from scratch, prefer defining custom error types
// Don't wrap non-existent errors
if someCondition {
    // Don't do: err = fmt.Errorf("something went wrong")
    // Consider creating a custom error type instead:
    return ErrInvalidState
}

// Define custom error types for common errors
var (
    ErrAlreadyExists = errors.New("resource already exists")
    ErrNotFound      = errors.New("resource not found")
    ErrInvalidInput  = errors.New("invalid input provided")
)

Additional Guidelines

Remove “failed to” Prefix

Instead of “failed to X”, just use “X” as the action:

"failed to open file: %w""opening file: %w"

"failed to connect to database: %w""connecting to database: %w"

Benefits of This Style

  1. Clarity: Separating wrapping from returning makes it clear where errors are being modified
  2. Maintainability: Having return on its own line makes it easier to modify function signatures
  3. Consistency: Following a standard pattern makes error handling predictable
  4. Debugging: Clean error chains without redundant information or formatting issues
  5. Readability: No multi-line error messages in the code