【Go常见错误】5. 善用defer

案例

首先,我们看一个拷贝文件函数的示例。

func CopyFile(srcName, dstName string) error {
   src, err := os.Open(srcName) //①
   if err != nil {
          return err   
   }
   stat, err := src.Stat()   
   if err != nil {       
       src.Close() //注意关闭1
       return err   
   }   
   if stat.IsDir() { //②
       src.Close() //注意关闭2  
       return fmt.Errorf("file %q is a directory", srcName)  
    }   
    dst, err := os.Create(dstName) //③   
    if err != nil {       
        src.Close() //注意关闭3
        return err   
    }   
    _, err = io.Copy(dst, src) //④   
    if err != nil {       
        src.Close() //注意关闭4
        dst.Close() //注意关闭5
        return err   
    }   
    err = dst.Sync() //⑤   
    src.Close() //注意关闭6
    dst.Close() //注意关闭7
    return err
 }

① 打开源文件

② 检查是否是目录

③ 创建目标文件

④ 拷贝源文件到目标文件

⑤ 刷新文件系统缓冲区

我们需要管理该文件描述符的关闭,因为一个 *os.File一旦被打开准备读写后,它就必须要使用Close函数进行关闭。

但值得注意的是,上面一共需要调用7处Close方法,如果有一处遗漏,就将导致错误发生。

解决方法

Go提供了defer关键字,可以优雅的解决该种问题。

在函数返回的时候会调用defer函数。

即使在主函数panic或意外终止时defer函数也能保证被执行。defer语句会被推送到栈中。当主函数返回时,defer函数会从栈中弹出(先进后出的顺序)。

这里,将会先调用c( ),然后b ( ),最后是a( )。

注意:一个defer调用的时机是在函数返回时,而非在所在的块退出时

再看一下上面例子用defer的实现:

func CopyFile(srcName, dstName string) error {
   src, err := os.Open(srcName)
   if err != nil {
       return err
    }
    defer src.Close() //①

    stat, err := src.Stat()
    if err != nil {
        return err
    }

   if stat.IsDir() {
       return fmt.Errorf("file %q is a directory", srcName)
   }

   dst, err := os.Create(dstName)
   if err != nil {
       return err
   }
   defer dst.Close() //②

   _, err = io.Copy(dst, src)
   if err != nil {
       return err
   }

   return dst.Sync()
}

① 延迟调用 src.Close()

② 延迟调用 dst.Close()

在这个版本的实现中,我们通过defer关键词的使用,移除了重复的close调用。这使得函数更轻量并且更易读。我们不必在每一个代码路径的末尾都关闭src和dst,这样就不容易出错了。

总结

defer语句经常会跟成对出现的操作函数一起使用,就像open/close,connect/disconnect,以及lock/unlock函数以确保在所有的场景下资源都能够得到释放。

总之,defer可以避免死板的代码以及减少忘记释放资源的风险,例如释放资源,断开链接,mutex解锁等等。


附一个有趣的用法

如果我们必须实现一个pre和post操作,比如不返回任何值的mutex lock/unlock,我们也可以这样实现:

func (s *Store) Set(key string, value int) {
     defer s.lockUnlock()() //①
     s.data[key] = value
 }
 
 func (s *Store) lockUnlock() func() {
     s.mutex.Lock()
     return func() {
         s.mutex.Unlock()
     }
 }

① 该语句会立刻执行s.lockUnlock(),但会延迟执行s.lockUnlock()()

延迟执行的函数调用是s.lockUnlock()(),而非s.lockUnlock()。

因此,s.lockUnlock()部分会立即执行(s.mutex.Lock),但是返回的闭包会被延迟执行(s.mutex.Unlock())。

它添加了一些语法糖,用一行代码来处理函数中的前/后操作,这有时非常方便。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值