错误处理
Go 的错误处理采用显式返回错误的方式,而不是传统的 try-catch 异常机制。函数通常把 error 作为最后一个返回值,调用方显式检查并处理。
主要机制:
| 机制 | 说明 |
|---|---|
| error 接口 | 标准的错误表示 |
| 显式返回值 | 通过函数返回值返回错误 |
| 自定义错误 | 通过标准库或自定义类型创建错误 |
| panic 和 recover | 处理不可恢复的严重错误 |
error 接口
error 是标准库定义的一个接口类型,任何实现了 Error() 方法的类型都可以作为错误:
type error interface {
Error() string
}
errors.New 创建错误
使用 errors 包创建简单错误,函数通常在最后返回错误信息:
package main
import (
"errors"
"fmt"
)
func Sqrt(f float64) (float64, error) {
if f < 0 {
return 0, errors.New("math: square root of negative number")
}
return f * f, nil
}
func main() {
result, err := Sqrt(-1)
if err != nil {
fmt.Println(err) // math: square root of negative number
} else {
fmt.Println(result)
}
}
显式返回错误
调用函数时必须检查 error 是否为 nil:
package main
import (
"errors"
"fmt"
)
func divide(a, b int) (int, error) {
if b == 0 {
return 0, errors.New("division by zero")
}
return a / b, nil
}
func main() {
result, err := divide(10, 0)
if err != nil {
fmt.Println("Error:", err) // Error: division by zero
} else {
fmt.Println("Result:", result)
}
}
fmt.Errorf 格式化错误
fmt.Errorf 可以带格式化参数生成错误信息,%w 可以包装错误,配合 errors.Is 和 errors.As 处理错误链:
package main
import (
"errors"
"fmt"
)
var ErrNotFound = errors.New("not found")
func findItem(id int) error {
// %w 把 ErrNotFound 包装进新的错误
return fmt.Errorf("database error: %w", ErrNotFound)
}
func main() {
err := findItem(1)
if errors.Is(err, ErrNotFound) {
fmt.Println("Item not found")
} else {
fmt.Println("Other error:", err)
}
}
自定义错误类型
通过定义实现了 Error() 方法的类型来扩展错误,可以携带更多信息:
package main
import (
"errors"
"fmt"
)
type DivideError struct {
Dividend int
Divisor int
}
func (e *DivideError) Error() string {
return fmt.Sprintf("cannot divide %d by %d", e.Dividend, e.Divisor)
}
func divide(a, b int) (int, error) {
if b == 0 {
return 0, &DivideError{Dividend: a, Divisor: b}
}
return a / b, nil
}
// errors.As 把错误转换为特定类型以便进一步处理
func main() {
_, err := divide(10, 0)
var de *DivideError
if errors.As(err, &de) {
fmt.Printf("自定义错误 - 被除数: %d, 除数: %d\n", de.Dividend, de.Divisor)
}
}
panic 与 recover
panic 用于处理不可恢复的错误,会导致程序崩溃并输出堆栈信息;recover 用于捕获 panic,避免程序崩溃。
recover 只能写在 defer 注册的函数内,详见 20-defer.md
package main
import "fmt"
func safeFunction() {
defer func() {
if r := recover(); r != nil {
fmt.Println("Recovered from panic:", r)
}
}()
panic("something went wrong")
}
func main() {
fmt.Println("Starting program...")
safeFunction()
fmt.Println("Program continued after panic")
}
输出结果:
Starting program...
Recovered from panic: something went wrong
Program continued after panic
panic 后不再执行后面的代码,会按逆序执行 defer 再逐层向外扩散,直到被 recover 捕获或程序结束
错误处理最佳实践
调用第三方函数时必须显式处理错误:
package main
import (
"fmt"
"os"
)
func readConfig(path string) error {
data, err := os.ReadFile(path)
if err != nil {
// 返回包装后的错误,方便定位调用链
return fmt.Errorf("读取配置失败: %w", err)
}
fmt.Println("配置内容:", string(data))
return nil
}
func main() {
if err := readConfig("nonexist.txt"); err != nil {
fmt.Println("处理错误:", err)
}
}
最佳实践:函数优先返回 error;不要吞掉错误;使用 %w 包装错误保留调用链;能用 error 处理的场景不要轻易用 panic