7.1 高质量编程
什么是高质量——编写的代码正确可靠、简洁清晰(边界条件、异常处理、可维护性)
-
注释
包中声明的每个公共的符号,不需要注释实现接口的方法
解释代码的作用、如何做的、实现的原因、什么情况会出错
-
代码格式
建议使用gofmt自动格式化代码
-
命名规范
简洁胜于冗长
驼峰命名法
变量距离其被使用的地方越远,需要携带越多的上下文信息
包名由小写字母构成,不要与标准库同名,使用单数而不是复数
-
控制流程
避免嵌套,保持正常流程清晰
-
异常处理
Wrap和Unwrap
不建议在业务代码中使用panic,建议使用error代替panic
recover只能在被defer的函数中使用(defer后进先出)
7.2 性能优化建议
性能优化的前提是满足正确可靠、清晰简洁等质量因素
性能优化是综合评估,有时候时间效率和空间效率可能对立
针对Go语言特性,介绍Go相关的性能优化建议
Benchmark
Go语言提供了支持基准性能测试的benchmark工具
go test -bench=. -benchmem
优化建议 - Slice:
切片的本质是一个数组片段的描述,包含以下三项:
- 数组指针
array unsafe.Pointer
- 片段的长度
len
- 片段的容量
cap
(不改变内存分配情况下的最大长度)
尽可能在使用 make()
初始化切片时提供容量信息。这是因为向切片中添加的元素数量超过默认容量会触发扩容机制,扩容是一个比较耗时的操作。
切片使用陷阱:大内存未释放
-
场景:
- 原切片较大,代码在原切片基础上新建小切片。
- 原底层数组在内存中有引用,得不到释放。
这是由于 Golang 中在已有切片的基础上创建切片,不会创建新的底层数组,而是直接复用原来的。如果只是需要用到其中的一小部分,复用原来的整个数组会导致占用较大的内存空间,建议使用 copy
替代 re-slice。
优化建议 - Map:
同样的,map 也建议预分配内存来避免扩容机制的时间开销。
- 不断向 map 中添加元素会触发 map 的扩容。
- 提前分配好空间可以减少内存拷贝和 Rehash 的消耗。
- 建议根据实际需求提前预估好需要的空间。
优化建议 - 字符串处理:
和 Java 语言类似,Golang 中直接使用 +
拼接字符串是一种十分低效的方式,因为字符串是不可变类型,使用 +
每次都会重新分配内存,推荐使用 strings.Builder
或 bytes.Buffer
操作字符串(strings.Builder
效率要更高一些)。
优化建议 - 空结构体:
使用空结构体 struct{}
可以节省内存。
-
空结构体实例不占据任何的内存空间。
-
可作为各种场景下的占位符使用。
- 节省资源。
- 空结构体本身具备很强的语义,即这里不需要任何值,仅作为占位符。
比如在实际的开发中,我们经常会使用到 Set 这种数据结构,然而 Golang 本身并不支持 Set,我们可以考虑用 map 来代替。换句话说我们只用到 map 的键,而不用它的值,那么值可以用 struct{}
类型占位。
优化建议 - atomic 包:
atomic 包主要用在多线程编程,相比于加锁的方式来保证并发安全,atomic 包效率更高。
- 锁的实现是通过操作系统来实现,属于系统调用。
- atomic 操作是通过硬件实现,效率比锁高。
sync.Mutex
应该用来保护一段逻辑,不仅仅用于保护一个变量,因此成本比较大。- 对于非数值操作,可以使用
atomic.Value
,能承载一个interface{}
。