Defer: 甜蜜但非语法糖
defer简析 ¶
学习Go语言时,很快会遇到defer
关键字。例如,《Go语言之旅》这样介绍defer:
defer语句将函数的执行推迟到周围函数返回时。
延迟调用的参数会立即求值,但函数调用要等到周围函数返回才会执行。
|
|
defer提供了一种便捷的方式,确保某段代码在封闭函数返回控制给调用者之前无条件执行。通常,你会编写获取某些资源(文件描述符、互斥锁等)的函数。这类函数通常必须在返回前释放这些资源,无论执行路径如何——你的函数可能确实包含多个return语句。未能这样做很可能导致运行时问题,如资源泄漏、死锁等。
《Go语言之旅》并非对Golang的详尽介绍;它在演示defer在封闭函数正常返回时的工作方式方面做得很好,但遗漏了一些重要的微妙之处。
你对defer的理解有多深? ¶
测验时间! ¶
为了测试Gopher对defer语义的理解,我在Twitter上进行了以下投票:
函数foo和bar具有完全相同的行为/语义:
|
|
真还是假?
花点时间思考一下……你的答案会是什么?
尽管投票的响应率较低(只有16位受访者),且受访者的Golang熟练程度难以确定,但我确实发现结果很有启示性:大多数受访者(56%)回答“真”,但正确答案是“假”。
解释 ¶
函数foo和bar不具有相同的语义。具体来说,如果函数f在调用时发生panic——要么是因为它恰好为nil,要么是因为在其执行过程中发生panic——函数bar将直接panic(不打印任何内容),而函数foo将在panic之前向标准输出打印“bye”。
你可以在Go Playground上亲自尝试。在这种情况下,如果要无条件执行fmt.Println("bye")
,使用defer不是可选的,因为函数bar无法保证其调用者传递的函数参数在调用时不会panic。
得益于defer提供的防panic保证,函数foo完全避免了这个问题。
用defer还是不用defer:这是个问题 ¶
这个例子应该作为一个警示。defer容易被误解(有罪!)为仅仅是语法便利。不使用它可能导致你的Go程序出现严重问题。
当然,如果你正在编写性能关键代码(如数据库)并且你知道自己在做什么,你可能不得不在代码的某些地方避免使用defer语句,但是
总是使用defer(除非你有充分的理由不这样做)。
是一个很好的经验法则。
引用Bill Kennedy(Twitter上的goinggodotnet)的劝诫,他在视频课程中经常重复:
正确性优先于性能。
牢固掌握defer的语义很可能为你的Go程序的正确性带来巨大回报。
不要推迟阅读关于defer的细则 :)