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.

Zur Installation springen

Quellinformationen

Repository
tomevault-io/skills-registry
Letzte Quellaktivität
28. April 2026 um 22:53
Erkannte Sprache von SKILL.md
Englisch
Sterne
0
Forks
0

Installationsoptionen

Standardmäßig ist der Prompt ausgewählt, der zuerst die Quelle prüft. Sie können zu einem direkten Befehl wechseln oder eine lokale Kopie herunterladen.

Quelldateien prüfen

Lesen Sie SKILL.md und alle von SkillsMP angezeigten Begleitdateien, bevor Sie sich für eine Installation entscheiden.

Datei-Explorer
2 Dateien

SKILL.md wird angezeigt

SKILL.md
Quellanweisungen · Schreibgeschützte Vorschau
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 -->
Auf GitHub ansehen