Go 的 defer
不仅仅是用于清理任务,还可以用于准备任务,考虑以下示例:
func setupTeardown() func() {
fmt.Println("Run initialization")
return func() {
fmt.Println("Run cleanup")
}
}
func main() {
defer setupTeardown()() // <--------
fmt.Println("Main function called")
}
// 输出:
// Run initialization
// Main function called
// Run cleanup
这种模式的美妙之处在于,只需一行代码,你就可以完成诸如以下任务:
- 打开数据库连接,然后关闭它。
- 设置模拟环境,然后拆除它。
- 获取分布式锁,然后释放它。
- …
“嗯,这似乎很聪明,但它在现实中有什么用处呢?”
还记得追踪执行时间的技巧吗?我们也可以这样做:
func TrackTime() func() {
pre := time.Now()
return func() {
elapsed := time.Since(pre)
fmt.Println("elapsed:", elapsed)
}
}
func main() {
defer TrackTime()()
time.Sleep(500 * time.Millisecond)
}
注意!如果我连接到数据库时出现错误怎么办?
确实,像defer TrackTime()
或defer ConnectDB()
这样的模式不会妥善处理错误。这种技巧最适合用于测试或者当你愿意冒着致命错误的风险时使用,参考下面这种面向测试的方法:
func TestSomething(t *testing.T) {
defer handleDBConnection(t)()
// ...
}
func handleDBConnection(t *testing.T) func() {
conn, err := connectDB()
if err != nil {
t.Fatal(err)
}
return func() {
fmt.Println("Closing connection", conn)
}
}
这样,在测试期间可以处理数据库连接的错误。