Go 对错误处理的态度非常直白:错误就是一种值(Values),不是异常(Exceptions)。我们通过函数返回值来传递错误,并显式地检查它们。只有在真正的不可恢复情况(如数组越界)下,才会使用 panic

示例代码

 1package main
 2
 3import (
 4    "errors"
 5    "fmt"
 6)
 7
 8// 定义一个除法函数,返回 result 和 error
 9func divide(a, b int) (int, error) {
10    if b == 0 {
11        // 使用 errors.New 创建一个简单的错误对象
12        return 0, errors.New("cannot divide by zero")
13    }
14    return a / b, nil
15}
16
17// 演示 panic 和 recover
18func safeCall() {
19    // defer 必须在 panic 发生前定义
20    defer func() {
21        // recover() 捕获 panic,如果返回值不为 nil,说明发生了 panic
22        if r := recover(); r != nil {
23            fmt.Println("Recovered from panic:", r)
24        }
25    }()
26
27    panic("Something went wrong terribly!")
28    fmt.Println("This line will not execute")
29}
30
31func main() {
32    // 1. 标准错误处理
33    res, err := divide(10, 0)
34    if err != nil {
35        fmt.Println("Error:", err)
36    } else {
37        fmt.Println("Result:", res)
38    }
39
40    // 2. 演示从 panic 中恢复
41    fmt.Println("Starting safeCall...")
42    safeCall()
43    fmt.Println("Program continues...")
44}

关键点解释

error 接口

Go 内置的 error 是一个接口,只包含一个方法 Error() string

  • errors.New("msg"):创建一个简单错误。
  • fmt.Errorf("code %d", 500):创建格式化的错误消息。

处理流程

习惯写法是:

1result, err := someFunction()
2if err != nil {
3    // 处理错误(返回、日志、重试等)
4    return err
5}
6// 正常逻辑

这种卫语句(Guard Clause)风格让正常逻辑保持在最左侧,避免了嵌套地狱。

Panic 和 Recover

  • Panic:导致程序崩溃,打印堆栈信息。一般用于逻辑无法继续执行的情况。
  • Recover:仅在 defer 函数中有效。可以捕获 panic,阻止程序崩溃,类似于 Java 的 catch。

小结

显式的错误检查虽然增加了一些代码量,但它迫使开发者在编写代码时就考虑错误情况,从而构建出更加健壮的系统。

练习题

  1. 编写一个函数,打开一个不存在的文件(使用 os.Open),并打印返回的错误信息。
  2. 定义一个自定义错误类型(结构体),包含 CodeMsg 字段,实现 error 接口。

相关阅读