标签:保护 time UNC 自己 known 返回结果 捕获异常 状态 模拟
panic
支持抛出任意类型的异常(而不仅仅是error
类型的错误),recover
函数调用的返回值和panic
函数的输入参数类型一致,它们的函数签名如下:
func panic(interface{})
func recover() interface{}
Go语言函数调用的正常流程是函数执行返回语句返回结果,在这个流程中是没有异常的,因此在这个流程中执行recover
异常捕获函数始终是返回nil
。另一种是异常流程: 当函数调用panic
抛出异常,函数将停止执行后续的普通语句,但是之前注册的defer
函数调用仍然保证会被正常执行,然后再返回到调用者。对于当前函数的调用者,因为处理异常状态还没有被捕获,和直接调用panic
函数的行为类似。在异常发生时,如果在defer
中执行recover
调用,它可以捕获触发panic
时的参数,并且恢复到正常的执行流程。
在非defer
语句中执行recover
调用是初学者常犯的错误:
func main() {
if r := recover(); r != nil {
log.Fatal(r)
}
panic(123)
if r := recover(); r != nil {
log.Fatal(r)
}
}
上面程序中两个recover
调用都不能捕获任何异常。在第一个recover
调用执行时,函数必然是在正常的非异常执行流程中,这时候recover
调用将返回nil
。发生异常时,第二个recover
调用将没有机会被执行到,因为panic
调用会导致函数马上执行已经注册defer
的函数后返回。
其实recover
函数调用有着更严格的要求:我们必须在defer
函数中直接调用recover
。如果defer
中调用的是recover
函数的包装函数的话,异常的捕获工作将失败!比如,有时候我们可能希望包装自己的MyRecover
函数,在内部增加必要的日志信息然后再调用recover
,这是错误的做法:
func main() {
defer func() {
// 无法捕获异常
if r := MyRecover(); r != nil {
fmt.Println(r)
}
}()
panic(1)
}
func MyRecover() interface{} {
log.Println("trace...")
return recover()
}
同样,如果是在嵌套的defer
函数中调用recover
也将导致无法捕获异常:
func main() {
defer func() {
defer func() {
// 无法捕获异常
if r := recover(); r != nil {
fmt.Println(r)
}
}()
}()
panic(1)
}
2层嵌套的defer
函数中直接调用recover
和1层defer
函数中调用包装的MyRecover
函数一样,都是经过了2个函数帧才到达真正的recover
函数,这个时候Goroutine的对应上一级栈帧中已经没有异常信息。
如果我们直接在defer
语句中调用MyRecover
函数又可以正常工作了:
func MyRecover() interface{} {
return recover()
}
func main() {
// 可以正常捕获异常
defer MyRecover()
panic(1)
}
但是,如果defer
语句直接调用recover
函数,依然不能正常捕获异常:
func main() {
// 无法捕获异常
defer recover()
panic(1)
}
必须要和有异常的栈帧只隔一个栈帧,recover
函数才能正常捕获异常。换言之,recover
函数捕获的是祖父一级调用函数栈帧的异常(刚好可以跨越一层defer
函数)!
当然,为了避免recover
调用者不能识别捕获到的异常, 应该避免用nil
为参数抛出异常:
func main() {
defer func() {
if r := recover(); r != nil { ... }
// 虽然总是返回nil, 但是可以恢复异常状态
}()
// 警告: 用`nil`为参数抛出异常
panic(nil)
}
当希望将捕获到的异常转为错误时,如果希望忠实返回原始的信息,需要针对不同的类型分别处理:
func foo() (err error) {
defer func() {
if r := recover(); r != nil {
switch x := r.(type) {
case string:
err = errors.New(x)
case error:
err = x
default:
err = fmt.Errorf("Unknown panic: %v", r)
}
}
}()
panic("TODO")
}
基于这个代码模板,我们甚至可以模拟出不同类型的异常。通过为定义不同类型的保护接口,我们就可以区分异常的类型了:
func main {
defer func() {
if r := recover(); r != nil {
switch x := r.(type) {
case runtime.Error:
// 这是运行时错误类型异常
case error:
// 普通错误类型异常
default:
// 其他类型异常
}
}
}()
// ...
}
不过这样做和Go语言简单直接的编程哲学背道而驰了。
标签:保护 time UNC 自己 known 返回结果 捕获异常 状态 模拟
原文地址:https://www.cnblogs.com/ithubb/p/14188016.html