这是我参与「第三届青训营 -后端场」笔记创作活动的的第4篇笔记
高质量编程
编写的代码能够达到正确可靠、简洁清晰的目标可称之为高质量代码
- 各种边界条件是否考虑完备
- 异常情况处理,稳定性保证
- 易读易维护
编程原则
- 简单性
- 消除“多余的复杂性”,以简单清晰的逻辑编写代码
- 不理解的代码无法修复改进(看不懂)
- 可读性
- 代码是写给人看的
- 编写可维护代码的第一步是确保代码可读
- 生产力
- 团队整体工作效率(团队看代码、加速测试等等链路的时间,提高整体开发时间)
编码规范
代码格式
使用格式化工具
- gofmt——管理码风
- goimports——管理包
注释
注释应该:
- 解释代码的作用
- 解释代码是如何做的
- 解释代码实现的原因
- 解释代码什么情况会出错
命名规范
- varible:简洁、对于不影响上下文的变量名可以省略、对于缩略词可以大写(不暴露给外部的名字开头是缩略词可以小写)、尽量不采用特定含义的名字在一般意义的变量中
- function:不携带上下文信息;函数名尽量简短;当包与返回类型一致则省略类型信息,否则加入类型信息(http.Serve(HTTP))
- package:小写字母;简短且包含上下文信息;使用单数;谨慎使用缩写;不用常用变量名做包名,防止歧义
控制流程
流程控制避免嵌套
优先处理错误/特殊情况,尽早返回或继续循环来减少嵌套
func Func() error{
err := doSomthing()
if err == nil {
err := doAnotherThing()
if err == nil{
return nil
}
return err
}
return err
}
这样的代码是不好的,嵌套判断显得非常乱且臃肿
func Func() error{
if err := doSomeThing(); err != nil {
return err
}
if err := doAnotherThing(); err != nil {
}
return nil;
}
- 线性原理,处理逻辑尽量走直线
- 避免复杂的嵌套分支
错误与异常处理
简单错误
是只出现一次的多无,且在其他地方不需要捕获该错误
优先使用errors.New来直接表示简单错误,格式化需求用fmt.Errorf
错误的wrap和unwrap
把错误打包和解包,形成错误跟踪链,携带上下文信息,更好的排查错误
错误判定
判断错误链上是否包含某个错误errors.Is
获取特定种类的错误,使用errors.As
panic
recover
性能优化
Benchmark
func BenchmarkFib(b *testing.B) {
for n := 0; n < b.N; n++ {
Fib(10)
}
}
Slice预分配内存
尽可能的在使用make()初始化切片时提供容量信息
提前分配可以有效的减少内存分配次数,可以加快时间,避免额外的分配
对于slice的切片
map预分配内存
如果有预分配的话,性能会很好
提前分配好空间可以减少内存拷贝和Rehash的消耗
字符串处理
strings.Builder
如果使用string来+拼接字符串,相当于创建新的string重新赋值,string不可变,会涉及到重新分配内存
而Builder和Buffer底层都是[]byte数组,有内存扩容策略,不需要每次拼接重新分配内存
如果还想更快,同样的Builder也有预分配的情况``

使用空结构体实现set
m := make(map[int]struct{})
m[i] = struct{}{}
先比值设为bool来说,减少内存,空结构体仅作为占位符使用
m := make(map[int]bool)
m[i] = false
使用atomic来实现多线程锁
使用这个包来实现原子的计数,同样加锁也可以
- 锁的实现是通过操作系统来实现,属于系统调用,atomic 操作是通过硬件实现的,效率比锁高很多
- sync.Mutex 应该用来保护一段逻辑,不仅仅用于保护一个变量(使用atomic)
- 对于非数值系列,可以使用 atomic.Value,atomic.Value 能承载一个 interface{}
实战
原则:
- 靠数据而不是猜测
- 定位最大瓶颈而不是细枝末节
- 不要过早优化
- 不要过度优化
pprof
- pprof 是用于可视化和分析性能分析数据的工具
- 可以知道应用在什么地方耗费了多少 CPU、memory 等运行指标
使用浏览器查看指标
http://localhost:6060/debug/pprof/
CPU
将采集十秒的数据,汇总到文件输出
go tool pprof "http://localhost:6060/debug/pprof/profile?seconds=10"
接着输入top命令,来查看占用资源最多的函数
- flat 当前函数本身的执行耗时
- flat% flat占cpu总时间的比例
- sum% 上面每一行的flat%总和
- cum 当前函数本身加上其调用函数的总耗时
- cum% cum站cpu总时间的比例
我们发现go-pprof-practice/animal/felidae/tiger.(*Tiger).Eat这个函数是最占用的
- 函数中没调用其他函数
- 函数中只有其他函数的调用
list命令,根据指定的正则表达式查找代码行
web命令,调用关系可视化
go tool pprof -http=:8080 "http://localhost:6060/debug/pprof/cpu"
这个会更加可视化一点
heap堆内存
如果查看内存,我们可以用上面的命令改成heap就好了
go tool pprof -http=:8080 "http://localhost:6060/debug/pprof/heap"
网页端也可以更直观
sample展示对于不同的指标所得到的结果不一定一样
goruntine协程
go tool pprof -http=:8080 "http://localhost:6060/debug/pprof/goroutine"
火焰图详解
从上到下表示调用的顺序
每一个小块代表一个函数,长度代表占用cpu的时间
锁
也是类似的
go tool pprof -http=:8080 "http://localhost:6060/debug/pprof/mutex"
阻塞问题
go tool pprof -http=:8080 "http://localhost:6060/debug/pprof/block"
服务调优
服务:能单独部署,承载一定功能的程序
依赖:Service A 的功能实现依赖 Service B 的响应结果,称为 Service A 依赖 Service B
调用链路:能支持一个接口请求的相关服务集合及其相互之间的依赖关系
基础库:公共的工具包、中间件
业务服务优化
当修改落实后一般不要立即上传,需要先保证修改的正确性,可以通过录制修改前的数据来对新程序的回放来比较结果,结果一致才能上传(差的不多)
基础库优化
go语言优化
比如更新go的sdk