Go中的5个常见错误

181 阅读4分钟

Bug-risks是代码中的问题,会在生产中引起错误和破坏。漏洞是代码中的一个缺陷,会产生不想要的或不正确的结果。由于不良的编码实践,缺乏版本控制的最佳实践,需求的误传,以及不现实的开发时间安排,代码经常会出现bug-风险。在这篇文章中,让我们来看看到目前为止我们所看到的围棋中几个常见的bug-风险。

1.无限的递归调用

递归调用自身的函数需要有一个退出条件。否则,它将永远地递归,直到系统的内存耗尽。

这个问题可能是由常见的错误引起的,比如忘记添加退出条件。它也可能 "故意 "发生。一些语言有尾部调用优化,这使得某些无限递归的调用可以安全使用。尾部调用优化允许你避免为一个函数分配一个新的堆栈框架,因为调用函数将返回它从被调用函数得到的值。最常见的用途是尾部递归,为利用尾部调用优化而编写的递归函数可以使用恒定的堆栈空间。然而,Go并没有实现尾部调用优化,你最终会耗尽内存。然而,这个问题并不适用于催生新的goroutine。

2.赋值到nil map

在添加任何元素之前,需要用make 函数(或map literal)来初始化一个map。一个新的、空的地图值是使用内置函数make ,它以map 类型和一个可选的容量提示作为参数。

make(map[string]int)
make(map[string]int, 100)

初始容量并不约束其大小:地图的增长是为了适应其中存储的项目数量,但nil 地图除外。一个nil 地图等同于一个空地图,只是不能添加任何元素。

不好的模式:

var countedData map[string][]ChartElement

好的模式:

countedData := make(map[string][]ChartElement)

推荐阅读。Go:对nil地图中的条目进行赋值

3.方法修改了接收器

一个修改非指针接收器值的方法可能会产生不必要的后果。这是一个错误的风险,因为该方法可能会改变方法内部的接收器的值,但它不会反映在原始值中。为了传播这种变化,接收器必须是一个指针。

4.可能不需要的值被用于goroutine中

循环中的范围变量在每次迭代时都会被重复使用;因此,在循环中创建的goroutine将从上层范围指向范围变量。这样一来,goroutine就可以用一个不想要的值来使用这个变量。

在下面的例子中,在goroutine中使用的index和value的值来自外层作用域。因为goroutine是异步运行的,所以index和value的值可能(而且通常是)与预期的值不同。

mySlice := []string{"A", "B", "C"}
for index, value := range mySlice {
    go func() {
        fmt.Printf("Index: %d\n", index)
        fmt.Printf("Value: %s\n", value)
    }()
}

为了克服这个问题,必须创建一个局部作用域,就像下面的例子一样。

mySlice := []string{"A", "B", "C"}
for index, value := range mySlice {
    index := index
    value := value
    go func() {
        fmt.Printf("Index: %d\n", index)
        fmt.Printf("Value: %s\n", value)
    }()
}

另一种处理方式是将值作为args传递给goroutines。

mySlice := []string{"A", "B", "C"}
for index, value := range mySlice {
    go func(index int, value string) {
        fmt.Printf("Index: %d\n", index)
        fmt.Printf("Value: %s\n", value)
    }(index, value)
}

5.在检查可能出现的错误之前推迟Close

defer 这是Go开发者中的一个常见模式,为实现io.Closer 接口的值,Close() 方法。例如,在打开一个文件时。

f, err := os.Open("/tmp/file.md")
if err != nil {
    return err
}
defer f.Close()

但是这种模式对于可写文件来说是有害的,因为推迟一个函数的调用会忽略其返回值,而Close() 方法可能会返回错误。例如,如果你向文件写了数据,那么在你调用Close 的时候,它可能已经被缓存在内存中而没有被刷到磁盘上。这个错误应该被明确地处理。

虽然你可以完全不使用defer ,但你需要记住在他们的工作完成后关闭文件。一个更好的方法是defer 一个封装函数,就像下面的例子。

f, err := os.Open("/tmp/file.md")
if err != nil {
    return err
}

defer func() {
    closeErr := f.Close()
    if closeErr != nil {
        if err == nil {
            err = closeErr
        } else {
            log.Println("Error occured while closing the file :", closeErr)
        }
    }
}()
return err