在Golang的异常处理体系中,panic和recover是两个核心关键字,它们配合defer关键字可以实现程序异常的捕获和恢复,避免未处理的错误导致整个程序直接退出。理解这两个关键字的执行逻辑和使用边界,是写出稳定Golang程序的基础。

panic和recover的基本定义
panic是Golang内置的关键字,用于主动触发一个运行时恐慌,当程序执行到panic语句时,会立即停止当前函数的执行,开始逐层向上执行之前注册的defer函数,直到被recover捕获或者程序终止。
recover同样是内置关键字,用于捕获panic抛出的异常值,它只能在defer修饰的函数中生效,在其他位置调用recover会返回nil,无法捕获任何异常。
基础使用示例
先看一个最简单的panic触发示例,当程序执行到panic时,会直接抛出错误并终止,除非有对应的recover处理。
package main
import "fmt"
func main() {
fmt.Println("程序开始执行")
// 主动触发panic
panic("发生了一个自定义错误")
// 下面的代码不会被执行
fmt.Println("程序结束执行")
}
上面的代码运行后会直接输出错误信息并退出,接下来看加入recover的版本,通过defer函数捕获panic:
package main
import "fmt"
func main() {
// 注册defer函数,内部调用recover
defer func() {
if err := recover(); err != nil {
fmt.Printf("捕获到异常: %vn", err)
}
}()
fmt.Println("程序开始执行")
panic("发生了一个自定义错误")
fmt.Println("程序结束执行")
}
运行上述代码后,panic抛出的异常会被defer中的recover捕获,程序不会崩溃,会继续执行main函数之后的逻辑。
panic和recover的执行顺序
当函数中触发panic时,执行顺序遵循以下规则:
- 立即停止当前函数的剩余代码执行
- 按照defer的注册逆序执行当前函数中所有已注册的defer函数
- 如果某个defer函数中调用了recover且捕获到了异常,那么panic的向上传播会停止,程序从捕获位置继续执行
- 如果没有recover捕获,会继续向上层调用函数传播panic,重复上述过程,直到最外层没有recover则程序崩溃
下面通过一个多层函数调用的示例验证这个顺序:
package main
import "fmt"
func funcA() {
fmt.Println("进入funcA")
funcB()
fmt.Println("离开funcA")
}
func funcB() {
defer func() {
fmt.Println("执行funcB的defer")
}()
fmt.Println("进入funcB")
funcC()
fmt.Println("离开funcB")
}
func funcC() {
defer func() {
if err := recover(); err != nil {
fmt.Printf("funcC捕获到异常: %vn", err)
}
}()
fmt.Println("进入funcC")
panic("funcC触发错误")
fmt.Println("离开funcC")
}
func main() {
funcA()
fmt.Println("程序正常结束")
}
运行上述代码,输出顺序为:进入funcA、进入funcB、进入funcC、funcC捕获到异常: funcC触发错误、执行funcB的defer、离开funcA、程序正常结束。可以看到panic在funcC中被捕获后,没有继续向上传播,funcB和funcA的后续代码都正常执行了。
常见实践场景
场景一:捕获不可预期的运行时错误
除了主动调用panic,Golang运行时也会在一些错误场景下自动触发panic,比如数组越界、空指针解引用等,这时可以通过recover捕获这些错误,避免程序直接崩溃。
package main
import "fmt"
func main() {
defer func() {
if err := recover(); err != nil {
fmt.Printf("捕获到运行时错误: %vn", err)
}
}()
var arr []int
// 访问空切片的元素,会触发运行时panic
fmt.Println(arr[0])
}
场景二:自定义业务异常恢复
在一些业务场景中,可以主动抛出panic来表示不可恢复的业务错误,然后在统一的入口处通过recover捕获,进行日志记录或者错误上报。
package main
import "fmt"
// 模拟业务处理函数
func businessHandle() {
defer func() {
if err := recover(); err != nil {
fmt.Printf("业务处理失败: %vn", err)
}
}()
// 模拟业务校验失败,主动抛出panic
userAge := -5
if userAge < 0 {
panic("用户年龄不能为负数")
}
fmt.Println("业务处理成功")
}
func main() {
businessHandle()
fmt.Println("主程序继续执行")
}
使用注意事项
- recover只有在defer函数中直接调用才会生效,不能在defer调用的普通函数中再嵌套调用recover,否则无法捕获异常
- 不要滥用panic和recover,对于可预期的错误,优先使用error返回值处理,panic更适合处理不可恢复的程序错误
- recover返回的是panic传入的参数值,可以是任意类型,捕获后可以根据值的类型做不同的处理逻辑
- 如果defer函数中发生了panic,且没有更内层的recover捕获,那么这个新的panic会继续向上传播
下面是一个recover使用不当的示例,在普通函数中调用recover无法捕获异常:
package main
import "fmt"
func recoverFunc() {
// 这里调用recover不会生效,因为不在defer的直接执行逻辑中
if err := recover(); err != nil {
fmt.Printf("捕获到异常: %vn", err)
}
}
func main() {
defer recoverFunc()
panic("测试错误")
}
上述代码中,recoverFunc是在defer中调用的普通函数,内部调用recover无法生效,程序还是会崩溃,正确的做法是将recover直接写在defer的匿名函数中。