使用recover简化错误处理需要注意的问题
在学习panic/recover机制的时候,示例中提到了利用这个机制来简化重复的错误处理工作,如下对可能会产生错误的函数进行包装。
// panic_recover.go
package main
import (
"fmt"
)
func protect(g func()) {
defer func() {
log.Println("done")
// Println executes normally even if there is a panic
if err := recover(); err != nil {
log.Printf("run time panic: %v", err)
}
}()
log.Println("start")
g() // possible runtime-error
}
func badCall() {
panic("bad end")
}
func test() {
defer func() {
if e := recover(); e != nil {
fmt.Printf("Panicing %s\r\n", e)
}
}()
badCall()
fmt.Printf("After bad call\r\n") // <-- wordt niet bereikt
}
func main() {
fmt.Printf("Calling test\r\n")
test()
fmt.Printf("Test completed\r\n")
}
这样,在被包装的函数里面出现错误的时候,我们可以直接通过 panic 抛出一个异常,这个panic不会影响到被包装的函数之后的函数运行,当然这个函数是无法正常执行完毕了。
既然 panic 会导致栈被展开直到 defer 修饰的 recover () 被调用或者程序中止, 而recover可以让程序可以从 panicking 重新获得控制权,停止终止过程进而恢复正常执行。 那么我想着,岂不是直接在 main 函数的 defer 里面进行 recover,就可以在整个程序中使用 panic 来抛出错误了?
在实际运行中确实也是如此,在检查错误的时候,如果 err != nil
便可以直接使用 panic 抛出。不过在之后的使用中我发现了这样操作的一个问题,比起包装器函数,在 main 中进行recover后,程序也就退出了。想了想,确实也是这样,recover后,会继续执行这个函数之后的函数,在包装器函数中进行捕获后,会执行后续的函数,该函数的执行则到此为止, 示例代码中的After bad call 是不会打印出来的而test函数之后的代码是可以继续执行的)。而在main中才进行recover的话,栈已经展开到了main这一层,也不存在下一个执行的函数了,剩下的就是退出了。
所以在main中进行recover来捕获错误,可以用来打印、记录日志,但是如果我们是希望在捕获一个错误后还要做出处理让程序执行下去,那么我们需要在可能会产生错误的函数中的defer中进行recover或者使用包装器。