Go自定义错误类型旨在区分错误种类并携带上下文字段,应使用导出字段的struct实现error接口,配合errors.Is/As进行健壮判断,避免敏感数据和大对象,确保调用链中类型穿透一致。

Go 里自定义错误类型不是为了“看起来更专业”,而是为了解决两个实际问题:能区分错误种类(比如 IsTimeoutErr 还是 IsAuthFailed),以及能携带上下文字段(比如失败的 UserID、重试次数 RetryCount)。
用 struct 实现 error 接口是最常用且可控的方式
Go 的 error 就是个接口:type error interface { Error() string }。只要实现这个方法,就是合法错误。用 struct 而非 string 或 fmt.Errorf 包装,是为了可扩展性。
常见错误写法是直接返回 fmt.Errorf("user %d not found", id) —— 这类错误无法被程序逻辑识别,只能靠字符串匹配,脆弱且不可靠。
正确做法是定义一个结构体:
立即学习“go语言免费学习笔记(深入)”;
type UserNotFoundError struct {
UserID int
Source string // 可选:标记来自 DB 还是 Cache
}
func (e *UserNotFoundError) Error() string {
return fmt.Sprintf("user %d not found in %s", e.UserID, e.Source)
}
这样调用方就能用类型断言精准识别:
if err != nil {
if _, ok := err.(*UserNotFoundError); ok {
// 触发降级逻辑,不记录告警
return fallbackData()
}
// 其他错误走通用处理
}
- 务必导出结构体字段(首字母大写),否则外部包无法访问
UserID等上下文 - 不要在
Error()方法里做耗时操作(如日志、网络请求),它可能被多次调用 - 如果错误不需要额外字段,用
errors.New("xxx")更轻量,不必强上 struct
用 errors.Is 和 errors.As 替代类型断言
Go 1.13 引入了 errors.Is 和 errors.As,它们能穿透多层包装错误(比如被 fmt.Errorf("wrap: %w", err) 包过),比裸类型断言更健壮。
使用模板与程序分离的方式构建,依靠专门设计的数据库操作类实现数据库存取,具有专有错误处理模块,通过 Email 实时报告数据库错误,除具有满足购物需要的全部功能外,成新商城购物系统还对购物系统体系做了丰富的扩展,全新设计的搜索功能,自定义成新商城购物系统代码功能代码已经全面优化,杜绝SQL注入漏洞前台测试用户名:admin密码:admin888后台管理员名:admin密码:admin888
前提是你的自定义错误也支持 Unwrap() 方法:
func (e *UserNotFoundError) Unwrap() error {
return nil // 表示它是最底层错误;若包装了其他 error,这里返回那个 error
}
然后就可以安全地跨层级判断:
err := doSomething() // 可能返回 fmt.Errorf("DB query failed: %w", &UserNotFoundError{UserID: 123})
var notFoundErr *UserNotFoundError
if errors.As(err, ¬FoundErr) {
log.Printf("user %d missing", notFoundErr.UserID)
}
- 只要任意一层错误是
*UserNotFoundError类型,errors.As就能捕获到 - 如果自定义错误内部包装了另一个 error(比如数据库驱动错误),就在
Unwrap()返回它,别返回nil -
errors.Is(err, someErr)适合判断是否等于某个预定义的哨兵错误(如io.EOF),不适合带字段的自定义类型
避免在错误中存敏感数据或大对象
错误值可能被长期持有、打印、序列化进日志甚至上报到监控系统。如果结构体里塞了 RawRequest []byte 或 UserPassword string,就构成泄露风险。
典型反例:
type BadRequestError struct {
RawBody []byte // ❌ 日志一打全暴露
User *User // ❌ 可能含密码字段
}
- 只保留诊断必需的最小字段:ID、状态码、时间戳、简短原因码(如
"invalid_token") - 需要调试原始数据?用独立的 debug 日志,而不是塞进 error 实例
- 结构体大小要小——错误频繁创建,大结构体会增加 GC 压力
真正难的不是定义一个 MyCustomError,而是在整个调用链里保持错误类型的穿透性和语义一致性。比如 HTTP handler 捕获到 *UserNotFoundError,不该转成 fmt.Errorf("internal error") 吞掉原类型;中间件加 wrap 时也要确保 Unwrap() 正确返回下层错误。这些细节漏掉一处,自定义错误就退化成普通字符串。









