Go语言的错误处理机制
recover
-
每次defer都会将defer函数压入栈中,调用函数或者方法结束时,从栈中取出执行,所以多个defer的执行顺序是先入后出。
-
recover只能在defer函数中捕获异常,单独出现没有意义,发生异常的函数停止执行,其余函数继续执行。
-
defer、return、返回值三者的执行顺序是:
- 先给返回值赋值
- 执行defer语句
- 包裹函数return返回
gofunc f() (r int) { t := 5 defer func() { r = r + 5 // 直接修改返回值变量r }() return t // 第一步r=5 → defer执行r=10 → 返回10 } // 等价于 func f() (r int) { t := 5 // 第1步 r = t // 第2步 defer func() { r = r + 5 // 第3步 }() return // 第4步 } // f()执行结果为t->5
代码示例
go
package main
import (
"fmt"
)
func test() {
defer func() {
if err := recover(); err != nil {
fmt.Println("Recovered:", err)
}
}()
a, b := 1, 0
fmt.Println(a / b)
fmt.Println("Error After")
}
func main() {
fmt.Println("Start")
test()
fmt.Println("End")
}
执行结果如下:
powershell
Start
Recovered: runtime error: integer divide by zero
End
代码解析
fmt.Println("Start")程序开始;- 进入函数
test();执行到fmt.Println(a / b),除数不能为0,所以该错误会立即被recover捕获,进入到defer函数中,该处打印错误信息Recovered: runtime error: integer divide by zero;此时test()函数结束执行,fmt.Println(a / b)下方的fmt.Println("Error After")不会被执行; fmt.Println("End")程序结束;
多唠两句
recover并不能阻止错误发生,而是让程序「不崩溃」,并能处理错误后继续运行。
panic
- 它会立即终止当前 goroutine 的正常代码执行流程;
- 如果没有 recover 兜底,会导致整个程序崩溃退出;
- 可以理解为:程序运行中遇到了 "没法继续往前走" 的致命问题,只能 "喊停"。
- 包含两大核心场景:Go 语言自动触发、开发者手动触发
panic 的执行机制
- 立即终止当前行及后续的正常代码;
- 倒序执行当前函数中已声明的 defer 语句(后进先出);
- 向上传递到调用方函数,重复步骤 1-2(相当于 "逐层回退");
- 如果整个调用链中没有 recover,程序最终崩溃退出。
代码示例
go
package main
import "fmt"
// 普通error:参数错误(可预期)
func divide(a, b int) (int, error) {
if b == 0 {
return 0, fmt.Errorf("除数不能为0") // 返回error,不终止程序
}
return a / b, nil
}
// panic:空指针(不可恢复)
func nilPointer() {
var p *int
*p = 10 // 自动触发panic
}
func main() {
// 处理error
res, err := divide(10, 0)
if err != nil {
fmt.Println("处理error:", err) // 输出:处理error:除数不能为0
} else {
fmt.Println("结果:", res)
}
// 处理panic
defer func() {
if err := recover(); err != nil {
fmt.Println("处理panic:", err) // 输出:处理panic: runtime error: invalid memory address or nil pointer dereference
}
}()
nilPointer()
// 处理完panic后,程序继续运行
fmt.Println("程序未崩溃,正常结束")
}
执行结果如下:
powershell
处理error: 除数不能为0
处理panic: runtime error: invalid memory address or nil pointer dereference
程序未崩溃,正常结束
多唠两句
- 尽量少用手动 panic:业务逻辑优先用 error 处理,panic 只留给 "程序没法运行" 的极端场景;
- recover 只在顶层函数兜底:比如 HTTP 服务的中间件、gRPC 拦截器、main 函数,避免在底层函数滥用 recover(会隐藏问题);
- recover 后记录日志:捕获 panic 时,一定要记录完整的堆栈信息(用 debug.Stack()),方便排查问题;
- 不要用 panic 替代 error:比如用户输入错误、接口调用失败,用 error 更优雅。