go-error-handling

📁 cxuu/golang-skills 📅 Jan 27, 2026
67
总安装量
67
周安装量
#6171
全站排名
安装命令
npx skills add https://github.com/cxuu/golang-skills --skill go-error-handling

Agent 安装分布

github-copilot 60
codex 60
gemini-cli 59
opencode 59
kimi-cli 57
amp 57

Skill 文档

Go Error Handling

In Go, errors are values – they are created by code and consumed by code. This skill covers how to return, structure, wrap, and handle errors effectively.


Returning Errors

Normative: Required per Google’s canonical Go style guide.

Use the error Type

Use error to signal that a function can fail. By convention, error is the last result parameter.

// Good:
func Good() error { /* ... */ }

func GoodLookup() (*Result, error) {
    // ...
    if err != nil {
        return nil, err
    }
    return res, nil
}

Never return concrete error types from exported functions – a concrete nil pointer can become a non-nil interface value:

// Bad: Concrete error type can cause subtle bugs
func Bad() *os.PathError { /*...*/ }

// Good: Always return the error interface
func Good() error { /*...*/ }

Return Values on Error

When a function returns an error, callers must treat all non-error return values as unspecified unless explicitly documented. Commonly, non-error return values are their zero values.

Tip: Functions taking a context.Context should usually return an error so callers can determine if the context was cancelled.


Error Strings

Normative: Required per Google’s canonical Go style guide.

Error strings should not be capitalized and should not end with punctuation:

// Bad:
err := fmt.Errorf("Something bad happened.")

// Good:
err := fmt.Errorf("something bad happened")

Exception: Error strings may start with a capital letter if they begin with an exported name, proper noun, or acronym.

Rationale: Error strings usually appear within other context before being printed.

For displayed messages (logs, test failures, API responses), capitalization is appropriate:

// Good:
log.Infof("Operation aborted: %v", err)
log.Errorf("Operation aborted: %v", err)
t.Errorf("Op(%q) failed unexpectedly; err=%v", args, err)

Handling Errors

Normative: Required per Google’s canonical Go style guide.

Code that encounters an error must make a deliberate choice about how to handle it. Do not discard errors using _ variables.

When a function returns an error, do one of:

  1. Handle and address the error immediately
  2. Return the error to the caller
  3. In exceptional situations: call log.Fatal or (if absolutely necessary) panic

Intentionally Ignoring Errors

In rare cases where ignoring an error is appropriate, add a comment explaining why:

// Good:
var b *bytes.Buffer
n, _ := b.Write(p) // never returns a non-nil error

Using errgroup for Related Operations

When orchestrating related operations where only the first error is useful, errgroup provides a convenient abstraction:

// Good: errgroup handles cancellation and first-error semantics
g, ctx := errgroup.WithContext(ctx)
g.Go(func() error { return task1(ctx) })
g.Go(func() error { return task2(ctx) })
if err := g.Wait(); err != nil {
    return err
}

Avoid In-Band Errors

Normative: Required per Google’s canonical Go style guide.

Do not return special values like -1, nil, or empty string to signal errors:

// Bad: In-band error value
// Lookup returns the value for key or -1 if there is no mapping for key.
func Lookup(key string) int

// Bad: Caller mistakes can attribute errors to wrong function
return Parse(Lookup(missingKey))

Use multiple return values instead:

// Good: Explicit error or ok value
func Lookup(key string) (value string, ok bool)

// Good: Forces caller to handle the error case
value, ok := Lookup(key)
if !ok {
    return fmt.Errorf("no value for %q", key)
}
return Parse(value)

This prevents callers from writing Parse(Lookup(key)) – it causes a compile-time error since Lookup(key) has 2 outputs.


Indent Error Flow

Normative: Required per Google’s canonical Go style guide.

Handle errors before proceeding with normal code. This improves readability by enabling the reader to find the normal path quickly.

// Good: Error handling first, normal code unindented
if err != nil {
    // error handling
    return // or continue, etc.
}
// normal code
// Bad: Normal code hidden in else clause
if err != nil {
    // error handling
} else {
    // normal code that looks abnormal due to indentation
}

Avoid If-with-Initializer for Long-Lived Variables

If you use a variable for more than a few lines, move the declaration out:

// Good: Declaration separate from error check
x, err := f()
if err != nil {
    return err
}
// lots of code that uses x
// across multiple lines
// Bad: Variable scoped to else block, hard to read
if x, err := f(); err != nil {
    return err
} else {
    // lots of code that uses x
    // across multiple lines
}

Error Types

Advisory: Recommended best practice.

If callers need to distinguish different error conditions programmatically, give errors structure rather than relying on string matching. Choose the right error type based on whether callers need to match errors and whether messages are static or dynamic.

Quick decision table:

Caller needs to match? Message type Use
No static errors.New("message")
No dynamic fmt.Errorf("msg: %v", val)
Yes static var ErrFoo = errors.New("...")
Yes dynamic custom error type

For detailed coverage of sentinel errors, structured error types, and error checking patterns, see references/ERROR-TYPES.md.


Error Wrapping

Advisory: Recommended best practice.

The choice between %v and %w significantly impacts how errors are propagated and inspected:

  • Use %v: At system boundaries, for logging, to hide internal details
  • Use %w: To preserve error chain for errors.Is/errors.As inspection

Key rules:

  • Place %w at the end: "context message: %w"
  • Add context that callers don’t have; don’t duplicate existing info
  • If annotation adds nothing, just return err directly

For detailed coverage of wrapping patterns, placement, adding context, and logging best practices, see references/WRAPPING.md.


Handle Errors Once

Source: Uber Go Style Guide

When a caller receives an error, it should handle each error only once. Choose ONE response:

  1. Return the error (wrapped or verbatim) for the caller to handle
  2. Log and degrade gracefully (don’t return the error)
  3. Match and handle specific error cases, return others

Never log AND return – this causes duplicate logging as callers up the stack will also handle the error.

// Bad: Logs AND returns - causes noise in logs
u, err := getUser(id)
if err != nil {
    log.Printf("Could not get user %q: %v", id, err)
    return err  // Callers will also log this!
}

// Good: Wrap and return - let caller decide how to handle
u, err := getUser(id)
if err != nil {
    return fmt.Errorf("get user %q: %w", id, err)
}

// Good: Log and degrade gracefully (don't return error)
if err := emitMetrics(); err != nil {
    // Failure to write metrics should not break the application
    log.Printf("Could not emit metrics: %v", err)
}
// Continue execution...

// Good: Match specific errors, return others
tz, err := getUserTimeZone(id)
if err != nil {
    if errors.Is(err, ErrUserNotFound) {
        // User doesn't exist. Use UTC.
        tz = time.UTC
    } else {
        return fmt.Errorf("get user %q: %w", id, err)
    }
}

Quick Reference

Pattern Guidance
Return type Always use error interface, not concrete types
Error strings Lowercase, no punctuation
Ignoring errors Comment explaining why it’s safe
In-band errors Avoid; use multiple returns
Error flow Handle errors first, no else clauses
Error type choice Match needed + dynamic → custom type; static → sentinel
Sentinel errors Use errors.Is for checking
%v vs %w %v for boundaries, %w for chain preservation
%w placement Always at the end: "context: %w"
Handle once Choose ONE: return, log+degrade, or match+handle
Logging Don’t log and return; let caller decide

See Also

  • go-style-core: Core Go style principles and formatting
  • go-naming: Naming conventions including error naming (ErrFoo)
  • go-testing: Testing patterns including error testing
  • go-defensive: Defensive programming including panic handling
  • go-linting: Linting tools that catch error handling issues

Reference Files