Go 很简单,但不容易掌握
Go is simple but not easy.
简单意味着易懂,Go 语法基本上花 2 小时就能全部看完。但是要想掌握它、写好它却不容易。比如,goroutine 和 channel 该简单了吧,但是使用 channel 出错的 case 数不胜数。
之前有篇讲 Concurrency bugs 的论文《Understanding Real-World Concurrency Bugs in Go》说:尽管人们普遍认为通过 channel 来传递消息更少出错误,但是论文里研究的 bug 表明,正好相反,用 mutex 才更少出错。
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 时才需要定义。是自下而上的过程,而非相反。
net 包和 net/http 包并没有层级关系
可以认为是两个不同的包,它们仅仅是文件位置有层级关系而已。
包名要反映这个包能提供什么能力,而不是它包含了哪些内容。
函数名反映它做了什么,而不是怎么做。虽然命名一直是编程界的难题,但不断尝试好的命名也是必要的。日常的 util, common, base 这些包名其实并不好。任何对外暴露的内容:包、函数、方法、变量都应该给出说明。
nil slice 的几个特点
不分配内存。对于一个函数的返回值而言,返回 nil slice 比 emtpy slice 要更好。
在 marshal 时,nil slice 是 null,而 empty slice 是 []。因此在使用相关库函数时,要特别注意这两者的区别。
nil slice 和 empty slice 不 equal。
以下代码中前 2 个是 nil slice,后两个不是。
copy 函数拷贝的元素数量是 min(len(dst), len(min))
初始化 map 时,指定一个长度
它能给 runtime 以提示,这样后续可以减少重新分配元素的开销。并且要注意:这个长度并不是说 map 只能放这么多元素,这里面有一个公式会计算。
map 的 buckets 数只会增,不会降。所以当在流量冲击后,map 的 buckets 数扩容到了一个新高度,之后即使把元素都删除了也无济于事。内存占用还是在,因为基础的 buckets 占用的内存不会少。
不要边遍历 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) }
break 可以作用于 for, select, switch
break 只能跳出一重循环,因此要注意,break 是否跳到了你预想的地方。可以用 break with label 来解决。毕竟标准库里也这样用了:
for 循环加指针,老司机也会掉的坑
在 for range 循环里保存迭代变量的指针是一个非常容易犯的错误,Go 老手也会犯。原因是迭代变量至始至终都是同一个值,对它取地址得到的值也是相同的:
来自anson博客
2023-11-05 21:14:52