Go 高质量编程与性能调优 | 青训营笔记

88 阅读5分钟

这是我参与「第五届青训营 」伴学笔记创作活动的第 3 天

一、高质量编程

1.1 高质量编程简介

正确可靠、简介清晰:

  • 是否考虑到各种边界条件
  • 异常处理情况,稳定保证性
  • 易读可维护

1.1.1 编程原则

简单性:

  • 以简洁的逻辑写代码

可读性:

  • 需要让其他阅读代码的人易于理解
  • 编写可维护的代码

生产力:

  • 团队整体工作效率非常重要

1.2 编码规范

1.2.1 代码格式

Golang 提供了 gofmt 命令快速格式化 Golang 源代码

使用 Goland 也能做到,保存时直接格式化代码,优化导入等操作

1.2.2 注释

  • 公共符号始终需要注释

注释需要做什么

  • 解释代码作用:适用公共符号的注释
  • 解释代码如何做的:适用实现过程的注释
  • 解释代码实现的原因:解释代码的外部因素或者提供额外的上下文
  • 解释代码在什么情况下会出错:解释代码的限制条件

1.2.3 命名规范

  • 简介胜于冗长
  • 缩略词全大写
  • 使用时离定义较远时,携带更多的上下文信息

package:

  • 只由小写字母组成,不包含大写字母和下划线
  • 简短并包含一定的上下文信息
  • 尽量不予标准库同名
  • 尽量不是使用常用变量名
  • 尽量使用单数而不是复数
  • 谨慎使用缩写

1.2.3 控制流程

  • 避免嵌套,保证正常的流程清晰
  • 尽量保持正常代码路劲为最小路劲

1.2.4 错误和异常处理

简单错误

仅仅出现一次,且在其它地方不需要捕获该错误

优先使用 errors.New 创建匿名变量来直接表示简单错误

或者使用 fmt.Errorf 创建有格式的错误信息

错误 Wrap(包装) & Unwrap(解包)

通过 fmt.Errorf%w 能将一个错误包装到另一个错误中

错误判定

判定一个错误是否为特定的错误,使用 errors.Is 方法(会检查错误链路中是否包含的特定的错误)

使用 errors.As 方法会获取链路上特定类型的错误

panic

因为 Golang 的规定了 Errors is values 但是遇到更严重的情况是,使程序无法正常工作时,使用 panic 函数,如果不使用 recover 会使函数崩溃

  • 不建议在业务中使用 panic
  • 若问题可以屏蔽或者解决可以使用 panic 代替
  • 当一个程序启动阶段发生不可逆转的错误时可以在 init 函数或者 main 函数中使用 panic

recover

类似 Java 程序的 catch

  • 只能在 defer 中调用 recover 处理该方法或者该方法调用的方法产生的 panic
  • 嵌套无法使用
  • 只在当前的 goroutine 生效
  • defer 的语句是后进先出的

1.3 性能优化建议

  • 性能优化的前提是满足正确可靠、简洁清晰等质量因素
  • 性能优化是综合评估,部分情况下还需要选择合适的时间效率或者空间效率来牺牲

1.3.1 Benchmark

使用方式为 go test -bench=. -benchmark

输出的各列数据表示为:

  1. 测试的函数名-GOMAXPROCS的值(1.5 之后默认为 CPU 核数,在容器环境下可能出现问题,可以使用 go.uber.org/automaxprocs 解决)
  2. 表示一共执行的次数,b.N 的值
  3. 每次执行花费的平均时间
  4. 每次执行花费的平均内存
  5. 每次执行申请多少次内存

1.3.2 Slice(切片)

  • 尽可能使用 make 初始化时提供容量信息,减少在扩容是需要重新申请更大的内存空间在容纳新的数据,
  • copy 是对切片深 copy,而 re-slice 是浅拷贝,会引用原切片的大数组,可能导致大内存未示范

1.3.3 map

  • 同理在使用时,尽量预分配内存

    提前分配好空间,能减少内存拷贝和 Rehash 的消耗

1.3.4 字符串拼接

尽量使用 strings.Builder 来进行复杂的字符串拼接

  • 使用 + 来拼接字符串每次都会重新分配内存
  • strings.Builderbytes.Buffer 底层都是基于 byte 数组,内容扩容策略,不需要每次都拼接重新分配内容
  • strings.Builderbytes.Buffer 都提供了 Grow 来初始化需要使用的内存大小

bytes.Buffer 在转字符串时会整个重新申请一个新的内存,而 strings.Builder 直接将底层数组转为字符串返回

1.3.5 空结构体

空结构体 struct{} 不占用任何内存空间,可以在各种场景下作为占位符使用

使用 map 实现 set(Golang 没有提供),map 是无序的,使用 value 值的类型设置为 struct{},从而节省内存使用

1.3.6 atomic(原子包)

  • 锁是通过操作系统来实现的,属于系统调用
  • atomic 是通过硬件实现的,效率比使用锁高
  • 使用 sync.Mutex 应该是来保护一段逻辑,而不是保护一个字段
  • 对于非数值操作可以使用 atomic.Value

二、性能调优实战

2.1 性能调优简介

性能调优的原则:

  • 依靠数据而不是猜测
  • 要定位的最大瓶颈而不是技术末节
  • 不要或早或过度优化

2.2 性能分析工具 pprof

pprof 能可视化的分析性能数据的工具

一图导览:

image.png