深度阅读之《100 Go Mistakes and How to Avoid Them》

《Mastering Go》和《Concurrency in Go》[1]之后,这是我精读的第 3 本 Go 主题的英文书了。全书 390+ 页,从开始读到全部读完,快 2 个月了,😓。

前不久曹大连接发了几个关于《100 mistakes》的视频,多猜他大都是看看标题,看看代码,就知道要说什么了,并且很快就跳过去,速度飞快。我开始设想的是除了读懂内容,还想练习一下英语阅读,慢就慢吧。不过,我过后也确实加快了速度,毕竟人家半小时的进度我要两周,稍微有点离谱。

简单谈一下这本书:全书“凑”了 100 个关于 Go 的错误。有些是非常经典且常见的错误,例如在 for 循环中保存迭代变量的指针、并发 append slice 等等,书中做了非常详细的讲述。另外有一些错误则见得不多,有凑数的嫌疑,例如很多错误是不知道 xxx、不懂 xxx……读来稍微有点别扭。还有一些瑕疵的地方是第 8 章关于 M 的描述是错误的……

关于书名,作者还找了几个为什么要从 mistakes 中学习的理由:我们印象最深的知识点一定是在犯错的场景下学到的。

Tell me and I forget. Teach me and I remember. Involve me and I learn.

我们最近正在组织这本书的翻译,估计明年 5 月左右能上市,不过还是建议大家读读英文版。


以下是我在读书的过程中所做的一些笔记,记下我认为今后可能会遇到的坑。

  1. Go 很简单,但不容易掌握

Go is simple but not easy.

简单意味着易懂,Go 语法基本上花 2 小时就能全部看完。但是要想掌握它、写好它却不容易。比如,goroutine 和 channel 该简单了吧,但是使用 channel 出错的 case 数不胜数。

之前有篇讲 Concurrency bugs 的论文《Understanding Real-World Concurrency Bugs in Go》[2]说:尽管人们普遍认为通过 channel 来传递消息更少出错误,但是论文里研究的 bug 表明,正好相反,用 mutex 才更少出错。

  1. The bigger the interface, the weaker the abstraction

Rob Pike说:The bigger the interface, the weaker the abstraction。当一个接口的方法越多,它的抽象能力越弱。像接口 Reader/Writer 为何很强大,因为它们就只有一个方法。

他还说:Don’t design with interfaces, discover them. 意思就是只有在实现过程中发现需要 interface 时才需要定义。是自下而上的过程,而非相反。

  1. net 包和 net/http 包并没有层级关系

可以认为是两个不同的包,它们仅仅是文件位置有层级关系而已。

  1. 包名要反映这个包能提供什么能力,而不是它包含了哪些内容。

函数名反映它做了什么,而不是怎么做。虽然命名一直是编程界的难题,但不断尝试好的命名也是必要的。日常的 util, common, base 这些包名其实并不好。任何对外暴露的内容:包、函数、方法、变量都应该给出说明。

  1. nil slice 的几个特点

不分配内存。对于一个函数的返回值而言,返回 nil slice 比 emtpy slice 要更好。

在 marshal 时,nil slice 是 null,而 empty slice 是 []。因此在使用相关库函数时,要特别注意这两者的区别。

nil slice 和 empty slice 不 equal。

以下代码中前 2 个是 nil slice,后两个不是。

2d4f3573d5f9b8d3ee06e45b1a35ce7f.png

  1. copy 函数拷贝的元素数量是 min(len(dst), len(min))

  2. 初始化 map 时,指定一个长度

它能给 runtime 以提示,这样后续可以减少重新分配元素的开销。并且要注意:这个长度并不是说 map 只能放这么多元素,这里面有一个公式会计算。

map 的 buckets 数只会增,不会降。所以当在流量冲击后,map 的 buckets 数扩容到了一个新高度,之后即使把元素都删除了也无济于事。内存占用还是在,因为基础的 buckets 占用的内存不会少。

关于这一点,之前专门写过一篇Go map 竟然也会发生内存泄漏?[3]去讲,私以为比书里讲得更详细。

  1. 不要边遍历 map 边写入 key

在遍历 map 的过程中,新写入的 key 可能被遍历出来,也可能不被遍历出来,可能会与预期的行为不符,因此不要边遍历边写入。

下面这个例子输出的结果不确定:

func main() {
 m := map[int]bool{
  0: true,
  1: false,
  2: true,
 }

 for k, v := range m {
  if v {
   m[10+k] = true
  }
 }
 
 fmt.Println(m)
}
  1. break 可以作用于 for, select, switch

break 只能跳出一重循环,因此要注意,break 是否跳到了你预想的地方。可以用 break with label 来解决。毕竟标准库里也这样用了:

8c759acadc1a0fc6dd10a28a2cdf3521.png

  1. for 循环加指针,老司机也会掉的坑

在 for range

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值