模块课程(2/5)
惯用的错误处理
Go 中的错误处理是明确的:没有异常,没有 try/catch。每个可能失败的函数都会返回 error 作为其最后一个值,由调用者决定。本章收集了以稳健的方式产生、传播和检查错误的惯用模式。
黄金法则
data, err := os.ReadFile(path)
if err != nil {
return fmt.Errorf("config %s: %w", path, err)
}四个原则:
error是最后一个返回。- 立即使用
if err != nil检查。 - 在传播之前添加上下文(使用
fmt.Errorf+%w)。 - 永远不要忽略错误:要么处理它,要么记录它,要么返回它。
_ = file.Close() // EXPLICITLY ignored (you know what you're doing)哨兵错误
哨兵错误是一个导出的全局变量 var ErrXxx = errors.New("...") ,调用者可以与它进行比较:
package store
var ErrNotFound = errors.New("not found")
func Get(key string) (string, error) {
if !exists(key) {
return "", ErrNotFound
}
// ...
}调用者使用 errors.Is (不是 ==),它遍历包装链:
v, err := store.Get(k)
if errors.Is(err, store.ErrNotFound) {
// handle 404
}标准示例:io.EOF、sql.ErrNoRows、os.ErrNotExist。
自定义类型错误
要携带结构化数据(HTTP代码、无效字段等),定义一个类型:
type ValidationError struct {
Field string
Msg string
}
func (e *ValidationError) Error() string {
return fmt.Sprintf("%s: %s", e.Field, e.Msg)
}调用者使用 errors.As 沿链安全地向下转换:
var ve *ValidationError
if errors.As(err, &ve) {
fmt.Println("invalid field:", ve.Field)
}errors.As 遍历 Unwrap() 链,直到找到可分配给传入的指针的错误。
用 %w 包装
fmt.Errorf 与 %w 动词会产生一个错误,包装原始错误,并将其保留给 errors.Is/errors.As:
if err := db.Query(); err != nil {
return fmt.Errorf("load users: %w", err)
}如果没有 %w (使用 %v 或 %s),您将获得更丰富的字符串,但 丢失 链:调用者无法再识别特定错误。
%w 的规则:
- 每个
fmt.Errorf调用仅一次(多重换行需要errors.Join)。 - 参数必须是
error,而不是字符串。 - 添加上下文,不要重复原始消息。
// bad: duplicates
return fmt.Errorf("error: %w", err) // "error: file not found"
// good: useful context
return fmt.Errorf("load config %s: %w", path, err)errors.Join 对于多个错误
从 Go 1.20 开始, errors.Join(errs...) 将多个错误合并为一个,使所有错误都可以被 errors.Is/As 检测到:
var errs []error
for _, item := range items {
if err := process(item); err != nil {
errs = append(errs, fmt.Errorf("item %s: %w", item.ID, err))
}
}
if len(errs) > 0 {
return errors.Join(errs...)
}何时使用 panic
永远不会出现预期的错误。 仅在三种情况下:
- 不可恢复的错误:在“不可能发生”的地方违反了不变量(
panic("unreachable"))。 - 独立程序的
init()/main()中初始化失败(缺少关键配置 → 快速失败)。 - 明确记录它的库 API:例如
regexp.MustCompile(如果模式无效则出现恐慌 - 可以接受,因为该模式是构建时常量)。
其他一切都是 error。 不要使用 panic 作为向上传播的快捷方式:它会破坏组合,跳过 defer Close(),并淹没调用者。
练习
os.ReadFile 中出现错误。使用错误信息%w e aggiungendo il contro 'config:' (con il path)。
3 次尝试后可用的解决方案
美国错误。是否已验证错误,并包含相应的错误信息,并印有“manca!”标记。在塔尔卡索。
3 次尝试后可用的解决方案
Quando è idiomatico 在 una libreria Go 中使用恐慌?
func F(x int) {
// ??? panic(...)
}