应显式用 errors.Is 或 errors.As 分流处理已知错误,而非空分支忽略;优先 errors.Is(err, os.ErrNotExist) 而非 os.IsNotExist(err);Close 时仅忽略 os.ErrClosed 等无害错误;需提取字段时用 errors.As 安全解包。

Go 中如何跳过已知错误不做 panic 或日志
直接忽略错误是危险的,但有些错误确实是预期中的(比如 os.IsNotExist(err)),这时不该让程序中断或刷屏日志。关键不是“忽略”,而是“有意识地分流处理”。
- 永远别用
err := something(); if err != nil { /* 空块 */ }—— 这属于隐藏错误,静态检查工具(如errcheck)会报错,且后期排查成本极高 - 真正该做的是:显式判断错误类型/值,匹配后走空分支或默认逻辑,其余错误仍要处理
- 常见场景:检查文件是否存在、读取配置时容忍缺失字段、HTTP 客户端收到 404 但业务上可接受
用 os.IsNotExist 和 errors.Is 判断已知错误类型
os.IsNotExist 是最常被误用的函数之一:它只对 os.PathError 等少数底层错误有效;而 Go 1.13+ 推荐用 errors.Is 做语义化比对,兼容包装错误(fmt.Errorf("failed: %w", err))。
-
errors.Is(err, os.ErrNotExist)比os.IsNotExist(err)更健壮,尤其当错误被多层%w包装过 - 自定义错误值(如
var ErrNotFound = errors.New("not found"))必须用errors.Is(err, ErrNotFound),不能用==,因为错误可能被包装 - 避免用
strings.Contains(err.Error(), "no such file")—— 依赖字符串易碎,且不支持国际化或错误包装
在 defer / Close 场景中安全忽略特定 close 错误
调用 Close() 时,如果资源已关闭或底层连接断开,常返回 io.ErrClosed 或类似错误。这类错误通常无需上报,但也不能直接丢弃。
- 标准做法是:仅忽略已知的“无害”错误,例如
errors.Is(err, os.ErrClosed)、errors.Is(err, net.ErrClosed) - 不要忽略所有
Close()错误 —— 比如写入缓冲区失败导致的io.ErrShortWrite可能意味着数据丢失 - 示例:
defer func() { if cerr := f.Close(); cerr != nil && !errors.Is(cerr, os.ErrClosed) { log.Printf("close failed: %v", cerr) } }()
用 errors.As 提取底层错误做细粒度判断
有些错误需要进一步提取结构体字段才能判断是否可忽略,比如 PostgreSQL 驱动返回的 *pq.Error,其 Code 字段决定是否是唯一键冲突(23505)这类可接受错误。
立即学习“go语言免费学习笔记(深入)”;
-
errors.As(err, &pqErr)能安全解包,比类型断言err.(*pq.Error)更可靠(后者在错误被包装时会失败) - 仅当确认错误类型稳定、文档明确支持时才用
errors.As;否则优先用errors.Is匹配预定义错误值 - 注意:多次调用
errors.As不会改变原错误,但需确保目标变量地址有效(比如传&pqErr而非pqErr)
实际写过滤逻辑时,最容易被绕过的点是:忘了错误可能被中间件、日志库或 SDK 自动包装一层。只认原始错误值或字符串,大概率漏判。多一层 errors.Is 或 errors.As 就多一分确定性。










