Go Mistakes
- 变量作用域问题:变量隐藏(if 分支里使用
:=,不会覆盖外部定义的变量);
init函数的误用:不要依赖默认的顺序关系(包内按文件字母序加载) ;
- 发现抽象而不是创建抽象:接口定义放在消费端(使用的包),而不是在生产端(实现的包);
- 生产端定义接口:C++ / Java 的用法,Go 的实现不需要继承;
- 消费端定义接口:客户端可以根据需要定义最准确的抽象;
- 决策:标准库中使用生产者端接口(方法尽量少);不能证明抽象现在有效,就不要创建抽象。
- 针对客户端抽象:生产端函数返回结构体而不是接口,客户端接受接口而不是结构体;
- 不要使用 any,使用泛型;(
~Int跟Int的区别,~Int 可以接受任意底层是 Int 的结构体)
- 内嵌类型的使用:内嵌类型的所有方法,都可以直接使用;类似于组合,但是添加函数的转发;
- nil slice 和 empty slice:两者 len 方法都返回0,nil slice 不分配内存;
- map 只能扩容,不能缩容:删除元素不会导致map底层的bucket数组为空;
- 使用 reflect.DeepEquals 对复杂的数据结构判等,或者使用第三方库 go-cmp等;
- for 循环中元素是拷贝的副本,修改元素不会影响原先的数据 ;
- map 遍历不保证顺序,且每次的顺序都可能不一致;
- 打印
s[i] 不会打印第 i 个字符,它打印索引 i 处字节的 UTF-8 表示;
bytes 包提供了与 strings 包相同的操作,这有助于避免额外的字节/字符串转换。
- 使用值接收器还是指针接收器:一般用指针
- 方法需要修改接收器的值,那么应该使用 指针接收器;
- 值接收器会创建原始值的副本,在方法内部对其进行的修改 不会 影响原始值。
- nil receiver 不等于 nil interface;(类型转换时,nil 只是将接口的值为 nil,但类型不是)
- interface 类型包含两部分信息——值信息和类型信息,只有 interface的值和类型都为nil时,interface才为nil
- nil receiver 调用方法时,不会有其他语言的空指针问题;
- 在
defer 函数中,参数立即被计算:使用闭包(内部引用外部变量),或者参数使用指针;
- 错误类型的判断不要使用等号,使用
errors.As/Is 来比较错误的类型;
- 大多数情况下,一个错误应该只处理一次:在日志记录或返回错误之间做出选择;
- defer 函数中的错误,也需要进行处理;
- goroutine 的创建开销 << 计算的开销,性能才会有效果;
- 并行 goroutine 之间的同步应该通过互斥来实现,并发 goroutine 必须协调和编排;
- 在传播上下文时,理解上下文可以被取消的条件应该很重要;
- goroutine 是一种资源,就像任何其他资源一样,最终必须关闭以释放内存或其他资源;
- 在多通道中使用
select 时,如果有多个选项, 是随机选择;
- 不需要特定的值来传递一些信息时,使用空结构的通道:
chan struct{};
- 允许从 nil 通道接收消息或向 nil 通道发送消息,会永远阻塞;
- 使用 nil 通道,以某种方式从
select 语句中删除一个 case
- 大部分情况,为缓冲通道使用的默认值是它的最小值:1;确定准确的队列大小并不是一个容易的问题;
- 标准库的
sync.Mutex 不支持重入(Reentrancy)
- 字符串格式可能的副作用,例如在已经获取锁的函数里调用Format,进一步调用 String 方法导致死锁;
- 在并发应用程序中对共享切片使用 append 可能(取决于是否后扩容)会导致数据竞争;
- 使用
sync.Cond 向多个 goroutine 发送重复的通知;
- 同步一组 goroutine,并使用
errgroup 包处理错误和上下文;
sync类型不应该被复制;
- 按大小降序重新组织结构的字段可以导致更紧凑的结构(更少的内存分配和潜在的更好的空间局部性);
- 在容器中,GOMAXPROCS 不会自动设置为被限制的cpu配额,通过 uber-go/automaxprocs 自动设置;
参考文献
- 100 Go Mistakes and How to Avoid Them.