点击上方蓝色“Golang来啦”关注我哟
加个“星标”,天天 15 分钟,掌握 Go 语言
via:
https://medium.com/technofunnel/understanding-golangs-defer-function-cd262e8cf54c
作者:Mayank Gupta
四哥水平有限,如有翻译或理解错误,烦请帮忙指出,感谢!
这篇文章源自 Medium,文章点赞 200+。
原文如下:
Golang 提供了函数式编程模型,为函数式编程增加了更强大的功能和灵活性,引进了其他服务端编程语言没有的新特性。其中一个新特性就是 Defer。
这篇文章会与大家探讨 Defer 关键字的一些细节,并且会分析该函数是如何给程序带来更强大的灵活性。
defer 的特性:
函数返回时会调用 defer 定义的函数;
无论函数的执行情况如何,defer 定义的函数总是会被执行;
defer 可以在函数内部的任何地方使用;
可以定义多次 defer 函数调用;
可以用来释放资源;
代码里释放资源
让我们假设一种需要释放文件资源的场景。我们需要打开并读取文件里的内容,对文件内容会做一些操作。打开文件并读取内容之后,需要关闭文件句柄。
我们可能具有执行多个任务的复杂功能,每个函数都有多个 “return” 关键字。然而函数返回时,可能需要执行一些清理操作。
清理操作可能包括:
关闭文件;
关闭数据库;
取消订阅某些事件;
函数执行报错情况下资源清理;
身为开发人员,我们需要确保函数返回时完成清理工作。某些意外情况下,程序可能没有按照正常情况返回。我们需要一种在任何情况下,都能完成清理工作的机制。
在处理非常负责的实时程序时,开发人员有可能会漏掉某些需要执行清理操作的地方和场景。
我们通过一个例子来理解下这个问题:
package main
import (
"fmt"
"os"
)
func main() {
GetFileContent()
}
func GetFileContent() string {
file, _ := os.Open("./FileContent/sampleFile.txt")
data := make([]byte, 100)
file.Read(data)
dataString := string(data)
fmt.Printf(dataString)
if(dataString == "Hello Mayank") {
file.Close()
return "Mayank";
} else {
file.Close()
return "Anshul"
}
}
上面的例子,我们试图访问文件的内容,一旦完成文件内容的读取就需要关闭文件。假设基于文件内容会有不同的操作,函数里使用了多个 return。需要确保函数返回时,文件已经关闭。所以我们需要在函数多个地方调用 file.Close()。
代码如下:
func GetFileContent() string {
file, _ := os.Open("./FileContent/sampleFile.txt")
data := make([]byte, 100)
file.Read(data)
dataString := string(data)
fmt.Printf(dataString)
if(dataString == "Mayank") {
file.Close()
return "Mayank";
} else {
file.Close()
return "Anshul"
}
}
使用 defer 关键字
针对上述场景,上面的解决办法使得程序变得更复杂了,我们来看下 defer 关键字如何解决这个问题。在任何函数之前加上 defer 关键字后,该函数将会在 return 时被执行。defer 的使用方法很简单,如下代码:
func InvokeDeferFunction() {
defer DeferFunctionCall()
fmt.Println("Defer Function Still Not Called...")
}
func DeferFunctionCall() {
fmt.Println("Defer Function is called after function call...")
}
上面的代码需要注意以下几点:
defer 关键字需要添加在 DeferFunctionCall() 函数之前(左边);
defer 定义的函数添加在执行函数的最顶部;
上面的代码输出如下:
从输出结果我们会发现,尽管 defer 定义的函数放在了执行函数的最顶部,但是当程序执行到 defer 那行代码时,defer 函数仍没有执行,该函数是执行函数返回时被执行到的。在退出 InvokeDeferFunction() 函数之前,defer 定义的函数会被执行。我们可以利用这个特性,在函数返回之前执行多个 defer 函数。
添加多个 defer 函数
在当前执行函数返回之前,我们可以执行多个 defer 函数。多个 defer 函数完成一些特殊的功能。我们试着使用多个 defer 函数扩展上面的代码。
package main
import "fmt"
func main() {
InvokeDeferFunction()
}
func InvokeDeferFunction() {
defer DeferFunctionCall()
defer OtherDeferFunctionCall()
fmt.Println("Still executing InvokeDeferFunction")
}
func DeferFunctionCall() {
fmt.Println("Defer Function Called...")
}
func OtherDeferFunctionCall() {
fmt.Println("Other Defer Function called...")
}
上面的代码,我们可以看到定义了多个 defer 函数。这些函数会被压入栈中,将会按照 LIFO(后入先出) 顺序执行。上面的代码输出如下:
通过上面的代码,我们会发现:
多个 defer 函数会被调用;
defer 函数按照后入先出的顺序执行;
处理文件资源
我们回过头来看下最开始读取文件的例子,我们使用 defer 关键字重写代码。我们接着往下看,使用 defer 之后代码会是什么样子的。
func GetFileContent() string {
file, _ := os.Open("./FileContent/sampleFile.txt")
defer file.Close()
data := make([]byte, 100)
file.Read(data)
dataString := string(data)
fmt.Printf(dataString)
if(dataString == "Hello Mayank") {
return "Mayank";
} else {
return "Anshul"
}
}
上面的代码中,使用 defer 调用了关闭文件的函数,该函数会在执行函数返回之前被调用。无论函数执行过程中发生什么意外,文件关闭函数都会被执行。
这种处理方式可以提供以下便利:
分配资源的代码与销毁或关闭资源的代码可以放在一块,例如在打开文件或创建数据库连接时,紧接着可以在下一行代码定义关闭资源的函数,这样的话,即使函数再复杂,开发人员也不会忘记这些清理工作;
每一个 defer 定义的函数完成不同的特殊功能,例如,一个 defer 函数处理文件资源,另一个 defer 函数关闭网络连接;
使用 defer 处理 Error
这可能是 defer 关键字最重要的功能,可以帮助开发人员处理 errors。可以使用下面几个关键字处理 error。
Panic
Defer
Recover
关于如何使用 defer 关键字处理错误的细节介绍,会在下一篇文章给大家安排。
推荐阅读:
5 年 Gopher 都不知道的 defer 细节,你别再掉进坑里!
如果我的文章对你有所帮助,点赞、转发都是一种支持!
给个[在看],是对四哥最大的支持