Skip to main content

golang-error-handling

Go error handling patterns: %w wrapping, errors.Is/As, sentinel errors, custom error types, panic prohibition rules, and error API design. Use when writing or reviewing error handling code.

Informations de source

Dépôt
bivex/go-quality-cli-agent
Dernière activité de la source
4 mars 2026 à 16:52
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.

Affichage de SKILL.md

SKILL.md
Instructions source · Aperçu en lecture seule
name
golang-error-handling
description
Go error handling patterns: %w wrapping, errors.Is/As, sentinel errors, custom error types, panic prohibition rules, and error API design. Use when writing or reviewing error handling code.
allowed-tools
Bash(go vet *), Bash(golangci-lint *), Bash(grep *), Read, Grep
# Go Error Handling Patterns Go's explicit error handling is its greatest strength for reliability — when done correctly. This skill enforces idiomatic, chain-preserving, API-stable error handling. --- ## 1. The Golden Rules | Rule | Why | |------|-----| | Always use `%w` to wrap | Preserves chain for `errors.Is`/`errors.As` | | Never use `panic` in libraries/services | Caller cannot recover gracefully | | Use `errors.Is` not `==` for comparison | Works through wrap chain | | Use `errors.As` not type assertion | Works through wrap chain | | Export sentinel errors only when needed | Avoid tight coupling | --- ## 2. Error Wrapping ### ✅ Correct: `%w` preserves the chain ```go // Wrap with context — %w is key, not %v func loadUser(id string) (*User, error) { user, err := db.Find(id) if err != nil { return nil, fmt.Errorf("loadUser %s: %w", id, err) // %w wraps } return user, nil } // Caller can inspect the original error if err := loadUser("42"); err != nil { if errors.Is(err, sql.ErrNoRows) { // works even through layers of %w return ErrUserNotFound } return fmt.Errorf("handleRequest: %w", err) } ``` ### ❌ Wrong: `%v` breaks the chain ```go // %v converts to string — errors.Is cannot inspect it return nil, fmt.Errorf("loadUser: %v", err) // BREAKS chain — DON'T do this ``` ### Convention for wrapping messages ``` "operation name: %w" ✅ "operation name failed: %w" ✅ (common but redundant) "failed to do X: %w" ✅ // Chain reads bottom-up: // "handleHTTP: loadUser 42: db.Find: connection refused" ``` --- ## 3. Sentinel Errors Sentinel errors signal a specific, expected condition that callers should handle. ```go // ✅ Correct: package-level sentinel var ErrNotFound = errors.New("not found") var ErrPermissionDenied = errors.New("permission denied") // Caller pattern: if errors.Is(err, ErrNotFound) { http.Error(w, "user not found", http.StatusNotFound) return } ``` ### When to expose sentinels | Expose | Don't expose | |--------|-------------| | Caller needs to branch on it | Only logged and returned | | Different HTTP status per error | Internal implementation detail | | Part of documented public API | Transient/infrastructure errors | ```go // ✅ Unexported sentinel — internal use only var errRetryExceeded = errors.New("max retries exceeded") // ✅ Exported — callers need to handle it var ErrInvalidInput = errors.New("invalid input") ``` --- ## 4. Custom Error Types Use custom types when callers need structured data from the error. ```go // ✅ Typed error with fields type ValidationError struct { Field string Message string } func (e *ValidationError) Error() string { return fmt.Sprintf("validation: field %q: %s", e.Field, e.Message) } // Raise: return nil, &ValidationError{Field: "email", Message: "must contain @"} // Inspect with errors.As (works through wrap chain): var ve *ValidationError if errors.As(err, &ve) { http.Error(w, ve.Message, http.StatusUnprocessableEntity) } // ✅ Wrapping preserves As detection: return nil, fmt.Errorf("createUser: %w", &ValidationError{...}) ``` --- ## 5. errors.Is and errors.As ```go // errors.Is — check if error IS a specific value (through chain) if errors.Is(err, io.EOF) { ... } if errors.Is(err, ErrNotFound) { ... } // errors.As — extract typed error (through chain) var netErr *net.OpError if errors.As(err, &netErr) { fmt.Println(netErr.Op, netErr.Addr) } // NEVER use == for non-sentinel comparison if err == someErr { ... } // ❌ breaks if wrapped if errors.Is(err, someErr) { ... } // ✅ ``` --- ## 6. panic Rules ```go // ✅ ONLY acceptable uses of panic: // 1. Programmer invariant violations (impossible state): func mustPositive(n int) { if n <= 0 { panic(fmt.Sprintf("mustPositive: got %d", n)) } } // 2. Package init failures (program cannot start): func init() { tmpl = template.Must(template.ParseFiles("tmpl.html")) // panics on bad template } // 3. Test helpers (panic becomes test failure): func mustMarshal(t *testing.T, v any) []byte { t.Helper() b, err := json.Marshal(v) if err != nil { t.Fatalf("marshal: %v", err) } return b } // ❌ NEVER in library/service code: func GetUser(id string) *User { u, err := db.Find(id) if err != nil { panic(err) } // WRONG — caller cannot handle return u } ``` --- ## 7. Detection Commands ```bash # Find unhandled errors golangci-lint run --enable=errcheck --disable-all ./... # Find broken wrap chain (%v instead of %w) golangci-lint run --enable=errorlint --disable-all ./... # Find missing error wrapping (wrapcheck — optional, strict) golangci-lint run --enable=wrapcheck --disable-all ./... # Find bare panic calls in production code grep -rn "panic(" --include="*.go" . | grep -v "_test.go" | grep -v "//.*panic" # Find == comparison on errors (should use errors.Is) grep -rn "== err\b\|err ==" --include="*.go" . | grep -v "_test.go" # Find %v used to wrap errors (should be %w) grep -rn 'Errorf.*%v.*err' --include="*.go" . ``` --- ## 8. golangci-lint config for error handling ```yaml # In .golangci.yml linters: enable: - errcheck # unhandled errors - errorlint # incorrect wrapping patterns - wrapcheck # errors from external packages must be wrapped linters-settings: errcheck: check-type-assertions: true # x.(T) without ok check-blank: true # _ = f() wrapcheck: ignorePackageGlobs: - "github.com/myorg/myproject/*" # internal errors don't need wrapping ```
Voir sur GitHub