go 1.13+ 应用 errors.is 判断错误是否由底层错误引发,因其可穿透多层 %w 包装;%w 用于需保留错误链供检查的场景,%s 仅作上下文记录;errors.as 要求目标为指针且类型匹配,且依赖全程 %w 包装;日志应直接打印 err,由日志库自动展开错误链。

Go 1.13+ 如何判断错误是否由某个底层错误引发
用 errors.Is,不是 == 或 strings.Contains。Go 1.13 引入错误包装后,原始错误可能被多层包裹,直接比较会失效。
-
errors.Is(err, io.EOF)能穿透任意层数的fmt.Errorf("...: %w", io.EOF)找到最内层匹配 - 若用
err == io.EOF,仅当err就是那个io.EOF实例才成立,包装后必然失败 - 避免用
errors.Unwrap自己循环解包——errors.Is已内置该逻辑,且处理了循环包装等边界情况
什么时候必须用 %w 而不是 %s 包装错误
只有想保留原始错误链供后续 errors.Is 或 errors.As 检查时,才用 %w。它不是“更高级的格式化”,而是明确的错误所有权移交。
- 用
%w:需要下游能识别并处理底层错误类型(如重试网络超时、忽略文件不存在) - 用
%s:仅需记录上下文信息,不希望暴露或传递原始错误(如隐藏敏感路径、避免泄露内部实现) - 混用风险:
fmt.Errorf("db query failed: %w", err)中若err是nil,结果仍是nil;而%s会转成字符串"<nil>"</nil>,导致误判
errors.As 解包自定义错误类型失败的常见原因
失败通常不是因为语法写错,而是目标变量类型与被包装错误的实际类型不匹配,或未正确声明接口/指针接收者。
- 确保目标变量是指针:
var e *os.PathError,然后调errors.As(err, &e)—— 值类型无法被赋值 - 若错误来自第三方库,确认其导出的错误类型是否实现了你期望的接口;很多库只返回未导出结构体,此时
errors.As无效 -
errors.As只解一层包装?不对,它会递归查找,但前提是每层都用%w包装;某层用了%s,链就断了
日志中打印错误时,要不要先 errors.Unwrap
不要。直接传给日志库(如 log.printf("%+v", err)),现代日志器(zap、zerolog)和 %+v 格式符已自动展开整个错误链,包括堆栈和包装关系。
立即学习“go语言免费学习笔记(深入)”;
- 手动
errors.Unwrap只能得到最内层错误,丢失中间上下文和位置信息 - 若日志库不支持,优先升级或换用支持错误展开的库,而非自己拼接字符串
- 唯一例外:调试时临时加
fmt.Printf("unwrapped: %+vn", errors.Unwrap(err))快速定位根因
错误链不是越多越好,每层 %w 都增加一次内存分配和间接引用。真正需要跨层诊断的场景才包装,否则用 %s 更轻量。另外,errors.Is 和 errors.As 的性能开销虽小,但在高频循环里反复调用仍要留意——这时候往往该重构错误策略,而不是优化单次调用。