Go文件读写错误需用fmt.Errorf+ %w封装以保留错误链,补充路径等结构化上下文;自定义错误类型须实现Unwrap;Close错误应用errors.Join合并;判断文件不存在必须用errors.Is(err, os.ErrNotExist)。

Go 文件读写错误本身不带路径、操作类型、时间戳等上下文,直接返回 os.Open 或 os.ReadFile 的原始错误,排查时只能看到 “no such file or directory”,根本不知道是哪个配置文件、在哪个函数里、用什么模式打开失败。必须封装。
用 fmt.Errorf + %w 包装并补充关键上下文
包装不是为了“美化错误”,而是让错误能被中层逻辑识别、被顶层统一处理。只加一句话描述远远不够,要包含可结构化提取的字段。
- 必须用
%w,否则errors.Is(err, os.ErrNotExist)会失效 - 推荐格式:
fmt.Errorf("open config file %q: %w", path, err)—— 路径用 %q 包裹,避免空格或特殊字符干扰日志解析 - 操作动词要准确:用
open/read/write/chmod,别全写成failed to process - 避免重复包装:同一错误在单个函数出口只包装一次;跨模块调用时再包一层语义升级(如 “load user config” → “init service”)
自定义错误类型携带结构化字段
当需要区分错误是否可重试、是否需告警、是否关联特定字段时,字符串包装就不够了。此时应定义结构体错误类型,并嵌入 error 字段。
- 字段至少包括:
Op(操作名,如"ReadFile")、Path(文件路径)、Err(底层 error) - 必须实现
Unwrap() error方法,返回e.Err,否则errors.As和errors.Is提取失败 - 不要导出敏感字段(如完整
path在生产日志中可能含用户 ID),可用RedactedPath()方法脱敏 - 示例:
type FileError struct { Op, Path string; Err error },并在Error()中组合输出
在 defer 中检查 Close 错误但不覆盖主错误
文件读写后 defer file.Close() 是常规操作,但 Close() 也可能失败(如磁盘满、网络文件系统断连)。这个错误不能丢,也不能简单覆盖前面的读写错误。
初始化并管理阿里云SDK Go 客户端,涵盖 sync.Once 单例、goroutine 安全、endpoint 与 region 配置、VPC 端点及调试。
立即学习“go语言免费学习笔记(深入)”;
- 正确做法:先保存主错误,
Close()失败时用errors.Join合并,而非fmt.Errorf覆盖 - 例如:
if closeErr := file.Close(); closeErr != nil && err == nil { err = closeErr }是错的——它丢了主错误;应改用err = errors.Join(err, closeErr) -
errors.Join返回的错误支持后续errors.Is遍历每个子错误,适合批量文件操作场景 - 注意:
os.File.Close()在已关闭文件上调用会返回nil,无需额外判空
忽略 os.ErrNotExist 但保留其他错误类型
很多场景下,文件不存在是合法路径分支(如加载可选配置),不是异常。但直接用 strings.Contains(err.Error(), "no such file") 极其危险——易误判、不兼容包装、无法跨平台。
- 必须用
errors.Is(err, os.ErrNotExist)判断,它能穿透任意层%w包装 - 封装一个辅助函数如
ignoreNotFound(err error) error,内部只对os.ErrNotExist返回nil,其余原样返回 - 切勿在包装时把
os.ErrNotExist转成新错误(如fmt.Errorf("config missing: %w", err)),这会让上层无法用errors.Is识别 - 如果业务上“文件不存在”需记录日志但不中断流程,应在判断后显式
logger.Debug("config not found, using defaults", zap.String("path", path)),而不是靠错误字符串匹配
最常被忽略的一点:错误封装后,所有中间层都不要再用 err.Error() 做条件判断或日志拼接——那等于主动放弃 Go 错误链的能力。真正要用的,永远是 errors.Is、errors.As 和 errors.Unwrap。

















