首页 > 其他分享 >避免defer陷阱:拆解延迟语句,掌握正确使用方法

避免defer陷阱:拆解延迟语句,掌握正确使用方法

时间:2023-11-16 11:44:45浏览次数:31  
标签:语句 defer return 函数 匿名 拆解 延迟

基本概念

Go语言的延迟语句defer有哪些特点?通常在什么情况下使用?

Go语言的延迟语句(defer statement)具有以下特点:

  1. 延迟执行:延迟语句会在包含它的函数执行结束前执行,无论函数是正常返回还是发生异常。

  2. 后进先出:如果有多个延迟语句,它们会按照后进先出(LIFO)的顺序执行。也就是说,最后一个延迟语句会最先执行,而第一个延迟语句会最后执行。

通常情况下,延迟语句在以下情况下使用:

  1. 资源释放:延迟语句可以用于在函数返回前释放打开的文件、关闭数据库连接、释放锁等资源,以确保资源的正确释放,避免资源泄漏。

  2. 错误处理:延迟语句可以用于处理函数执行过程中可能发生的错误。通过在函数开始时设置延迟语句,在函数返回前检查错误并进行相应的处理,可以简化错误处理的逻辑。

  3. 日志记录:延迟语句可以用于在函数返回前记录日志或执行其他的调试操作,以便在函数执行过程中收集相关的信息。

延迟语句的使用可以提高代码的可读性和可维护性,同时确保资源的释放和清理操作按照逆序进行。它是Go语言中一种常用的编程技巧,用于处理资源管理和错误处理等场景。

避坑之旅

实际开发中defer的使用并不像前面介绍的这么简单,defer用不好,会陷入泥潭。

下面我从两个角度带大家避坑:

  1. 首先拆解一下延迟语句的执行,注意Go语言的return语句不是原子性的;

  2. 另外重点和大家分享一下defer语句后面使用匿名函数和非匿名函数的区别。

拆解延迟语句

避免陷入泥潭的关键是必须深刻理解下面这条语句:

return xxx

上面这条语句经过编译之后,实际上生成了三条指令:

1)返回值 =xxx。

2)调用 defer 函数。

3)空的 return。

第1和第 3 步是return语句生成的指令,也就是说return并不是一条原子指令;

第2步是 defer 定义的语句,这里可能会操作返回值,从而影响最终结果。

下面来看两个例子,试着将return 语句和 defer语句拆解到正确的顺序。

第一个例子:

func f()(r int){
  t:=5

  defer func(){
    t=t+5
    }()
    
  return t
}

拆解后:

func f()(r int){
  t:=5
  
  //1,赋值指令
  r=t

  // 2.defer 被插入到赋值与返回之间执行,这个例子中返回值r没被修改过 
  func(){
    t=t+5
    }()
    
  //3.空的 return 指令
  return
  }

这里第二步实际上并没有操作返回值r,因此,main函数中调用f()得到5。

第二个例子:

func f()(r int){
  defer func(r int){
    r=r+5
    }(r)
    
    return 1
}

拆解后:

func f() (r int) {
  //1.赋值 
  r=1
  
  //2.这里改的r是之前传进去的r,不会改变要返回的那个r值 
  func(r int) {
    r=r+5
  }(r)
  
  // 3. 空的 return 
  return
}

第二步,改变的是传值进去的r,是形参的一个复制值,不会影响实参r。因此,main函数中需要调用f()得到1。

defer匿名函数

在Go语言中,使用匿名函数作为defer的参数时,可以理解为:defer语句中的匿名函数在包裹该defer语句的函数返回后才执行。这是因为defer语句的执行时机是在包裹函数即将返回之前,但在实际返回之前。

为什么不是在return语句之前执行呢?这是因为defer语句的设计初衷是为了在函数返回之前执行一些清理操作,例如关闭文件、释放资源等。将defer语句放在return语句之后,可以确保在函数返回之前执行这些清理操作,保证函数的执行完整性和资源的正确释放。

在使用匿名函数和非匿名函数作为defer的参数时,主要区别在于对函数参数的传递和作用域的影响:

  1. 匿名函数作为defer的参数:匿名函数可以直接在defer语句中定义,可以访问外部函数的变量,并且在执行时会使用当前的变量值。这种方式可以方便地在defer语句中使用外部变量,但需要注意变量的值在执行时可能已经发生了改变。

  2. 非匿名函数作为defer的参数:非匿名函数需要先定义好,然后作为defer的参数传递。在执行时,会使用函数的当前参数值。这种方式可以在defer语句中使用已定义的函数,但需要注意函数参数的传递和作用域。

产生这种区别的原因是,匿名函数和非匿名函数在定义和作用域上的差异。匿名函数可以直接在defer语句中定义,可以访问外部函数的变量,而非匿名函数需要先定义好,然后作为参数传递。这种设计灵活性使得开发者可以根据具体的需求选择合适的方式来使用defer语句。

举例来说

当使用匿名函数作为defer的参数时,可以在defer语句中直接定义匿名函数,并访问外部变量。

以下是一个示例代码:

package main

import "fmt"

func main() {
    x := 10

    defer func() {
        fmt.Println("Deferred anonymous function:", x)
    }()

    x = 20
    fmt.Println("Before return:", x)
}

在上述示例中,匿名函数作为defer的参数,可以访问外部变量x
在函数返回之前,defer语句中的匿名函数会执行,并打印出x的值。

输出结果如下:

当使用非匿名函数作为defer的参数时,需要先定义好函数,然后将函数名作为defer的参数传递。

以下是一个示例代码:

package main

import "fmt"

func main() {
    x := 10

    defer printX(x)

    x = 20
    fmt.Println("Before return:", x)
}

func printX(x int) {
    fmt.Println("Deferred function:", x)
}

在上述示例中,printX函数作为defer的参数传递,函数定义在main函数之后。

在函数返回之前,defer语句中的printX函数会执行,并打印出传递的参数x的值。输出结果如下:

总结一下

通过以上示例,我们可以明确体现出使用匿名函数和非匿名函数作为defer的参数的区别。

匿名函数可以直接在defer语句中定义,并访问外部变量,而非匿名函数需要先定义好函数,然后将函数名作为参数传递。

通过前面带着大家拆解了defer的语句的执行,相信大家可以更好的理解了。

更多defer使用的技巧和踩坑经验,欢迎在评论区交流讨论。
欢迎加我微信:wangzhongyang1993

标签:语句,defer,return,函数,匿名,拆解,延迟
From: https://www.cnblogs.com/wangzhongyang/p/17835888.html

相关文章

  • 在 let 的块作用域中无法重复声明,也无法在声明语句之前使用(没有变量提升)。简而言之,在
    以下代码执行时不会在控制台输出错误信息的是:Aleta=decodeURIComponent('%');Bvara;a();functiona(){console.log(a);}CPromise.reject(123).finally(a=>{console.log(a);});Dvara=1;leta=2;console.log(a);正确答案:B●首先,js有变量提升和函数......
  • 软件测试|使用Python提取出语句中的人名
    简介在自然语言处理(NLP)中,提取文本中的人名是一项常见的任务。Python作为一种流行的编程语言,拥有强大的NLP库和工具,使我们能够轻松地进行这项任务。在本文中,我们将使用Python示例来演示如何提取文本中的人名。环境准备我们将使用以下Python库来执行人名提取任务:spaCy:一个流行的NLP库......
  • Django查看orm内部封装的sql语句的2种形式
    第一种:QuerySet.queryuser_queryset=models.User.objects.values_list('name','age')print(user_queryset.query)结果:SELECT`app01_user`.`name`,`app01_user`.`age`FROM`app01_user` 第二种:执行脚本时打印日志显示到终端将以下代码复制进入settings文件中......
  • pgsql 查询对应模式下的表结构语句
    selecttable_name,column_name,data_type,udt_name,table_catalog,table_schema,table_name,column_name,ordinal_position,column_defaultfrominformation_schema.columnswheretable_schema='{schema}'andtab......
  • MySQL - 一条SQL查询语句是如何执行的
    MySQL的逻辑架构图 详细解答https://www.processon.com/mindmap/655307fe6a2ff722ead8d9e4 ......
  • Golang select语句代码示例
    在Go语言中,select语句用于多路选择(multiplexing),允许在多个通信操作中选择可用的操作进行执行。select语句的语法如下:select{case<-channel1://当channel1有数据可读时执行的代码casedata:=<-channel2://当channel2有数据可读时执行的代码,同时将读取的数据存储在......
  • 202311141210——《一些修改表字段的sql语句》
    ALTERTABLEuserADDCOLUMNtelCHAR(11)AFTERwechat;#添加列ALTERtablecustomermodifycolumnpasswordvarchar(200);#修改列类型ALTERTABLEuserALTERCOLUMNstatusSETDEFAULT1;#设置默认值ALTERTABLEuserMODIFYcolumnemp_idTIMESTAMPDEFAULTNULL......
  • NineData:通过一个SQL语句构建实时数仓
    随着企业数据量呈现出爆炸式增长,跨部门、跨应用、跨平台的数据交互需求越来越频繁,传统的数据查询方式已经难以满足这些需求。同时,不同数据库系统之间的数据格式、查询语言等都存在差异,直接进行跨库查询十分困难。原生跨库查询的局限性虽然MySQL、Oracle、PostgreSQL等数据库系......
  • Django中ORM如何执行原生sql语句操作数据库
    django中的ORM提供的操作功能有限,在模型提供的查询API不能满足实际工作需要时,可以在ORM中直接执行原生sql语句。Django提供两种方法使用原生SQL进行查询:一种是使用raw()方法,进行原生SQL查询并返回模型实例;另一种是完全避开模型层,直接执行自定义的SQL语句。另外就是用extra方法。......
  • 在created等虚拟DOM没有完成挂载的钩子函数中,避免操作DOM报错可以把操作语句放在$next
    在created等虚拟DOM没有完成挂载的钩子函数中,避免操作DOM报错可以把操作语句放在$nextTick的回调函数中在组件内使用vm.$nextTick()实例方法特别方便,因为它不需要全局Vue,并且回调函数中的this将自动绑定到当前的Vue实例上需要使用$nextTick()原因是Vue是异步渲染$nextTick()返......