Go通过error值显式处理错误,链式处理需保留原始上下文;Go1.13起用%w包装、errors.Is/As/Unwrap检查;自定义错误需实现Unwrap();避免重复包装、忽略原始错误或滥用panic。

Go 语言本身不支持异常机制,而是通过返回 error 值显式处理错误。链式错误处理的核心在于:**在不丢失原始错误上下文的前提下,逐层添加上下文信息,并支持最终展开、检查和日志记录**。从 Go 1.13 开始,标准库提供了 errors.Is、errors.As 和 fmt.Errorf 的 %w 动词,让链式包装变得简洁可靠。
使用 %w 包装错误(推荐方式)
用 fmt.Errorf("msg: %w", err) 可以将底层错误“包裹”进新错误中,形成错误链。被包裹的错误可通过 errors.Unwrap 获取,且支持递归展开。
- 只允许一个直接包装目标(
%w只能出现一次) - 被包装的错误必须是
error类型,不能是nil - 若需多层包装,逐层调用
fmt.Errorf即可
示例:
func readFile(path string) error {
data, err := os.ReadFile(path)
if err != nil {
return fmt.Errorf("failed to read config file %q: %w", path, err)
}
return parseConfig(data)
}
func parseConfig(data []byte) error {
if len(data) == 0 {
return fmt.Errorf("empty config data: %w", errors.New("no content"))
}
return nil
}
检查和提取链中特定错误
不要用字符串匹配或类型断言原始错误,而应使用标准库提供的语义化检查工具:
-
errors.Is(err, target):判断错误链中是否存在某个已知错误(如os.ErrNotExist) -
errors.As(err, &target):尝试将错误链中任意一层的错误赋值给指定类型的变量(用于自定义错误) -
errors.Unwrap(err):获取直接被包装的错误(仅一层),返回nil表示无包装
示例(捕获并分类处理):
if errors.Is(err, os.ErrNotExist) {
log.Println("config file missing, using defaults")
return loadDefaults()
}
if var e *json.SyntaxError; errors.As(err, &e) {
log.Printf("JSON syntax error at offset %d", e.Offset)
}
自定义错误类型并支持包装
若需携带额外字段(如追踪 ID、时间戳、HTTP 状态码),可实现 Unwrap() error 方法,使其兼容标准链式操作。
示例:
type AppError struct {
Msg string
Code int
Err error // 底层错误
}
func (e *AppError) Error() string {
return fmt.Sprintf("[%d] %s: %v", e.Code, e.Msg, e.Err)
}
func (e *AppError) Unwrap() error { return e.Err }
// 使用
return &AppError{Msg: "processing failed", Code: 500, Err: io.ErrUnexpectedEOF}
这样它就能被 errors.Is、errors.As 正确识别和解包。
避免常见陷阱
-
不要重复包装同一错误:比如
fmt.Errorf("x: %w", fmt.Errorf("y: %w", err))会冗余增加层级,优先合并上下文 -
不要在包装时忽略原始错误:避免写成
fmt.Errorf("failed: %v", err)—— 这会丢失可检查性 - 不要在中间层 panic 或 recover 来模拟异常:违背 Go 错误处理哲学,难以调试和测试
-
日志时慎用
%+v:第三方库如github.com/pkg/errors提供堆栈,但标准库不带堆栈;如需堆栈,建议用debug.PrintStack()或专用日志库










