Go Error Handling Practices in ZuanZuan Operations: Usage and Management
This article explains how ZuanZuan's operations team applies Go's error handling mechanisms—including error values, panic/recover, sentinel errors, and custom error types—along with practical guidelines and code examples to improve reliability and maintainability of production services.
In production projects, Go's error values provide a clear way to locate issues, but unlike Java's try...catch or Python's try...except , Go relies on multiple return values and propagates error upward through function calls. This article shares the ZuanZuan operations team's practices for using and handling errors in Go.
Error vs Exception
Advantages of using errors instead of exceptions
Simple and controllable; handling each function's possible error avoids semantic confusion of exception variables.
Focuses on failure rather than success, emphasizing error handling.
Maintains linear program flow without hidden control flow.
Drawbacks of using errors
Every function that returns an error must handle it, leading to verbose code.
Propagating error upward can cause duplicate handling and noisy logs.
Misuse of panic or ignoring recover reduces program robustness.
panic and recover
In Go, a "panic" is the language's notion of an exception. A panic terminates the program unless recovered. Panics are typically thrown in two scenarios: unexpected library or data structure failures, and explicit developer‑initiated panics during initialization.
panic usage scenarios
Panics should rarely be thrown manually; they are appropriate during critical initialization failures (e.g., missing log configuration, failed database connection) where the program cannot continue.
Another scenario is configuration validation: if a config value is obviously invalid (e.g., an absurd port number), a panic can prevent obscure runtime errors.
In summary, panics are reserved for unrecoverable errors that occur outside normal program flow.
recover usage scenarios
The recover keyword catches a panic, acting as the final safeguard. Typical uses include:
Recovering from panics in the main HTTP server to avoid total process termination and allow a supervisor (systemd, container runtime) to restart the service.
Recovering within worker goroutines so that a panic in one worker does not crash the entire application; the goroutine can be restarted.
Generally, recover is applied in both the main goroutine and auxiliary goroutines to quickly restore crashed execution.
Custom Error
Sentinel Error
Sentinel errors are predefined variables (e.g., io.EOF ) that callers compare with == . While simple, they are inflexible because they prevent attaching additional context and can create tight coupling between packages.
var ErrShortWrite = errors.New("short write")
var errInvalidWrite = errors.New("invalid write result")
var ErrShortBuffer = errors.New("short buffer")
var EOF = errors.New("EOF")
var ErrUnexpectedEOF = errors.New("unexpected EOF")Because sentinel errors require equality checks, they are less suitable when richer error information is needed.
Hiding Internal Details
The standard error interface is simple:
type error interface {
Error() string
}Custom error types can implement additional methods, such as the net.Error interface with Timeout() and Temporary() methods:
package net
type Error interface {
error
Timeout() bool // is the error a timeout?
Temporary() bool // is the error transient and retry‑able?
}Example of checking a temporary error:
if nerr, ok := err.(net.Error); ok && nerr.Temporary() {
time.Sleep(1e9)
continue
}
if err != nil {
log.Fatal(err)
}A helper to detect temporary errors:
type temporary interface {
Temporary() bool
}
func IsTemporary(err error) bool {
temp, ok := err.(temporary)
return ok && temp.Temporary()
}Error Handling Principles
Principle 1: Keep the normal code path linear
Check errors early ( if err != nil { … } ) and handle them immediately, keeping the main flow free of nested indentation.
// Recommended style
f, err := os.Open(path)
if err != nil {
// handle error
}
// normal workflowPrinciple 2: Do not ignore errors, and avoid handling the same error multiple times
Every error that can affect service stability should be handled exactly once; logging counts as handling.
Principle 3: Wrap errors to provide richer context
Using packages like github.com/pkg/errors , wrap errors with errors.Wrap to retain stack traces and context, and retrieve the root cause with errors.Cause . Log the error only once.
Principle 4: Once an error is handled, it is no longer an error
After processing, the error should not be returned or reported again.
Summary
Go's error design follows two core ideas:
Errors Are Values : any type implementing Error() can be used as an error, enabling flexible definitions.
Handle All Potential Errors : Go encourages explicit error handling rather than hidden exceptions, improving program robustness.
The ZuanZuan operations team distilled these principles into practical guidelines for most error scenarios, acknowledging that no single approach fits all cases and that trade‑offs must be considered based on business needs.
About the Author
Lü Rui, ZuanZuan Operations Engineer, responsible for cloud computing and cloud‑native operations and development.
Zhuanzhuan Tech
A platform for Zhuanzhuan R&D and industry peers to learn and exchange technology, regularly sharing frontline experience and cutting‑edge topics. We welcome practical discussions and sharing; contact waterystone with any questions.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.