• 企业400电话
  • 微网小程序
  • AI电话机器人
  • 电商代运营
  • 全 部 栏 目

    企业400电话 网络优化推广 AI电话机器人 呼叫中心 网站建设 商标✡知产 微网小程序 电商运营 彩铃•短信 增值拓展业务
    Golang巧用defer进行错误处理的方法

    本文主要跟大家介绍了Golang巧用defer进行错误处理的相关内容,分享出来供大家参考学习,下面来看看详细的介绍:

    问题引入

    毫无疑问,错误处理是程序的重要组成部分,有效且优雅的处理错误是大多数程序员的追求。很多程序员都有C/C++的编程背景,Golang的程序员也不例外,他们处理错误有意无意的带着C/C++的烙印。

    我们看看下面的例子,就有一种似曾相识的赶脚,代码如下:

    func deferDemo() error {
     err := createResource1()
     if err != nil {
     return ERR_CREATE_RESOURCE1_FAILED
     }
     err = createResource2()
     if err != nil {
     destroyResource1()
     return ERR_CREATE_RESOURCE2_FAILED
     }
    
     err = createResource3()
     if err != nil {
     destroyResource1()
     destroyResource2()
     return ERR_CREATE_RESOURCE3_FAILED
     }
    
     err = createResource4()
     if err != nil {
     destroyResource1()
     destroyResource2()
     destroyResource3()
     return ERR_CREATE_RESOURCE4_FAILED
     }
     return nil
    }

    从代码的实现中可以看出:在一个函数中,当创建新资源失败时,则要清理所有前面已经创建成功的资源,这使得函数中有了重复代码的坏味道,比如destroyResource1函数调用了3次,destroyResource2函数调用了2次。

    重构一:一个defer + 多个flag

    Golang提供了一个很好用的关键字defer,当包含defer的函数执行完毕时(不管是通过return的正常结束,还是由于panic导致的异常结束),defer语句才被调用。

    考虑到这一点,我们尝试将所有资源在defer语句中统一清理。由于函数返回时,不知道是否需要清理以及清理那些资源,所以要增加多个flag。

    重构后的代码如下所示:

    func deferDemo() error {
     flag := false
     flag1 := false
     flag2 := false
     flag3 := false
    
     defer func() {
     if !flag {
     if flag3 {
      destroyResource3()
     }
     if flag2 {
      destroyResource2()
     }
     if flag1 {
     destroyResource1()
     }
     }
     }()
    
     err := createResource1()
     if err != nil {
     return ERR_CREATE_RESOURCE1_FAILED
     }
     flag1 = true
    
     err = createResource2()
     if err != nil {
     return ERR_CREATE_RESOURCE2_FAILED
     }
     flag2 = true
    
     err = createResource3()
     if err != nil {
     return ERR_CREATE_RESOURCE3_FAILED
     }
     flag3 = true
    
     err = createResource4()
     if err != nil {
     return ERR_CREATE_RESOURCE4_FAILED
     }
     flag = true
     return nil
    }

    从重构后的代码可以看出,虽然消除了重复,但是引入了太多的flag:

    显然,这不是我们想要的

    重构二:多个defer

    看过linux源码的同学都知道,在内核代码中,很多地方都通过goto语句来集中处理错误,非常优雅。

    我们用这种方法将重构前的代码用C语言写一下,代码如下所示:

    ErrCode deferDemo()
    {
     ErrCode err = createResource1();
     if (err != ERR_SUCC)
     {
     goto err_1;
     }
    
     err = createResource2();
     if (err != ERR_SUCC)
     {
     goto err_2;
     }
    
     err = createResource3();
     if (err != ERR_SUCC)
     {
     goto err_3;
     }
    
     err = createResource4();
     if (err != ERR_SUCC)
     {
     goto err_4;
     }
    
     return ERR_SUCC;
    
     err_4:
     destroyResource3();
     err_3:
     destroyResource2();
     err_2:
     destroyResource1();
     err_1:
     return ERR_FAIL;
    }

    没有重复,没有flag,错误处理也很优雅,感觉很爽,那以前在C/C++编码规范中禁止使用goto语句的规则确实有点过,呵呵...

    从重构后的C代码中可以看出,create操作和destroy操作的顺序类似入栈和出栈的顺序:

    于是我们又想到了defer语句:当Golang的代码执行时,如果遇到defer语句,则压入堆栈,当函数返回时,会按照后进先出的顺序调用defer语句。

    我们看一个例子,代码如下所示:

    func main() {
     defer fmt.Println(1)
     defer fmt.Println(2)
     defer fmt.Println(3)
    }

    运行后,日志如下所示:

    3
    2
    1

    然而,有堆栈特性还不够,因为伴随着create操作,destroy操作入栈是有条件的:

    可见,destroy操作的入栈条件是create操作成功,但是destroy操作并不是一定执行,只有当某个create操作失败("err != nil")时,前面入栈的destory操作才需要执行,所以err的值也需要入栈。然而,destroy操作入栈时"err == nil" ,于是问题就变成:当err的值在后面变成非nil时,应该同步修改堆栈中的err值,即堆栈中传递的是引用或指针而不是值。

    当err的引用或指针和destroy操作都需要入栈时,defer后面必须是一个闭包调用。我们知道,对于闭包的参数是值传递,而对于外部变量却是引用传递。为了简单优雅起见,我们将err不通过参数的指针传递,而通过外部变量的引用传递。

    我们根据这个结论重构一下代码,如下所示:

    func deferDemo() error {
     err := createResource1()
     if err != nil {
     return ERR_CREATE_RESOURCE1_FAILED
     }
     defer func() {
     if err != nil {
     destroyResource1()
     }
     }()
    
     err = createResource2()
     if err != nil {
     return ERR_CREATE_RESOURCE2_FAILED
     }
     defer func() {
     if err != nil {
     destroyResource2()
     }
     }()
    
     err = createResource3()
     if err != nil {
     return ERR_CREATE_RESOURCE3_FAILED
     }
     defer func() {
     if err != nil {
     destroyResource3()
     }
     }()
    
     err = createResource4()
     if err != nil {
     return ERR_CREATE_RESOURCE4_FAILED
     }
     return nil
    }

    本次重构消除了代码的坏味道,不由的感叹一句:”升级了,我的哥!“

    总结

    本文通过巧用defer,有效且优雅的处理了错误,该技巧应该被所有的Golang程序员掌握并大量使用。

    好了,以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作能带来一定的帮助,如果有疑问大家可以留言交流,谢谢大家对脚本之家的支持。

    您可能感兴趣的文章:
    • GO语言标准错误处理机制error用法实例
    • Go语言中更优雅的错误处理
    • 详解Go多协程并发环境下的错误处理
    • Go语言中错误处理实例分析
    • Go 自定义error错误的处理方法
    • Golang中重复错误处理的优化方法
    • 一些关于Go程序错误处理的相关建议
    上一篇:Golang读写Excel的方法教程
    下一篇:Go语言判断文件或文件夹是否存在的方法
  • 相关文章
  • 

    © 2016-2020 巨人网络通讯 版权所有

    《增值电信业务经营许可证》 苏ICP备15040257号-8

    Golang巧用defer进行错误处理的方法 Golang,巧用,defer,进行,错误,