defer

为什么要用 defer

任何一个特性都有它的设计初衷,主要是被用来解决什么问题的,任何一个特性也都有它合适和不合适出现的地方,我们清楚地了解并正确合理地使用,是非常重要的。

优势

提高安全性、健壮性

让代码更优雅

劣势

可读性、可维护性

deferdefer

defer 怎么用

deferdeferdefer

建议使用

Recover

defer func() {
    if r := recover(); r != nil {
        fmt.Println("Recovered", r)
    }
}()

资源回收

各种资源的使用,如果在用完之后不 close,就会造成资源的泄露,可能会严重影响程序运行,甚至造成程序死掉

网络 I/O
c, err := Dial("udp", raddr)
if err != nil {
    return err
}
defer c.Close()
文件 I/O
f, err := os.Open(filename)
if err != nil {
    return
}
defer f.Close()
channel 关闭
fd, _ := os.Open("txt")
errc := make(chan error, 1)
// 主动关闭,减小 GC 压力。
defer close(errc)
    
var buf [1]byte
n, err := fd.Read(buf[:1])
if n == 0 || err != nil {
    errc <- fmt.Errorf("read byte = %d, err = %v", n, err)
}

避免死锁

type A struct {
    t int
    sync.Mutex
}

func main() {
    a := new(A)
    for i := 0; i < 2000; i++ {
        go a.incr()
    }
    time.Sleep(500 * time.Millisecond) // 此处用 sleep 简单模拟等待同步,实际这样写不严谨,可用 waitGroup、channel 等
    fmt.Println(a.t)
}

func (a *A) incr() {
    a.Lock()
    defer a.Unlock()
    
    // 模拟 ... 一堆逻辑

    // 然后 ... 中间有好几个 return 出口
    
    // 如果我们不用 defer,就要在每个 return 都写上 a.Unlock,不然就可能会造成死锁    
    a.t++
}

中立

函数返回时的打点

记日志
start := time.Now() fmt.Printf("enter %s\n", msg)
func main() {
    do()
}

func do() {
    defer log("do")()

    // ... 一些逻辑

    time.Sleep(1 * time.Second)
}

func log(msg string) func() {
    start := time.Now()
    fmt.Printf("enter %s\n", msg)
    return func() { fmt.Printf("exit %s (%s)", msg, time.Since(start)) }
}

错误处理

因为 go 自带的比较恶心的 err != nil 的判断,业务逻辑中可能会有大量的这种代码,而我们又要对出错进行一个统一的处理的时候,可以用。

数据库事务的回滚操作
tx, err := db.Begin()
if err != nil {
    return err
}
defer func() {
    if err != nil {
        tx.Rollback()
    }
}()

// ... 中间会发生多个数据库操作 ...

// 提交,那么在提交之前发生的任何错误,返回时都可利用之前注册的 defer 进行回滚
tx.Commit()

不建议

不建议的用法就不给出代码示例了,怕你看了错误的代码示例反而记住了,就不好了。下面只说不建议的用法场景。

不要直接在循环中使用 defer

deferdeferdefer

不要在 defer 中传入体积很大的参数

defer

不要用 receiver 调用 defer

receiverreceiverdefer

未完待续。。。

defer 原理简述

defer
recoverrecover
defer
func deferproc(siz int32, fn *funcval)
return
func deferreturn(arg0 uintptr)
arg0defer

结语

老老实实写代码,不要总想玩魔法。