2.1.1 defer 陷阱
前言
项目中,有时为了让程序更健壮,也即不panic
,我们或许会使用recover()
来接收异常并处理。
比如以下代码:
func NoPanic() {
if err := recover(); err != nil {
fmt.Println("Recover success...")
}
}
func Dived(n int) {
defer NoPanic()
fmt.Println(1/n)
}
func NoPanic()
会自动接收异常,并打印相关日志,算是一个通用的异常处理函数。
业务处理函数中只要使用了defer NoPanic()
,那么就不会再有panic
发生。
关于是否应该使用recover接收异常,以及什么场景下使用等问题不在本节讨论范围内。 本节关注的是这种用法的一个变体,曾经出现在笔者经历的一个真实项目,在该变体下,recover再也无法接收异常。
recover使用误区
在项目中,有众多的数据库更新操作,正常的更新操作需要提交,而失败的就需要回滚,如果异常分支比较多, 就会有很多重复的回滚代码,所以有人尝试了一个做法:即在defer中判断是否出现异常,有异常则回滚,否则提交。
简化代码如下所示:
func IsPanic() bool {
if err := recover(); err != nil {
fmt.Println("Recover success...")
return true
}
return false
}
func UpdateTable() {
// defer中决定提交还是回滚
defer func() {
if IsPanic() {
// Rollback transaction
} else {
// Commit transaction
}
}()
// Database update operation...
}
func IsPanic() bool
用来接收异常,返回值用来说明是否发生了异常。func UpdateTable()
函数中,使用defer来判断最终应该提交还是回滚。
上面代码初步看起来还算合理,但是此处的IsPanic()
再也不会返回true
,不是IsPanic()
函数的问题,而是其调用的位置不对。
recover 失效的条件
上面代码IsPanic()
失效了,其原因是违反了recover的一个限制,导致recover()失效(永远返回nil
)。
以下三个条件会让recover()返回nil
:
- panic时指定的参数为
nil
;(一般panic语句如panic("xxx failed...")
) - 当前协程没有发生panic;
- recover没有被defer方法直接调用;
前两条都比较容易理解,上述例子正是匹配第3个条件。
本例中,recover() 调用栈为“defer (匿名)函数” --> IsPanic() --> recover()。也就是说,recover并没有被defer方法直接调用。符合第3个条件,所以recover() 永远返回nil。
赠人玫瑰手留余香,如果觉得不错请给个赞~
本篇文章已归档到GitHub项目,求星~ 点我即达
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论