Go错误需显式检查与精准包装:用%w保留错误链,errors.Is/As安全判断类型,避免字符串匹配;清理失败不可忽略,可用multierror聚合;自定义错误应含结构化字段并嵌入原始错误。

Go 的错误不是异常,而是必须显式检查的值;“优雅”不在于掩盖错误,而在于让错误可追溯、可分类、可恢复。
用 fmt.Errorf 包装错误,而不是丢弃或拼接字符串
很多新手会写 return errors.New("failed to open config: " + err.Error()),这破坏了错误链,导致 errors.Is 和 errors.As 失效。正确做法是用 %w 动词包装:
-
return fmt.Errorf("loading config failed: %w", err)—— 保留原始错误,支持后续errors.Unwrap和链式判断 - 避免多次包装同一错误(比如连续两层都用
%w),否则调用栈冗余,日志难以阅读 - 只在需要添加上下文时包装:比如从
os.Open到 “加载 TLS 证书失败”,而非“打开文件失败”这种无信息增量
用 errors.Is 和 errors.As 判断错误类型,别用字符串匹配或直接比较
写 if err.Error() == "file does not exist" 或 if err == os.ErrNotExist 是危险的——前者易被翻译/日志修饰干扰,后者在错误被包装后就失效。
-
errors.Is(err, os.ErrNotExist)能穿透任意层%w包装,安全可靠 -
errors.As(err, &parseErr)用于提取自定义错误(如*ParseError),比类型断言err.(*ParseError)更健壮 - 注意:
errors.As第二个参数必须是指针变量,传值会导致匹配失败
清理失败时,别吞掉错误:用 multierror 或手动组合
常见反模式:主操作失败后调 os.Remove 清理,却用 _ = os.Remove(...) 忽略其返回值。一旦清理也失败,调用方完全不知道资源残留。
立即学习“go语言免费学习笔记(深入)”;
- 简单场景:用
fmt.Errorf("write failed: %w; cleanup also failed: %v", writeErr, removeErr)合并两个错误信息 - 复杂流程(如多个 defer 清理点):引入
github.com/hashicorp/go-multierror,它能聚合多个错误并保持可判断性 - 关键原则:主错误优先,但次要错误不能静默消失;尤其在 CLI 工具或初始化函数中,残留状态比报错更危险
自定义错误类型时,带字段比带格式化字符串更实用
只实现 Error() 方法返回字符串,对调试帮助有限。真正有用的自定义错误应携带结构化字段:
- 例如:
type ValidationError struct { Field string; Code int; Err error },这样上层可读取e.Field做字段级重试,或根据e.Code映射 HTTP 状态码 - 务必嵌入原始
Err字段并用%w包装,否则丢失底层原因(比如 JSON 解析失败的具体位置) - 避免在
Error()中做耗时操作(如访问网络、读文件),它可能被日志系统高频调用
最容易被忽略的一点:错误处理不是越“完整”越好,而是越“精准”越好。一个函数该返回什么错误、该在哪儿包装、该暴露哪些字段,取决于它的调用方是谁、是否会被重试、是否影响状态一致性——脱离上下文谈“优雅”,只会让代码更难维护。










