Loading...
Loading...
Comprehensive Go error handling patterns from Google and Uber style guides. Covers returning errors, wrapping with %w, sentinel errors, choosing error types, handling errors once, error flow structure, and logging. Use when writing Go code that creates, returns, wraps, or handles errors.
npx skill4agent add cxuu/golang-skills go-error-handlingNormative: Required per Google's canonical Go style guide.
errorerrorerror// Good:
func Good() error { /* ... */ }
func GoodLookup() (*Result, error) {
// ...
if err != nil {
return nil, err
}
return res, nil
}nil// Bad: Concrete error type can cause subtle bugs
func Bad() *os.PathError { /*...*/ }
// Good: Always return the error interface
func Good() error { /*...*/ }context.ContexterrorNormative: Required per Google's canonical Go style guide.
// Bad:
err := fmt.Errorf("Something bad happened.")
// Good:
err := fmt.Errorf("something bad happened")// Good:
log.Infof("Operation aborted: %v", err)
log.Errorf("Operation aborted: %v", err)
t.Errorf("Op(%q) failed unexpectedly; err=%v", args, err)Normative: Required per Google's canonical Go style guide.
_log.Fatalpanic// Good:
var b *bytes.Buffer
n, _ := b.Write(p) // never returns a non-nil errorerrgroup// 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
}Normative: Required per Google's canonical Go style guide.
-1nil// 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))// 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)Parse(Lookup(key))Lookup(key)Normative: Required per Google's canonical Go style guide.
// 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
}// 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
}Advisory: Recommended best practice.
| Caller needs to match? | Message type | Use |
|---|---|---|
| No | static | |
| No | dynamic | |
| Yes | static | |
| Yes | dynamic | custom |
Advisory: Recommended best practice.
%v%w%v%werrors.Iserrors.As%w"context message: %w"errSource: Uber Go Style Guide
// 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)
}
}| Pattern | Guidance |
|---|---|
| Return type | Always use |
| 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 |
| %v vs %w | |
| %w placement | Always at the end: |
| Handle once | Choose ONE: return, log+degrade, or match+handle |
| Logging | Don't log and return; let caller decide |