高质量编程
高质量代码的标准在于其可靠性、清晰性和性能无隐患。无论在何种编程语言中,都有一些通用的原则来指导我们编写高质量代码,其中包含简单性、可读性和生产力等。本节将从代码优化、注释规范、内存分配和后端性能调优等方面入手,总结如何通过优化来提升项目的质量和效率。
一、常见编码规范
1.1 代码格式与风格一致性
在 Go 语言中,使用 gofmt 可以自动格式化代码,使所有代码符合官方推荐的标准。规范的代码格式不仅提升了代码的可读性,还能降低团队协作的学习成本和 Review 成本。
1.2 注释
注释需要明确说明代码的功能、实现方式、设计原因以及可能会出错的情况。以下是一些注释编写的准则:
- 代码功能:注释代码实现的具体作用。
- 实现方式:说明代码是如何实现该功能的。
- 设计原因:解释为什么要采用该设计。
- 错误情况:描述代码在什么情况下可能出现错误。
- 公共符号:任何公共符号均应附带注释,方便他人理解和维护。
1.3 命名规范
变量名应简洁明了,并根据上下文信息决定是否添加更多信息。一般而言,包内的函数名可以省去包名,而包名应避免与标准库重名(如 sync 或 strings),这样可以避免潜在的冲突问题。
1.4 控制流程
在控制流中,应避免多层嵌套,将正常流程保持为最小缩进,优先处理错误和特殊情况并尽早返回。这样可以降低逻辑复杂度,使代码更易于阅读。
1.5 错误和异常处理
使用 panic 处理严重异常,error 提供必要的上下文信息以便问题定位,而 recover 可用于控制函数内部的异常范围。同时,为了提高系统的稳定性和可读性,应在合理的地方添加适当的错误信息。
二、性能优化建议
在确保代码的正确性和可维护性的前提下,提高程序效率是编写高质量代码的重要一环。以下是一些常见的性能优化方法。
2.1 Slice 预分配内存
在使用 make 初始化 Slice 时,应尽量提供容量信息,以减少内存拷贝。Slice 本质上是数组的引用,当追加元素后其长度超过容量时会重新分配内存。因此预先设置 Slice 的容量可以避免多次拷贝,提高性能。
// 初始化时设置容量,避免频繁分配内存
slice := make([]int, 0, 100)
for i := 0; i < 100; i++ {
slice = append(slice, i)
}
另一个陷阱:大内存得不到释放
- 在已有切片的基础上进行切片,不会创建新的底层数组。因为原来的底层数组没有发生变化,内存会一直占用,直到没有变量引用该数组
- 因此很可能出现这么一种情况,原切片由大量的元素构成,但是我们在原切片的基础上切片,虽然只使用了很小一段,但底层数组在内存中仍然占据了大量空间,得不到释放
- 推荐的做法,使用 copy 替代 re-slice
2.2 Map 预分配内存
在使用 Map 时,提前分配空间能够减少内存的拷贝和 Rehash 的开销。比如在数据量大的场景中,使用 make 提前分配合适的容量可以提升性能。
// 预分配容量,减少扩容的消耗
m := make(map[string]int, 100)
for i := 0; i < 100; i++ {
m[fmt.Sprintf("key%d", i)] = i
}
2.3 使用 strings.Builder 进行字符串拼接
在 Go 语言中,字符串是不可变类型,使用 + 拼接字符串会不断申请新内存,而 strings.Builder 则更高效,因为它的内存是以倍数增长的。
var builder strings.Builder
for i := 0; i < 100; i++ {
builder.WriteString("Hello ")
}
result := builder.String()
2.4 使用空结构体节省内存
空结构体在 Go 中不占内存,可以用于 Set 结构的占位符。例如使用 Map 实现集合时,可以用 struct{} 作为值,以减少内存占用。
// 使用空结构体实现 Set
set := make(map[string]struct{})
set["item1"] = struct{}{}
set["item2"] = struct{}{}
2.5 使用 atomic 包提高并发性能
atomic 包比 sync.Mutex 更高效,因为 atomic 通过硬件实现操作,无需系统调用。如果只需要对数值类型做简单的加减操作,atomic 是更好的选择。
var count int64
// 使用 atomic 进行并发操作
atomic.AddInt64(&count, 1)
性能调优实战
-
性能调优原则
- 要依靠数据不是猜测
- 要定位最大瓶颈而不是细枝末节
- 不要过早优化
- 不要过度优化
-
性能调优工具 pprof 实践
- 获取项目、编译并运行
git clone https://github.com/wolfogre/go-pprof-practice.git
cd go-pprof-practice
go build
./go-pprof-practice
实际分析排查过程
- 排查 CPU 问题
apt install graphviz # for ubuntu 安装图形化工具
go tool pprof http://localhost:6060/debug/pprof/profile # 打开交互式终端
输入 top 命令
我们发现
CPU占用过高是 github.com/wolfogre/go-pprof-practice/animal/felidae/tiger.(*Tiger).Eat 造成的。
然后我们使用 list 命令查看问题具体在代码的哪一个位置:
安装完图形化工具后,在交互式终端里输入 web,将产生一个 .svg 文件,并调用系统里设置的默认打开 .svg 的程序打开它。如果系统里打开.svg的默认程序是编辑器,需要更改为默认浏览器打开。通过web 页面分析,如调用关系图,火焰图,我们可以更加直观的分析程序性能瓶颈。
- 排查堆内存问题
go tool pprof http://localhost:6060/debug/pprof/heap
- 排查协程问题
go tool pprof http://localhost:6060/debug/pprof/goroutine
性能调优案例
- 业务优化
流程- 建立服务性能评估手段
- 分析性能数据,定位性能瓶颈
- 重点优化项改造
- 优化效果验证
- 基础库优化
- Go 语言优化
总结
通过本次课程的学习,我学会了高质量代码编写的核心原则、常见性能优化方法及前后端优化技巧,并通过性能调优实践,锻炼了自己的性能分析排查能力。在实际开发中,保持代码的简洁、稳定和可维护性,才能编写出高质量的代码。通过合理的性能调优,应用的运行效率也会大大提高。