go-error-handling
Idiomatic Golang error handling — creation, wrapping with %w, errors.Is/As, errors.Join, and best practices for structured logging.
SKILL.mdskills/go/go-error-handling/SKILL.md
GitHubBest Practices
- Check every error — NEVER discard with
_. - Wrap with context — Use
fmt.Errorf("context: %w", err)to add meaning while preserving the error type. - Lowercase messages — Error strings should be lowercase and not end with punctuation (e.g.,
user not found). - Use
errors.Isanderrors.As— Avoid direct comparison or type assertions. - Log OR Return — Never do both. Logging then returning leads to duplicate logs and confusion.
- Low cardinality — Keep error strings static; put variable data (IDs, paths) in structured log fields or custom error fields.
- Sentinel errors — Use
var ErrNotFound = errors.New("not found")for expected conditions. - Custom error types — Use when you need to carry structured data back to the caller.
Inspection Patterns
errors.Is (Sentinel checks)
if errors.Is(err, sql.ErrNoRows) {
return nil, ErrUserNotFound
}
errors.As (Type checks)
var pathErr *os.PathError
if errors.As(err, &pathErr) {
fmt.Println("Failed at path:", pathErr.Path)
}
errors.Join (Multiple errors)
err1 := validateName(name)
err2 := validateEmail(email)
if err := errors.Join(err1, err2); err != nil {
return err
}
Structured Logging (slog)
Prefer log/slog for structured logs.
slog.Error("failed to process user",
"user_id", userID,
"error", err,
)
Anti-patterns
- ❌ Swallowing errors by ignoring the return value.
- ❌ Log and Return:
log.Println(err); return err(results in noisy logs). - ❌ Capitalized error messages:
errors.New("Failed to Save"). - ❌ Direct comparison:
if err == sql.ErrNoRows(breaks if error is wrapped). - ❌ Using
panicfor control flow or expected errors.