Go语言中的错误处理--defer|Go主题月

1,537 阅读2分钟

前言

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
    
  • 遇到了returnpanic依然可以输出,这里的意思是说,如果defer后边有returnpanic,依然会在函数的最后执行defer后边的内容,比如
    func tryDefer() {
        defer fmt.Println(1)
        defer fmt.Println(2)
        fmt.Println(3)
        return
        fmt.Println(4)
    }
    
    这里的话输出是3 2 1,在函数最后依然会调用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()方法的返回参数,但是此时的errdefer中的err是不同的两个error,这会导致无法通过defer正常完成错误的捕获。