ThinkChat2.0新版上线,更智能更精彩,支持会话、画图、阅读、搜索等,送10W Token,即刻开启你的AI之旅 广告
### defer陷阱 #### 1. defer 与 closure ~~~ func foo(a, b int) (i int, err error) { defer fmt.Printf("first defer err %v\n", err) defer func(err error) { fmt.Printf("second defer err %v\n", err) }(err) defer func() { fmt.Printf("third defer err %v\n", err) }() if b == 0 { err = errors.New("divided by zero!") return } i = a / b return } 输出结果: third defer err divided by zero! second defer err <nil> first defer err <nil> ~~~ 解释:如果 defer 后面跟的不是一个 closure 最后执行的时候我们得到的并不是最新的值 #### 2. defer 与 return ~~~ func foo() (i int) { i = 0 defer func() { fmt.Println(i) }() return 2 } 输出结果: 2 ~~~ 解释:在有具名返回值的函数中(这里具名返回值为 i),执行 return 2 的时候实际上已经将 i 的值重新赋值为 2。所以defer closure 输出结果为 2 而不是 1 #### 3. defer nil 函数 ~~~ func test() { var run func() = nil defer run() fmt.Println("runs") } func main() { defer func() { if err := recover(); err != nil { fmt.Println(err) } }() test() } 输出结果: runs runtime error: invalid memory address or nil pointer dereference ~~~ 解释:名为 test 的函数一直运行至结束,然后 defer 函数会被执行且会因为值为 nil 而产生 panic 异常。然而值得注意的是,run() 的声明是没有问题,因为在test函数运行完成后它才会被调用 #### 4. 在错误的位置使用 defer 当 http.Get 失败时会抛出异常。 ~~~ func do() error { res, err := http.Get("http://www.google.com") defer res.Body.Close() if err != nil { return err } // ..code... return nil } 输出结果: panic: runtime error: invalid memory address or nil pointer dereference ~~~ 解释:因为在这里我们并没有检查我们的请求是否成功执行,当它失败的时候,我们访问了 Body 中的空变量 res ,因此会抛出异常 #### recover使用误区 在项目中,有众多的数据库更新操作,正常的更新操作需要提交,而失败的就需要回滚,如果异常分支比较多, 就会有很多重复的回滚代码,所以有人尝试了一个做法:即在defer中判断是否出现异常,有异常则回滚,否则提交。 简化代码如下所示: ~~~golang 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`: 1. panic时指定的参数为`nil`;(一般panic语句如`panic("xxx failed...")`) 2. 当前协程没有发生panic; 3. recover没有被defer方法直接调用; 本例中,recover() 调用栈为“defer (匿名)函数” –> IsPanic() –> recover()。也就是说,recover并没有被defer方法直接调用。符合第3个条件,所以recover() 永远返回nil