引入
程序总会遇到意外情况——文件找不到、网络断了、参数不合法。很多语言用异常(exception)来处理这些问题,但 Go 选择了另一条路:用返回值代替异常。函数多返回一个 error 值,调用方检查它是不是 nil,就知道有没有出错。这种方式让错误处理变得显式、可控,不会悄悄被忽略。
正文
定义
定义
错误处理是程序应对异常情况的方式。Go 用
error接口表示错误,函数通过返回error值来告知调用方是否出错。调用方检查error是否为nil来判断是否出错,为nil表示成功,不为nil表示有错误。
语法
// error 接口定义(Go 内置)
type error interface {
Error() string
}
// 创建错误
err := errors.New("something went wrong")
// 带格式的错误
err := fmt.Errorf("expected %d, got %d", 10, 20)
// 检查错误
result, err := doSomething()
if err != nil {
// 处理错误
return err
}例子
标准错误检查模式
file, err := os.Open("config.txt")
if err != nil {
fmt.Println("打开文件失败:", err)
return
}
defer file.Close()
// 正常使用 file这是 Go 里最常见的错误处理模式:先检查 err,有问题就提前返回,没问题就继续。
errors.New()
import "errors"
func divide(a, b float64) (float64, error) {
if b == 0 {
return 0, errors.New("除数不能为 0")
}
return a / b, nil
}
func main() {
result, err := divide(10, 0)
if err != nil {
fmt.Println("错误:", err)
return
}
fmt.Println("结果:", result)
}errors.New() 用字符串创建一个简单的错误值,适合错误信息固定的场景。
fmt.Errorf()
import "fmt"
func getAge(name string) (int, error) {
if name == "" {
return 0, fmt.Errorf("名字不能为空")
}
if len(name) > 50 {
return 0, fmt.Errorf("名字太长: %d 个字符", len(name))
}
return 25, nil
}fmt.Errorf() 支持格式化字符串,可以把变量插入到错误信息里,比 errors.New() 更灵活。
自定义错误类型
type ValidationError struct {
Field string
Message string
}
func (e *ValidationError) Error() string {
return fmt.Sprintf("字段 %s 校验失败: %s", e.Field, e.Message)
}
func validateEmail(email string) error {
if email == "" {
return &ValidationError{
Field: "email",
Message: "不能为空",
}
}
return nil
}
func main() {
err := validateEmail("")
if err != nil {
fmt.Println(err)
// 字段 email 校验失败: 不能为空
}
}只要实现 Error() string 方法,任何类型都可以当 error 用。自定义错误类型可以携带更多上下文信息。
判断错误类型
err := validateEmail("")
if err != nil {
// 类型断言判断具体错误类型
if ve, ok := err.(*ValidationError); ok {
fmt.Printf("字段: %s, 原因: %s\n", ve.Field, ve.Message)
} else {
fmt.Println("未知错误:", err)
}
}用类型断言可以区分不同的错误类型,做针对性的错误处理。
errors.Is() 和 errors.As()
import "errors"
// 预定义哨兵错误
var ErrNotFound = errors.New("not found")
func findUser(id int) (string, error) {
if id <= 0 {
return "", ErrNotFound
}
return "Alice", nil
}
func main() {
_, err := findUser(-1)
// errors.Is:判断错误链中是否包含指定错误
if errors.Is(err, ErrNotFound) {
fmt.Println("用户不存在")
}
// errors.As:从错误链中提取指定类型的错误
var ve *ValidationError
if errors.As(err, &ve) {
fmt.Println("校验失败:", ve.Field)
}
}errors.Is() 和 errors.As() 是 Go 1.13 引入的,支持错误包装链,比直接比较 == 更可靠。
包装错误
func readConfig(path string) error {
_, err := os.Open(path)
if err != nil {
// %w 把原始错误包装进去
return fmt.Errorf("读取配置失败: %w", err)
}
return nil
}
func main() {
err := readConfig("missing.txt")
if err != nil {
fmt.Println(err)
// 读取配置失败: open missing.txt: ...
// 用 errors.Is 可以检查被包装的原始错误
if errors.Is(err, os.ErrNotExist) {
fmt.Println("文件不存在")
}
}
}fmt.Errorf() 用 %w 而不是 %v 来包装错误,保留原始错误信息,让 errors.Is() 和 errors.As() 能正常工作。
常见写法
| 写法 | 说明 |
|---|---|
errors.New("msg") | 创建简单错误 |
fmt.Errorf("msg %d", v) | 创建格式化错误 |
fmt.Errorf("msg: %w", err) | 包装错误(保留链) |
if err != nil { return err } | 检查并返回错误 |
errors.Is(err, target) | 判断错误链是否包含目标错误 |
errors.As(err, &target) | 从错误链提取指定类型 |
自定义 Error() string | 自定义错误类型 |
特点
- Go 没有 try-catch 异常机制,错误就是普通返回值
error是一个接口,只要实现Error() string方法就行- 错误检查是显式的,必须手动
if err != nil,不会悄悄吞掉错误 errors.New()和fmt.Errorf()是创建错误的两种标准方式%w用于包装错误,%v只是格式化字符串,不保留错误链- Early return 是 Go 错误处理的主流风格:有错就提前返回,不嵌套
- 不要把
error丢掉不管(_ = doSomething()这种写法要谨慎)
理解
Go 的错误处理哲学是”错误是值,不是异常”。没有隐藏的异常传播链,每个错误都在代码里明明白白地处理。虽然 if err != nil 写多了觉得啰嗦,但好处是错误不会被忽略,代码流程一目了然。自定义错误类型配合 errors.Is() 和 errors.As() 可以构建清晰的错误处理体系。
引出
错误处理是 Go 基础语法里最后一个大块。接下来可以进入 Go 数据类型,学习 Go 里的各种数据类型:整型、浮点型、字符串、数组、切片、map 等。