Come usare
- Copia il comando e controlla il pacchetto prima di installarlo nel tuo ambiente agente.
- Controlla strumenti, credenziali e ambienti richiesti nel documento originale.
- Descrivi input e risultato desiderato; confronta l’output con le fonti.
npx skills add https://github.com/victorzhuk/skills --skill z-go-errorsPrima di iniziare
- L’installazione non include abbonamenti, quote API o accesso ai file.
- Stelle e installazioni indicano attività, non un test indipendente di qualità o sicurezza.
Contenuto dal repository con titoli, esempi, codice, tabelle, link e immagini preservati.
Go errors
Core rules
- Check every error — never discard with
_. - Wrap with context:
fmt.Errorf("create user: %w", err)— short prefix, lowercase, no period. - Translate errors at transport boundaries —
%vremoves wrapping but does not sanitize the message. Return stable public errors; retain internal causes for one safe log. - Inspect via `errors.Is` / `errors.As` — never
==on wrapped errors or bare type assertions. - Handle exactly once — log OR return, never both (see Single Handling below).
- Panic only for truly unrecoverable states — programmer errors, broken invariants; never for expected conditions.
Wrapping and inspection
// wrap
return fmt.Errorf("fetch user %s: %w", id, err)
// sentinel check — works through the chain
if errors.Is(err, ErrNotFound) { ... }
// typed check
var ve *ValidationError
if errors.As(err, &ve) { ... }
// combine independent errors (Go 1.20+)
return errors.Join(errA, errB)Sentinel vs custom type
| Use sentinel | Use custom type |
|---|---|
| Expected condition, no data needed | Need to carry fields (code, field, user message) |
| Leaf error, never needs unwrapping | Callers need to extract structured data |
Simple errors.Is check is enough | Need errors.As to extract structured data |
// sentinel — unexported, exported error value
var ErrNotFound = errors.New("not found")
// custom type — carries data
type ValidationError struct {
Field string
Message string
}
func (e *ValidationError) Error() string {
return fmt.Sprintf("validation %s: %s", e.Field, e.Message)
}Error var names: ErrFoo. Error type names: FooError. Error strings: lowercase, no trailing punctuation.
Single handling rule
An error must be logged OR returned, never both. Violating this creates duplicate log lines and buries the wrapped chain during incidents.
Interior code (repos, services, domain): wrap and return only.
// before — log-and-return violation
if err != nil {
logger.Error("query failed", "err", err)
return fmt.Errorf("query: %w", err)
}
// after
if err != nil {
return fmt.Errorf("query: %w", err)
}Boundary code (HTTP handler, gRPC interceptor, worker loop, main): log and absorb. Never log inside interior layers; the boundary does it once.
// boundary: worker loop
for job := range w.jobs {
if err := w.process(ctx, job); err != nil {
w.logger.Error("job failed", "job_id", job.ID, "err", err)
// handle: retry, mark failed, etc.
}
}False positives — do NOT fix
| Pattern | Why it's OK |
|---|---|
Log + return nil / continue | Error absorbed; logging IS the single handling |
| Log in goroutine that can't return | No caller to propagate to |
| Boundary handler logs + returns HTTP status | It IS the top-level handler |
Log + panic / os.Exit / log.Fatal | Terminal, not propagation |
Deferred cleanup (defer tx.Rollback()) | Can't return errors to caller |
| Metrics counter + return error | Metrics are aggregations, not per-event log noise |
Panic discipline
// Only for programmer errors / broken invariants
func mustPositive(n int) int {
if n <= 0 {
panic(fmt.Sprintf("mustPositive: got %d", n))
}
return n
}Do not recover an unexpected panic merely to keep a server or worker alive. A broken invariant can leave shared state unsafe even when the goroutine exits.
If an existing package uses an internal panic protocol, recover only its private panic type at the package boundary and re-panic every unexpected value. Define the returned error and resource cleanup. Any broader containment boundary must establish state isolation, define the failed operation's result, and preserve stack diagnostics; a goroutine boundary alone provides no such guarantee.
Quick reference
| Pattern | Rule |
|---|---|
| Wrap format | "create user: %w" — short, lowercase, no period |
| Chain exposure | wrap internally; translate to a safe public error at transport boundaries |
| Sentinel match | errors.Is(err, ErrFoo) |
| Typed extraction | errors.As(err, &target) |
| Join independent | errors.Join(errA, errB) |
| Single handling | log OR return; boundary logs, interior wraps |
| Panic scope | Programmer errors and broken invariants only |
| Recovery | Owned panic protocol or explicit containment; re-panic unexpected values |
| Error var name | ErrFoo |
| Error type name | FooError |
Do not
- Log AND return in the same error branch (interior code).
- Use
==to compare errors that may be wrapped. - Use bare type assertions on errors — use
errors.As. - Produce high-cardinality data in the stable log message; attach as structured attrs.
- Expose raw internal errors to external callers — translate at the boundary.
- Convert every panic to an error or log entry without a containment contract.
Verify
go build ./... && timeout 5m go test -parallel 4 -p 2 -short -timeout 2m ./...
golangci-lint run ./...Review any recovery path for its accepted panic values, unexpected-value propagation, cleanup, and explicit failure result.

