前言
defer是Golang中到一个非常重要的特性,我们可以通过defer来实现就地释放已经申请的资源,中的比如一个文件句柄
file, _ := os.Open("test")
defer file.Close()
此外,我们还经常使用defer来处理错误返回,例如在mysql的事务处理中
tx := DB.Begin() // 开启事务
defer func() {
if err != nil {
log.Logger.Error("insert beans failed")
tx.Rollback() // 事务回退
}
}()
if err = tx.Error; err != nil {
return err
}
// 执行创建操作
if err = tx.Debug().Create(bean).Error; err != nil {
log.Logger.Error("insert beans failed")
return err
}
// 提交事务
return tx.Commit().Error
defer的特性
- 调用一定是在方法结束时发生
- 部是一个栈,后进先出,例如
defer fmt.Println(1) defer fmt.Println(2) // 会输出2 1 - 遇到了
return、panic依然可以输出,这里的意思是说,如果defer后边有return或panic,依然会在函数的最后执行defer后边的内容,比如这里的话输出是3 2 1,在函数最后依然会调用func tryDefer() { defer fmt.Println(1) defer fmt.Println(2) fmt.Println(3) return fmt.Println(4) }defer的内容,当然,如果是这样那么是不会输出的,因为根本没有执行到func tryDefer() { return //请注意这个return defer fmt.Println(1) defer fmt.Println(2) fmt.Println(3) fmt.Println(4) }defer语句,这和Java的finally效果是不一样的 - 参数在defer语句时计算,并不是到最后输出的时候再去进行计算
- 这里可能就有人疑问了,为啥前言中第二个例子的err就是直接调用的呢?这是因为err起始是一个指针类型。
defer在错误处理时容易遇到的问题
func main() {
var err error
err = fmt.Errorf("aaa")
defer func() {
fmt.Println(err)
}()
{
dir, err := os.Getwd()
fmt.Println(dir)
}
}
func main() {
var err error
err = fmt.Errorf("aaa")
defer func() {
fmt.Println(err)
}()
dir, err := os.Getwd()
fmt.Println(dir)
}
上边两段代码,都是使用defer来做错误处理,但是第一段是有问题的,而且是平常使用中很难排查到的问题dir, err := os.Getwd()这段代码是在一个新的作用域中对err做了一次新的声明,用来获取os.Getwd()方法的返回参数,但是此时的err与defer中的err是不同的两个error,这会导致无法通过defer正常完成错误的捕获。