这是我参与「第五届青训营 」伴学笔记创作活动的第 3 天
高质量编程
简介
高质量:
- 边界条件考虑完备
- 异常情况处理
- 易读易维护
编程原则:
- 简单性: 消除多余的复杂性
- 可读性
- 生产力: 效率
编码规范
高质量的代码: 代码格式, 注释, 命名规范, 控制流程, 错误和异常处理
-
代码格式: 使用gofmt进行自动格式化, 使用goimports进行依赖包管理(自动增删包的引用)
-
注释:
- 解释代码作用
- 解释代码如何做的
- 解释代码实现的原因
- 解释代码什么情况会出错
-
命名规范:
-
变量: 简洁胜于冗长; 缩略词大写; 变量距离被使用的地方越远, 需要携带更多上下文信息
-
函数: 不携带包名信息; 简短
-
包(package):
小写字母组成, 不包含大写字母和下划线; 简短; 不与标准库同名.
尽量满足: 不使用常用变量名; 使用单数而不是复数; 谨慎使用缩写
-
-
控制流程:
- 避免嵌套, 保证正常流程清晰, 如去除冗余的else
- 尽量保持正常代码路径为最小缩进, 如优先处理错误情况和特殊情况
小结:
- 线性原理, 逻辑走直线
- 正常流程的代码沿着屏幕向下移动
- 提升代码可维护性
- 问题一般出现在复杂的条件语句和循环语句中
-
错误和异常处理
-
简单错误
- 仅出现一次的错误, 其他地方不需要捕获该错误
- 优先使用
errors.New创建匿名变量来表示简单错误 - 格式化使用
fmt.Errorf
-
wrap和unwrap: error嵌套另一个error, 生成一个error的跟踪链
fmt.Errorf("reading: %w", err) -
错误判定:
- 使用
errors.Is判断一个错误链上是否有指定错误 - 使用
errors.As获取错误链上指定种类的错误
- 使用
-
panic与recover
当程序产生panic即将崩溃, 可以使用recover函数获取这个panic并根据信息进行处理(类似其它语言的try-catch)
- recover只能在defer中使用
- 只在当前goroutine生效
-
性能优化建议
benchmark
性能表现需要实际数据衡量, go提供了支持基准性能测试的benchmark工具
go test -bench=. -benchmem
slice
-
预分配内存: 尽可能在使用make()初始化切片时提供容量信息
-
切片: 本质是一个数组片段的描述, 包括数组指针, 片段长度, 片段容量
- 切片操作不复制切片指向的元素
- 创建一个新的切片会复用原来切片的底层数组
- 由于底层数组在内存中有引用, 大量使用切片可能导致大内存无法释放, 可以使用copy代替切片
map
map也需要预分配内存
- 不断向map中添加元素会触发map的扩容
- 提前分配空间可以减少内存拷贝和rehash的消耗
- 根据实际需求提前预估好需要的空间
字符串操作
如字符串拼接操作, 可以使用strings.Builder或bytes.Buffer代替+
- 字符串在go中是不可变类型, 占用内存大小固定, 导致每次使用+号都会重新分配内存
strings.Builder和bytes.Buffer是字节数组, 可以动态分配内存bytes.Buffer转化为字符串时重新申请空间;strings.Builder直接将字节数组转化为字符串类型
空结构体
空结构体struct{}本身不占据内存空间, 可作为占位符使用
例如实现set, 可以考虑使用值为空结构体的map来代替
atomic包
- 锁(sync.Mutex)的实现通过操作系统, 属于系统调用
- atomic操作通过硬件实现, 效率较高
- mutex应该用来保护一段逻辑, 不仅仅是保护一个变量
- 非数值操作可以使用
atomic.Value, 能承载一个interface{}
性能调优实战
原则:
- 要依靠数据而不是猜测
- 要定位最大瓶颈而不是细枝末节
- 不要过早优化
- 不要过度优化
性能分析工具pprof
希望知道应用在什么地方耗费了多少cpu, memory. 使用pprof用于可视化和分析性能数据
cpu采样原理
- 操作系统: 每10ms向进程发送一次SIGPROF信号
- 进程: 每次接收到SIGPROF信号记录调用堆栈
- 写缓冲: 每100ms读取已经记录的调用栈并写入输出流
heap-堆内存采样原理
- 采样程序通过内存分配器在堆上分配和释放的内存, 记录分配/释放的大小和数量
- 采样率: 每分配512KB记录一次, 可修改
- 采样时间: 从程序运行开始到采样时
- 采样指标: alloc_space, alloc_objects, inuse_space, inuse_objects
Goroutine协程和ThreadCreate线程
- Goroutine: 记录所有用户发起且在运行中的goroutine的调用栈信息
- ThreadCreate: 记录程序创建的所有系统线程的信息
Block阻塞和Mutex锁
- 阻塞: 采样阻塞操作的次数和耗时, 阻塞耗时超过阈值才记录
- 锁竞争: 采样争抢锁的次数和耗时, 只记录固定比例的锁操作