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) —— 这会丢失可检查性%+v:第三方库如 github.com/pkg/errors 提供堆栈,但标准库不带堆栈;如需堆栈,建议用 debug.PrintStack() 或专用日志库