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
- Clarity: Separating wrapping from returning makes it clear where errors are being modified
- Maintainability: Having return on its own line makes it easier to modify function signatures
- Consistency: Following a standard pattern makes error handling predictable
- Debugging: Clean error chains without redundant information or formatting issues
- Readability: No multi-line error messages in the code