Skip to main content

error-handling

Error handling patterns for Go 1.20+. Covers errors.Is, errors.As, errors.Join, sentinel errors, error wrapping, multi-error, slog error logging, defer cleanup, error types vs sentinels, performance traps. Use when this capability is needed.

Aller à l'installation

Informations de source

Dépôt
tomevault-io/skills-registry
Dernière activité de la source
28 avril 2026 à 22:53
Langue détectée de SKILL.md
anglais
Étoiles
0
Forks
0

Options d'installation

Le prompt qui vérifie d'abord la source est sélectionné par défaut. Vous pouvez passer à une commande directe ou télécharger une copie locale.

Vérifiez les fichiers source

Lisez SKILL.md et les fichiers associés affichés par SkillsMP avant de décider de l'installer.

Explorateur de fichiers
2 fichiers

Affichage de SKILL.md

SKILL.md
Instructions source · Aperçu en lecture seule
name
error-handling
description
Error handling patterns for Go 1.20+. Covers errors.Is, errors.As, errors.Join, sentinel errors, error wrapping, multi-error, slog error logging, defer cleanup, error types vs sentinels, performance traps. Use when this capability is needed.
metadata
{"author":"air-gapped"}
# Error Handling ## Go 1.20+: Multi-Error Support ### errors.Join (Go 1.20+) Combine multiple errors without losing any: ```go // Collecting errors from goroutines var errs []error for result := range results { if result.err != nil { errs = append(errs, result.err) } } return errors.Join(errs...) // Returns nil if all nil ``` ### Multiple %w in fmt.Errorf (Go 1.20+) Wrap multiple errors with context: ```go err := fmt.Errorf("operation failed: %w and %w", err1, err2) ``` Both `errors.Is(err, err1)` and `errors.Is(err, err2)` return true. ### Critical: Unwrap Behavior Change ```go // Old pattern - BROKEN with multi-errors wrapped := errors.Unwrap(joinedErr) // Returns nil! // New pattern - works with multi-errors if errors.Is(joinedErr, targetErr) { ... } // Works if errors.As(joinedErr, &typedErr) { ... } // Works ``` `errors.Unwrap()` returns nil for multi-wrapped errors. Always use `errors.Is`/`errors.As`. Sources: [Go 1.20 Multi-Error Wrapping](https://lukas.zapletalovi.com/posts/2022/wrapping-multiple-errors/), [errors package](https://pkg.go.dev/errors) --- ## Performance: Sentinel Errors Are Slow Benchmarks from [DoltHub](https://www.dolthub.com/blog/2024-05-31-benchmarking-go-error-handling/): | Pattern | ns/op | vs baseline | |---------|-------|-------------| | Boolean return | 3.4 | 1x | | Direct `==` check | 7.4 | 2x | | `errors.Is()` | 19.4 | **6x** | | Wrapped sentinel through stack | 1,374 | **117x** | ### When Performance Matters **Don't use sentinel errors for expected conditions:** ```go // SLOW - sentinel for "not found" var ErrNotFound = errors.New("not found") func Get(key string) (Value, error) { if !exists { return Value{}, ErrNotFound // Allocates, slow } } // FAST - boolean for expected condition func Get(key string) (Value, bool) { if !exists { return Value{}, false // No allocation } } ``` **Reserve errors for unexpected failures.** Expected outcomes (not found, empty, EOF) should use booleans or special values. ### If You Must Use Sentinel Errors Check nil first to avoid `errors.Is` overhead: ```go if err != nil { if errors.Is(err, ErrSpecific) { // handle } } ``` --- ## Project Patterns ### Error Message Format Use lowercase, action-oriented prefixes: ```go // Pattern: "action: description" return fmt.Errorf("upstream fetch: %w", err) return fmt.Errorf("render markdown: %w", err) return fmt.Errorf("parse upstream url: %w", err) return fmt.Errorf("cache store: %w", err) return fmt.Errorf("read response body: size exceeds %d bytes", maxSize) ``` When wrapping, preserve the context chain: ```go if err := fetchUpstream(url); err != nil { return fmt.Errorf("render %s: %w", url, err) // Chains: "render https://...: upstream fetch: connection refused" } ``` ### slog Error Logging Always use `"error"` as the key, error as value: ```go slog.Error("upstream fetch failed", "error", err, "upstream", url) slog.Warn("cache eviction", "error", err, "key", cacheKey) ``` Never stringify the error: ```go // WRONG slog.Error("failed", "error", err.Error()) // RIGHT slog.Error("failed", "error", err) ``` ### HTTP Error Responses cooked returns styled HTML error pages: ```go func renderError(w http.ResponseWriter, status int, errType, message string) { w.Header().Set("Content-Type", "text/html; charset=utf-8") w.WriteHeader(status) // Execute error template } ``` ### Type Assertions with errors.As For stdlib error types: ```go var maxBytesErr *http.MaxBytesError if errors.As(err, &maxBytesErr) { // Request body too large } ``` --- ## Decision Tree ### Returning Errors ``` Is the condition expected (not found, empty, no match)? YES → Return (value, bool) or special value, not error NO → Continue Need to add context? YES → fmt.Errorf("context: %w", err) NO → return err Multiple errors to combine? YES → errors.Join(errs...) or fmt.Errorf("%w and %w", e1, e2) ``` ### Checking Errors ``` Need to check specific error type? YES → errors.As(err, &typed) Need to check sentinel error? YES → if err != nil && errors.Is(err, sentinel) Just need to know if error occurred? YES → if err != nil ``` ### Logging vs Returning ``` Can the caller handle this error? YES → Return it, don't log NO → Log it, then either: - Return a wrapped/sanitized error - Handle and continue At system boundary (HTTP handler, main)? YES → Log with full context, return user-safe message ``` --- ## Error Types vs Sentinels | Use | When | |-----|------| | **Sentinel** (`var ErrX = errors.New(...)`) | Well-known conditions: `io.EOF`, `context.Canceled` | | **Error type** (`type MyError struct{...}`) | Error carries structured data callers need | | **Wrapped sentinel** (`fmt.Errorf("...: %w", ErrX)`) | Need both context and identifiable cause | | **Plain error** (`fmt.Errorf("...")`) | Caller doesn't need to distinguish error types | Default to plain errors. Only add sentinels/types when callers need to handle specifically. --- ## Defer Cleanup Pattern Handle errors from deferred cleanup: ```go func DoWork() (err error) { // Named return f, err := os.Open(path) if err != nil { return err } defer func() { if cerr := f.Close(); cerr != nil && err == nil { err = cerr // Only set if no prior error } }() // ... work with f return nil } ``` --- > Converted and distributed by [TomeVault](https://tomevault.io/claim/air-gapped) — claim your Tome and manage your conversions. <!-- tomevault:4.0:skill_md:2026-04-13 -->
Voir sur GitHub