《The Way to Go》Chapter16 常见陷阱与错误
Chapter16 常见陷阱与错误
前15章提及了一些错误使用方式,就不再赘述,https://github.com/unknwon/the-way-to-go_ZH_CN/blob/master/eBook/16.0.md 中也都列出了,重要的是有以下最佳实践:
- 使用正确的方式初始化值是切片slice的一个映射map
- 使用逗号ok模式进行类型断言
- 使用一个工厂函数创建并实例化自己的定义的类型
- 只有当想要改变结构体时,使用结构体指针作为方法接收者,否则都使用结构体值类型
误用短声明导致变量覆盖
如果在语句块内使用了短声明:=
,那么语句块内的同名变量仅在语句块内有效,退出语句块后仍然是外部的变量生效
var remember bool = false
if something {
remember := true //错误
}
// 使用remember,得到的仍然是false
// ------
// 正确做法
if something {
remember = true
}
误用字符串
字符串是不可变的,使用 a+=b
会导致大量内存开销和拷贝,考虑使用字符数组代替字符串,将字符串内容写入到缓存中,即
bytes.Buffer
错误地使用defer
defer
仅在函数返回时执行,在循环的结尾或者有限范围的代码内不会执行
何时使用new()和make()
重复内容:
- 对切片slice、映射map、通道channel使用make()
- 对数组、结构体和值类型,使用new()
不要对接口使用指针类型,减少对值类型使用指针
如使用接口的指针类型函数参数,这会导致编译错误 → 接口本身已是一个指针
尽管值类型的拷贝似乎是对内存的滥用,但值类型内存的栈上分配快速且开销小;如果传递值类型的指针会导致对象的创建,且移动到了堆上。
闭包与协程的使用
注意在循环中对索引值(index)使用闭包时,可能指向的是同一个变量,在闭包逻辑执行时,可能循环已经结束,得到的都是索引的最终值;可选的方法是创建局部变量(值复制),让闭包指向这些变量,或者用这些值启动协程
var values = [5]int{10, 11, 12, 13, 14}
func main() {
// ...
// 版本 B:和 A 版本类似,但是通过调用闭包作为一个协程
for ix := range values {
go func() {
fmt.Print(ix, " ")
}()
}
fmt.Println()
time.Sleep(5e9)
// 4 4 4 4 4
// 版本 C:正确的处理方式
for ix := range values {
go func(ix interface{}) {
fmt.Print(ix, " ")
}(ix)
}
fmt.Println()
time.Sleep(5e9)
// 1 0 3 4 2
}