Go中error是普通接口,需显式实现Error()方法;返回error时须早检查早返回,避免nil掩盖错误;%w用于错误包装以支持errors.Is/As,自定义错误字段须导出且勿实现String()。

Go里error是个接口,不是特殊类型
Go语言中error只是一个内建的接口类型,定义极其简单:type error interface { Error() string }。它不带任何魔法,也不隐式转换——你返回的必须是实现了这个方法的类型,否则编译直接报错cannot use ... as error value in return statement。
常见误区是以为error像Java的Exception一样有继承体系或运行时行为。实际上它只是个契约:只要提供Error()方法,就能当错误用。
-
标准库的
errors.New("msg")返回的是一个私有结构体,内部存字符串,Error()直接返回它 -
fmt.Errorf("failed: %w", err)返回的是*fmt.wrapError,支持%w嵌套和errors.Is()/errors.As()检查 - 自定义错误类型只需实现
Error() string,建议额外加字段(如Code int、Timestamp time.Time),但别忘了同时暴露这些字段供上层判断
返回error时别漏掉nil检查和提前return
函数签名里写了func Do() (int, error),不代表调用方会主动判空;更常见的是开发者在逻辑分支里忘了return,导致error变量未初始化却一路跑到函数末尾,返回零值nil——而调用方可能正等着这个nil来判断成功,结果掩盖了真实失败。
典型出错写法:
立即学习“go语言免费学习笔记(深入)”;
func parseConfig(path string) (map[string]string, error) {
data, err := os.ReadFile(path)
if err != nil {
log.Printf("read failed: %v", err)
// 忘了 return nil, err ← 这里就出事了
}
return parse(data), nil // data可能是nil,parse panic,且err被忽略
}
正确做法是早返回、显式控制流:
- 每个
if err != nil分支后紧跟return,不要靠缩进“记得后面处理” - 避免在
if块里只记录日志却不返回,日志≠错误处理 - 如果确实需要继续执行(比如收集多个错误),那就别用
error返回,改用自定义错误切片或multierr包
区分errors.New和fmt.Errorf,尤其注意%w的使用时机
errors.New适合静态、无上下文的错误(如errors.New("invalid input"));fmt.Errorf适合拼接动态信息或包装底层错误。关键区别在于是否需要保留原始错误链。
用%w表示“包裹”,意味着你希望调用方能通过errors.Unwrap()或errors.Is(err, target)追溯到原始错误;不用%w(比如用%s)就切断了链路,只剩字符串描述。
- 数据库查询失败后加一层业务语义:
fmt.Errorf("fetch user %d: %w", id, dbErr)→ 可用errors.Is(err, sql.ErrNoRows)判断 - 仅做日志透传或用户提示:
fmt.Errorf("failed to fetch user: %s", dbErr.Error())→ 原始错误丢失,无法程序化判断 -
%w只能出现在最后一个参数位置,且只能包裹一个error;想包多个?得自己实现或用multierr.Combine()
自定义error类型要小心fmt.Sprint等默认格式化行为
如果你写了type MyError struct { Msg string; Code int }并实现了Error() string,那它在fmt.Println(err)里会按预期输出字符串。但一旦你额外实现了String() string,fmt包会优先调用String()而非Error()——这会导致errors.Is()失效,因为比较的是String()结果,而不是错误本身。
更隐蔽的问题是:某些中间件或日志库(如zap)在结构化打印时会反射读取字段,若你把Code设为小写首字母(code int),外部就不可见,errors.As()也无法赋值成功。
- 自定义错误字段必须导出(大写首字母),否则
errors.As()无法填充 - 不要实现
String()方法,除非你明确知道它和Error()的调用关系 - 如果需要调试时显示更多信息,可在
Error()里拼接,例如return fmt.Sprintf("MyError[code=%d]: %s", e.Code, e.Msg)
errors.Is的判断逻辑,到HTTP handler里switch errors.Cause(err).(type)的写法,再到gRPC状态码映射,都依赖错误是否被正确定义和包装。随便fmt.Errorf("xxx: %v", err)一塞,后面排查成本翻倍。










