这是我参与「第三届青训营 -后端场」笔记创作活动的的第3篇笔记
本篇笔记对课上提到的高质量编程规范进行一个总结,对性能调优实战附上自己的实验过程
1.高质量编程
1.1 什么是质量编程?
- 各种边界条件是否考虑完备
- 异常情况处理、稳定性保证
- 易读易维护
1.2 编码规范
如何编写高质量的Go代码
- 代码格式:使用gofmt自动格式化代码
- 注释
- 命名规范
- 控制流程
- 错误和异常处理
1.2.1 注释
- 解释代码作用 ①注释公共符号、函数的功能等。
- 解释代码如何做的 ①适合注释实现的过程。
- 解释代码实现的原因 ①适合解释代码的外部因素。 ②提供额外的上下文。
- 解释代码在什么情况下会出错 ①适合解释代码的限制条件。
- 公共符号始终要注释 ①包中声明的每个公共符号:变量、常量、函数以及结构都需要添加注释。 ②任何既不明显也不简短的公共功能必须注释 ③无论长度或复杂程度如何,对库中的任何函数都必须进行注释 *不需要注释实现接口的方法
1.2.2 命名
变量命名
- 简洁>冗长
- 缩略词全大写,但当其位于变量开头且不需要导出时,使用全小写
例如使用
ServeHTTP而不是ServeHttp - 变量距离其被使用的地方越远,则需要携带越多的上下文信息
举例
在for循环内部使用 "i"比"index"更为直接明了
在函数传参时,使用带有更多信息的变量更有助于理解功能
函数命名
- 函数名不携带包名的上下文信息,因为包名和函数名总是成对出现的
- 函数名尽量简短
- 当名为"foo"的包的某个函数返回类型为"Foo"时,可以省略类型信息而不导致歧义
- 当名为"foo"的包的某个函数返回类型T时,可以在函数名中加入类型信息
举例
例如调用http包中的Server方法,代码是http.Server,带有http包名就不需要添加包信息
1.2.3 控制流程
- 避免嵌套,保持正常流程清晰 ① 如果两个分支中都包含return 语句,则可以去掉冗杂的else
- 尽量保持正常代码路径为最小缩进
//“不符合编程规范的代码” func OneFunc() error{ err :=doSomething() if err==nil{ err:=doAnotherThing() if err==nil{ return nil } return err } return err } //改进后 代码 func OneFunc() error{ if err :=doSomething;err!=nil{ return err } if err:=doAnotherThing();err!=nil{ return err } return nil }
小结:线性原理,处理逻辑尽量走直线;正常流程代码沿着屏幕向下移动;在复杂的条件语句和循环语句中容易出现故障问题(遗漏部分条件)
1.2.4 错误和异常处理
- 简单错误
- 简单的错误指的是仅出现一次的错误,且在其他地方不需要捕获该错误
- 优先使用
errors.New来创建匿名变量来直接表示简单错误 - 如果有格式化的需求,使用
fmt.Errorf
1.3 性能优化
Slice
经可能在使用make()切片时提供容量信息
字符串处理
使用"+"拼接性能最差,strings.Builder,bytes.Buffer相近,strings.Buffer更快
原因:
- 字符串在Go语言中是不可变类型,占用内存大小是固定的
- 每次使用"+"都会重新分配内存
- 内存扩容策略不需要每次拼接重新分配内存
- bytes.Buffer转化为字符串时重新申请了一块空间
- strings.Builder直接将底层的[]byte转换成了字符串类型返回
疑问:
strBuilder程序执行内存分配数目大于ByteBuffer?
空结构体
atomic包
1.3 小结
- 避免常见的性能陷阱可以保证大部分程序的性能
- 普通应用代码,不要一位追求程序的性能
- 越高级的性能优化手段越容易出现问题
- 在满足正确可靠、简洁气息的质量要求的前提下提高程序性能
2 性能调优实战
2.1 性能调优原则
- 要依靠市局而不是猜测
- 要定位最大瓶颈而不是细枝末节
- 不要过早优化
- 不要过度优化
2.2 pprof
pprof是用于可视化和分析性能分析数据的工具
第一步首先在windows环境中安装 graphviz,并将其添加到环境变量,这里就不过多阐述
复制代码到本地后:终端运行
go run main.go;
2.2.1 CPU优化
在新的终端中运行go tool pprof "http://localhost:6060/debug/pprof/profile?seconds=10" ;此时,先采集10秒的数据到文件中pprof监测工具先采集10秒的数据到文件中再进行性能分析;终端输入 top 查看程序CPU使用情况
可知是
tiger.(*Tiger).Eat占用了“过多的”CPU,使用命令list Eat:
可知是
func (t *Tiger) Eat()函数中使用无意义的for 循环导致大量占用cpu的情况;
使用web可以将调用关系可视化
将其注释掉并再次运行
2.2.2 内存优化
终端再次输入 go tool pprof http://localhost:6060/debug/pprof/heap 以及top
可知是Steal函数占用内存过多,
list Steal查看细节:
可知是for循环不断给m.buffer追加1MB的内容,并设定上限1GB,推测这是程序占用高内存的原因。
将其注释后再次运行