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程序出现严重问题。
诚然,如果你正在编写性能关键的代码(如数据库)并且你知道自己在做什么,你可能不得不在代码的某些地方避免使用deferred语句,但是
总是使用defer(除非你有充分的理由不这样做)
是一个很好的经验法则。
引用Bill Kennedy(Twitter上的goinggodotnet)的劝诫,他在视频课程中经常重复:
正确性优先于性能。
牢固掌握defer的语义很可能为你的Go程序的正确性带来巨大的回报。
不要推迟阅读关于defer的细则 :)