How we centralized and structured error handling in Golang
Centralizing and structuring error handling in Go has prompted sharp debate over whether it fights the language or fixes a real pain point. Many engineers argue that trying to encode all domain-specific errors in a single “god” package or map them rigidly to HTTP status codes is brittle, hard to scale, and unidiomatic compared to Go’s simple `error` values plus context wrapping. Others counter that large systems need more structured, typed errors for logging, monitoring, and API behavior, and see these patterns as workarounds for Go’s lack of sum types and richer, compiler-enforced error semantics.
Centralized error package proposal
- Many see the article’s “god error package” as over-centralizing domain knowledge and tightly coupling services.
- Critics argue errors are part of each service’s API; only very low-level, cross-cutting concerns (HTTP, protocols) should be global.
- Some feel it effectively reimplements an error “language” or exceptions inside Go, adding complexity and indirection.
- A few defend centralization for shared schemas/standards, but think the article’s concrete design is too heavy.
Go’s error model vs alternatives
- Strong sentiment that Go’s
(T, error)style is clumsy and encourages boilerplate, especially for composing multiple calls. - Calls for sum types /
Result-like types and a?-style operator; Rust is often cited as a better realization of “errors as values.” - Others defend Go’s simplicity and explicitness, preferring visible error handling over “magic” monadic or exception-based flows.
- Debate over exceptions: some see them as better for default bubbling; others argue they’re harder to reason about, especially without checked exceptions or effect types.
HTTP status codes vs application errors
- Strong pushback against conflating internal errors with HTTP status codes or centrally mapping all errors to HTTP in a low level.
- Many argue HTTP should reflect transport/request handling (200/400/401/403/404/500 etc.), while application semantics live in the body.
- Some advocate extreme minimalism (always 200, errors in body); others highlight loss of monitoring, tooling, and infrastructure benefits.
- Consensus trend: use a small, pragmatic subset of HTTP codes plus structured JSON error payloads.
Error context, structure, and logging
- Broad agreement that plain strings without context are inadequate in large systems.
- Techniques mentioned: wrapping with
%w, sentinel/custom error types, attaching structured metadata (key–value pairs), and stack traces via logging libraries. - Some stress adding context mainly at subsystem boundaries rather than every helper function.
Fail-fast vs graceful degradation
- One camp: treat violated invariants as bugs, assert/fail fast, and avoid running in an unknown state.
- Opposing view: crashing entire processes (especially servers) for a single bad request is unacceptable; isolate failures per request/goroutine and recover.
- General recognition that distinguishing “bug” vs “user/input/environment error” is crucial to choosing between aborting, retrying, or degrading gracefully.
Idiomatic Go vs “imported patterns”
- Multiple comments warn against “writing Java/Scala/Rust in Go” with heavyweight frameworks and non-idiomatic abstractions.
- Others counter that Go’s minimalism sometimes forces people to reinvent missing features in ad hoc, inconsistent ways.