How would you design an error model for a large service, for example with Op, Kind and a wrapped cause?
Question 324HardGo 1.22 to 1.25
A common senior-level design, inspired by Upspin and many internal Google and Uber codebases, is a single structured error type. It carries a machine-readable Kind (used for control flow and protocol mapping), an Op (where the failure happened), and the wrapped cause (detail for debugging):
type Kind uint8
const (
KindOther Kind = iota
KindNotFound
KindInvalid
KindPermission
KindConflict
)
type Error struct {
Op string // "userSvc.Update"
Kind Kind
Err error
}
func (e *Error) Error() string { return e.Op + ": " + e.Err.Error() }
func (e *Error) Unwrap() error { return e.Err }
func E(op string, k Kind, err error) error { return &Error{Op: op, Kind: k, Err: err} }
// KindOf returns the outermost non-Other kind in the chain.
func KindOf(err error) Kind {
var e *Error
for errors.As(err, &e) {
if e.Kind != KindOther {
return e.Kind
}
err = e.Err
}
return KindOther
}
func httpStatus(err error) int {
switch KindOf(err) {
case KindNotFound:
return http.StatusNotFound
case KindInvalid:
return http.StatusBadRequest
case KindPermission:
return http.StatusForbidden
case KindConflict:
return http.StatusConflict
}
return http.StatusInternalServerError
}
What to point out: kinds are a closed set that maps onto HTTP and gRPC codes in one place. User-safe messages stay separate from internal detail. The model works with errors.Is/As. Logging happens once, at the edge. Avoid putting request-specific values into sentinel messages.
More on Error Handling & panics
- Q322What is "asserting errors for behavior"? How would you implement retry logic based on error behavior?
- Q323How does
net/httphandle a panic in a handler? What are the pitfalls? - Q325What is the
io.Readererror contract, and why isif err != nil { return }wrong there? - Q326Implement a wrapper error type that adds context and works with
errors.Is/As. What happens if you forgetUnwrap? - Q327What does this print? (Which side's
Ismethod gets called) - Q328What happens when a deferred function panics while the goroutine is already panicking?