高质量编程:
什么是高质量?
实际是个偏主观的标准,个人理解的高质量代码就是编写的代码能够达到正确可靠、简洁清晰的目标可称之为高质量代码 。虽然要求不多,实际并不容易达到。
正确性:是香考虑各种边界条件,错误的调用是否能够处理
可靠性:异常情况或者错误的处理策略是否明确,依赖的服务出现异常是否能够处理
简洁:逻辑是否简单,后续调整功能或新增功能是否能够快速支持
清晰:其他人在阅读理解代码的时候是否能清楚明白,重构或者修改功能是否不会担心出现无法预料的问题
只有做到高质量编程才能编写出简单、可读性高、生产力强的代码,提高团队整体的工作效率。
如何编写高质量的Go代码
- 代码格式
1、缩进和折行:缩进直接使用 gofmt 工具格式化即可(gofmt 是使用 tab 缩进的); 折行方面,一行最长不超过120个字符,超过的请使用换行展示,尽量保持格式优雅。 我们使用Goland开发工具,可以直接使用快捷键:ctrl+alt+L,即可。
2、语句的结尾:Go语言中是不需要类似于Java需要冒号结尾,默认一行就是一条数据 如果你打算将多个语句写在同一行,它们则必须使用 ;
3、括号和空格:括号和空格方面,也可以直接使用 gofmt 工具格式化(go 会强制左大括号不换行,换行会报语法错误),所有的运算符和操作数之间要留空格。
- 注释
Go提供C风格的/* */块注释和C ++风格的//行注释。行注释是常态;块注释主要显示为包注释,但在表达式中很有用或禁用大量代码。
单行注释是最常见的注释形式,你可以在任何地方使用以 // 开头的单行注释
多行注释也叫块注释,均已以 /* 开头,并以 */ 结尾,且不可以嵌套使用,多行注释一般用于包的文档描述或注释成块的代码片段
go 语言自带的 godoc 工具可以根据注释生成文档,注释的质量决定了生成的文档的质量。每个包都应该有一个包注释,在package子句之前有一个块注释。对于多文件包,包注释只需要存在于一个文件中,任何一个都可以。包评论应该介绍包,并提供与整个包相关的信息。它将首先出现在godoc页面上,并应设置下面的详细文档。
- 命名规范
命名是代码规范中很重要的一部分,统一的命名规则有利于提高的代码的可读性,好的命名仅仅通过命名就可以获取到足够多的信息。
Go在命名时以字母a到Z或a到Z或下划线开头,后面跟着零或更多的字母、下划线和数字(0到9)。Go不允许在命名时中使用@、$和%等标点符号。Go是一种区分大小写的编程语言。因此,Manpower和manpower是两个不同的命名。
当命名(包括常量、变量、类型、函数名、结构字段等等)以一个大写字母开头。
- 流程控制
线性原理,处理逻辑尽量走直线,避免复杂的嵌套分支。
正常流程代码沿着屏幕向下移动 。
提升代码可维护性和可读性 。
故障问题大多出现在复杂的条件语句和循环语句中 。
- 错误和异常处理
错误处理的原则就是不能丢弃任何有返回err的调用,不要使用 _ 丢弃,必须全部处理。接收到错误,要么返回err,或者使用log记录下来
尽早return:一旦有错误发生,马上返回
尽量不要使用panic,除非你知道你在做什么
错误描述如果是英文必须为小写,不需要标点结尾
采用独立的错误流进行处理
error尽可能提供简明的上下文信息链,方便定位问题
panic 用于真正异常的情况
recover生效范围,在当前goroutine的被defer的函数中生效
性能优化
- benchmark
文件名以 _test.go 结尾,如 practice_test.go
函数名统一以 Benchmark 开头,参数是 *testing.B
对于要测试的函数,函数外面套上一个 for 循环,for 循环次数的上限是 b.N
为了排除其它流程的干扰,一般会在 for 循环前加上 b.ResetTimer
测试结果说明:
- slice
创建切片时可根据实际需要预分配容量,尽量避免追加过程中扩容操作,有利于提升性能
切片拷贝时需要判断实际拷贝的元素个数
谨慎使用多个切片操作同一个数组,以防读写冲突
每个切片都指向一个底层数组
每个切片都保存了当前切片的长度、底层数组可用容量
使用len()、cap()计算切片长度、容量时,时间复杂度均为O(1),不需要遍历切片
通过函数传递切片时,不会拷贝整个切片,因为切片本身只是个结构体而矣
使用 append() 向切片追加元素时有可能触发扩容,扩容后将会生成新的切片
- Map
不断向map中添加元素的操作会触发map 的扩容
提前分配好空间可以减少内存拷贝和Rehash的消耗
建议根据实际需求提前预估好需要的空间
- 字符串处理
比较strings.Builder和+:
strings.Builder 和 + 性能和内存消耗差距如此巨大,是因为两者的内存分配方式不一样。 字符串在 Go 语言中是不可变类型,占用内存大小是固定的,当使用 + 拼接 2 个字符串时,生成一个新的字符串,那么就需要开辟一段新的空间,新空间的大小是原来两个字符串的大小之和。拼接第三个字符串时,再开辟一段新空间,新空间大小是三个字符串大小之和,以此类推。
而 strings.Builder,bytes.Buffer,包括切片 []byte 的内存是以倍数申请的。例如,初始大小为 0,当第一次写入大小为 10 byte 的字符串时,则会申请大小为 16 byte 的内存(恰好大于 10 byte 的 2 的指数),第二次写入 10 byte 时,内存不够,则申请 32 byte 的内存,第三次写入内存足够,则不申请新的,以此类推。
比较strings.Builder和bytes.Buffer
strings.Builder 和 bytes.Buffer 底层都是 []byte 数组,但 strings.Builder 性能比 bytes.Buffer 略快约 10% 。一个比较重要的区别在于,bytes.Buffer 转化为字符串时重新申请了一块空间,存放生成的字符串变量,而 strings.Builder 直接将底层的 []byte 转换成了字符串类型返回了回来。
- 空结构体
使用空结构体节省内存
实现Set,可以考虑用map来代替
对于这个场景,只需要用到map的键,而不需要值 即使是将map的值设置为bool类型,也会多占据1个字节空间
- Atomic包
锁的实现是通过操作系统来实现,属于系统调用
atomic 操作是通过硬件实现,效率比锁高
sync.Mutex应该用来保护─段逻辑,不仅仅用于保护一个变量
对于非数值操作,可以使用atomic.Value,能承载一个interface {}
性能优化
- 性能分析工具pprof
分析部分-有两种方式 具体的工具-可以在runtime/pprof中找到源码,同时Golang的http标准库中也对pprof做了-些封装,能让你在http服务中直接使用它采样部分-它可以采样程序运行时的CPU、堆内存、goroutine、锁竞争、阻塞调用和系统线程的使用数据 展示-用户可以通过列表、调用图、火焰图、源码、反汇编等视图去展示采集到的性能指标。方便分析 。
- 业务服务优化
那么接下来就来看一下业务服务优化的主要流程,主要分四步,这些流程也是性能调优相对通用的流程,可以适用其他场景和上面评估代码优化效果的benchmark工具类似,对于服务的性能也需要一个评估手段和标准 优化的核心是发现服务性能的瓶颈,这里主要也是用pprof采样性能数据,分析服务的表现发现瓶颈后需要进行服务改造,重构代码,使用更高效的组件等 最后一步是优化效果验证,通过压测对比和正确性验证之后,服务可以上线进行实际收益评估整体的流程可以循环并行执行,每个优化点可能不同,可以分别评估验证。
我们性能评估要依靠数据,用实际的结果做决策 对于pprof工具,可以通过分析实际的程序熟悉相关功能,理解基本原理,后续能够更好地解决性能问题在真正的服务性能调优流程中,链路会很长,重点是要保证正确性,不影响功能,同时定位主要问题。
引用
1、Golang GC性能优化技巧 | (知乎)
2、go语言性能优化 | (bravenewgeek.com/so-you-wann…)
3、高质量编程与性能调优实战.pptx