该用%w,不是%v,因为%w支持错误链、errors.is/as判断,而%v丢失原始类型和堆栈;%w必须置于格式串末尾且仅接一个error。

fmt.Errorf 里该用 %w 还是 %v?
用 %w,不是 %v,这是 Go 错误链能否工作的分水岭。用错就等于主动切断调试线索。
-
%w会把原始错误嵌入新错误中,支持errors.Is和errors.As判断——比如你能精准识别出是不是os.ErrNotExist,而不是靠字符串匹配 -
%v只是把错误转成字符串拼进去,原始类型和堆栈全丢,errors.Is(err, os.ErrNotExist)永远返回false -
%w必须放在格式化字符串的末尾,且只能接一个error类型值;写成fmt.Errorf("read %s: %w, retrying: %w", f, err1, err2)是非法语法
常见错误现象:日志里看到 “failed to save user: &{…}”,但上层无法用 errors.Is 判断类型,最终所有错误都 fallback 到 500 ——其实本该是 404。
自定义 error 结构体时,Error() 方法怎么写?
Error() 方法只负责返回面向调用方的可读提示,不是调试日志,也不是错误分类器。
- 必须小写开头、不加句号,例如
"invalid email format",不是"Invalid email format."或"Email validation failed: xxx" - 不能包含路径、密码、token、用户 ID 等敏感信息;调试用的字段(如
TraceID、SQL)要单独存,绝不塞进Error()返回值 - 如果需要包装底层错误,必须实现
Unwrap() error并返回它;漏掉Unwrap,%w包装和errors.Unwrap都会失效
示例中常看到 ValidationError 忘记 Unwrap(),导致 errors.As(err, &e) 总是失败,最后只能靠字符串判断,一换语言或翻译就崩。
立即学习“go语言免费学习笔记(深入)”;
HTTP 接口返回错误时,message 和 details 怎么分?
响应给前端的 message 和记录到日志里的上下文,必须物理隔离——它们服务的对象、用途、安全要求完全不同。
-
message是给用户的,要简短、中性、无技术细节,例如"资源未找到",不是"user_id=123 not found in pg.users (query took 82ms)" -
details字段只用于内部日志或调试接口,生产环境应为空或脱敏;它不该出现在 JSON 响应里,除非明确开启 debug 模式 - 状态码不能靠
strings.Contains(err.Error(), "not found")匹配——这种写法在加个前缀日志、换种错误包装后立刻失效
正确做法是让错误实现 StatusCode() int 接口,由统一响应函数读取,而不是每个 handler 自己 if-else 判字符串。
团队协作中,错误文案风格为什么必须统一?
不是为了好看,而是为了让 grep 能搜、监控能聚、新人能懂、CI 能校验。
- 动词开头(
"failed to connect")、小写、无句号,是硬约束——否则日志系统按正则聚合时,"Failed to connect"和"failed to connect."就算作两个错误类型 - 同一类错误在 CLI、HTTP、gRPC 中的提示语气可以不同,但核心动词和名词必须一致,比如都用
parse而不是有时用decode、有时用read - 禁止在
Error()里写 “请联系管理员” 或 “请重试”——那是业务响应逻辑,不是错误本身;错误只说“发生了什么”,不说“该怎么办”
最容易被忽略的一点:错误消息里混用中文和英文变量名(比如 "参数 name 无效"),会导致国际化时模板难维护、机器翻译出错、甚至 JSON 序列化失败。统一用英文占位符,靠 message key + i18n 模板解耦才是正路。










