Go 错误处理

Go 没有异常机制,错误就是普通的值:函数出错时返回 error,由调用方检查处理。error 是内置接口,只有一个方法 Error() string。(说明:文中的片段代码需放进完整程序里运行)

error 与 errors.New

完整示例:

package main

import (
	"errors"
	"fmt"
)

func checkAge(age int) error {
	if age < 0 {
		return errors.New("年龄不能为负数")
	}
	return nil // 没有错误就返回 nil
}

func main() {
	if err := checkAge(-1); err != nil {
		fmt.Println("出错了:", err)
	} else {
		fmt.Println("一切正常")
	}
}
// 输出: 出错了: 年龄不能为负数

约定:错误变量叫 errnil 表示没有错误,if err != nil 是 Go 最常见的代码节奏。

fmt.Errorf 与 %w 包装

fmt.Errorf 可以拼接上下文信息;%w包装已有错误,形成可追溯的错误链,供 errors.Is/As 使用:

err := errors.New("文件名为空")
fmt.Errorf("读取失败:%w", err) // 输出: 读取失败:文件名为空

注意 %w 只能用于 fmt.Errorf;用 %v 只是拼文本,会丢失错误链。

errors.Is 与 errors.As

错误被多层包装后,用 errors.Is 判断链上是否存在某个目标错误;errors.As 把链上某类错误取出赋值:

var ErrNotFound = errors.New("记录不存在") // 哨兵错误

type MyError struct{ Code int }

func (e *MyError) Error() string { return fmt.Sprintf("错误码 %d", e.Code) }

func find(id int) error {
	if id <= 0 {
		return fmt.Errorf("find: %w", ErrNotFound) // 包装
	}
	return &MyError{Code: 404}
}

// errors.Is(find(0), ErrNotFound) → true
// var me *MyError; errors.As(find(1), &me) → me.Code == 404

判断错误应优先用 errors.Is/As 而不是比较字符串——字符串容易拼错,且经 %w 包装后会失效。

自定义错误类型

只要实现 Error() string 就满足 error 接口,还能携带额外字段:

type MyError struct {
	Code int
	Msg  string
}

func (e *MyError) Error() string {
	return fmt.Sprintf("错误码 %d:%s", e.Code, e.Msg)
}

// err := &MyError{Code: 1001, Msg: "除数不能为 0"}
// fmt.Println(err) → 错误码 1001:除数不能为 0

若还想取回字段细节,配合类型断言 e, ok := err.(*MyError) 即可。

panic 与 recover(一句话)

Go 也有 panic(抛出)和 recover(在 defer 中捕获),但只适合程序无法继续的致命情况;可预期的错误请用 error 返回处理

小结:习惯 if err != nil;包装用 %w,判断用 errors.Is/As,自定义错误只需实现 Error()

笔记加载中…