当Go程序员使用错误处理时,defer语句非常有用,这将确保特定的函数调用在发生意外情况时执行。然而,错误处理和defer语句的组合在某些情况下可能会导致不期望的结果。接下来就来详细讲解Go程序员踩过的defer坑错误处理的完整攻略。
错误处理与defer语句的组合
通过错误处理,程序员可以判断何时出现了问题,并采取相应的措施来解决这些问题。错误处理如果与defer语句紧密结合,就可以确保无论什么情况,都会执行在其错误恢复之前设置的延迟函数。然而,这种方法并不是完美无缺的。接下来将讲解两个错误处理和defer语句结合的实例。
实例一
假设我们有一个函数,它需要打开一个文件并将其读入内存中。如果文件读取失败,则将打印错误消息并返回空切片。如果文件可以正确读取,则将延迟关闭打开的文件。代码如下:
func readFile(fileName string) []byte {
file, err := os.Open(fileName)
if err != nil {
fmt.Println("error opening file:", err)
return []byte{}
}
defer file.Close()
fileInfo, _ := file.Stat()
size := fileInfo.Size()
buffer := make([]byte, size)
_, err = file.Read(buffer)
if err != nil {
fmt.Println("error reading file:", err)
return []byte{}
}
return buffer
}
此函数在错误情况下将正确地返回一个空片段,并在正常情况下关闭了文件。但是,在处理大量文件时,通常需要更复杂的错误处理和日志记录。在这种情况下,我们可以使用类似下面的函数(片段):
func (m *Map) Scan(batch *leveldb.Batch) error {
m.RLock()
defer m.RUnlock()
it := m.DB.NewIterator(nil, nil)
defer it.Release()
for it.Next() {
k := it.Key()
v := it.Value()
err := batch.Put(k, v)
if err != nil {
log.Fatalf("fail to put %v: %v", string(k), err)
}
}
return nil
}
这段代码从一个存储文件并自动重新载入关键字和基于前缀的键空间数据。它通过RWMutex来保证并发安全,Loop通过遍历迭代器并将数据插入到一个Leveldb批量上来执行。
可以看到,此函数在defer语句中使用了m.RUnlock()和it.Release()来释放RWMutex和迭代器对象。但是,log.Fatalf是一个立即退出程序的函数,这意味着从defer语句返回时将永远不会执行,这意味着锁和迭代器将一直被保留。
实例二
以下示例说明两次调用的defer块执行顺序不同,因此结果也不同。
package main
import "fmt"
func main() {
fmt.Println("start")
defer fmt.Println("defer in main1")
f()
defer fmt.Println("defer in main2")
fmt.Println("end")
}
func f() {
defer fmt.Println("defer in f1")
defer fmt.Println("defer in f2")
}
在上面的代码中,首先是 main 函数,它首先打印 "start"。然后,第一行 defer fmt.Println("defer in main1") 放入 defer 栈中,但是我们需要等到 f() 运行结束才能调用该 defer 语句。接着调用函数 f()。 f() 函数中有两个 defer 语句(defer fmt.Println("defer in f1") 和 defer fmt.Println("defer in f2"))。这两个语句的顺序和放在哪里在这里并不重要,因为它们将像预期的那样工作。尽管如此,您需要知道某些情况下,defer 语句的放置位置(尤其是在for循环中)会影响执行顺序,甚至可能会导致意外的结果。此时,两个defer语句在defer栈中添加,并等待逆序完成。最后一行是第二个程度的 defer 语句 fmt.Println("defer in main2"),这将再次填充 defer 栈。
最后一行是“end”,但是在执行这个语句之前,main 函数将等待 defer 栈中的所有语句执行。同时,由于 defer 栈采用后进先出的方式执行,因此最后一个加入的语句(“defer in main2”)会先执行,然后是f()内的defer语句(f2,然后f1),并最终是 main 函数内的第一个 defer 语句(“defer in main1”)。结果输出如下:
start
defer in f2
defer in f1
defer in main2
end
defer in main1
结论
当使用defer语句时,需要注意避免嵌套函数和及时清理资源。除此之外,必须认真考虑要执行的函数可能随时终止的情况,从而编写以避免意外情况发生的代码,特别是在处理大量文件时。这些示例说明,一个小的误差就可能导致程序bug,所以在编写生产环境代码时,必须特别小心。
本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:Go程序员踩过的defer坑错误处理 - Python技术站