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

49 阅读4分钟

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

高质量编程与性能调优

高质量编程

简介

  • 什么是高质量的代码?

    --编写的代码能够达到正确可靠,简洁清晰的目标即可称为高质量代码

    • 边界条件考虑完备
    • 异常情况处理,保证稳定性
    • 简洁清晰易读易维护
  • 编程原则

    • 简单性

      消除多余的复杂性,用简单清晰的逻辑编写代码

    • 可读性

      代码是写给人看的,而不只是机器

      编写可维护代码的第一步是确保可读

    • 生产力

      团队整体工作效率

如何编写高质量的Go代码?

  • 代码格式

    • 使用gofmt/goimports统一代码格式
  • 注释

    • 注释需要做什么?

      • 解释代码的作用
      • 解释代码如何做的
      • 解释代码为什么这么做
      • 解释代码什么情况下会出错,即代码的适用条件
    • 对于所有的公共符号(变量、函数)都需要给出 注释

    • 注释要提供代码未表达的上下文信息

  • 命名规范

    • 变量:

      • 命名简洁清晰、
      • 缩略词全大写(如果在开头且非导出,使用全小写)
      • 变量距离其使用的地方越远,则命名中带的上下文信息需要越多
    • 函数名

      • 函数名不用带包名的上下文信息
      • 函数名应尽量简短
    • package

      • 只包含小写字母,不用大写字母和下划线
      • 简短并且包含一定的上下文信息
      • 不要与标准库同名
  • 控制流程

    • if/else尽量避免嵌套,保持流程清晰
    • 保持正常代码路径为最小缩进,优先处理错误/特殊情况并返回来保持正常代码为最小缩进
  • 错误&异常处理

    • 简单错误【只出现一次的错误,在其他地方不需要捕获该错误】

      • 使用errors.New创建匿名便来来表示
      • 使用fmt.Errorf来格式化
    • 错误的Wrap和Unwrap

      • 可以生成一个错误的跟踪链,包含其上下文的信息
    • 错误判断

      • 使用error.Is来判断是否是特定的错误

        • 不要使用==运算符,因为前文描述过的Wrap的情况
      • 使用error.As来转换为特定的错误

      • panic:尽量不要在业务代码中使用panic,因为不含recover的调用函数会导致程序崩溃

        • 一般在启动阶段启动失败时,在main或者init函数中使用panic
      • recover

        • 只能在defer的函数中使用
        • 只适用于当前的goroutine
        • 可以在recover后,在log中使用debug.Stack()记录程序调用栈

性能优化建议

性能优化的前提是满足正确可靠、简洁清晰等代码质量要素。并且性能优化有时候是时间与空间的tradeoff。

使用Benchmark工具

func BenchmarkFib(b *testing.B){
    for n :=0; n<b.N; n++{
        Fib(10)
    }
}

go test -bench

会打印出执行的次数(即b.N),每次执行时间,每次执行申请内存数量与次数等。

Go语言性能优化建议

Slice

  • 在make时提供切片容量以预分配内存
  • 在已有切片的基础上创建新切片时不会释放原来的底层数组

    • 在一个大切片上重新切片一个只使用了其中几个元素的切片,原有的底层数组不会被回收
    • 在这种情况下可以使用copy代替re-slice

Map

  • map也可以预分配内存来提高性能

    • 提前分配空间可以减少扩容时的拷贝和Rehash的消耗
    • 如果无法预知Map的大小则不要做额外的操作

字符串处理

  • 使用strings.Builder来拼接字符串

    • Go中的字符串是不可变类型,使用+拼接时会创建新字符串、重新分配内存
    • strings.Builder和bytes.Buffer的底层都是带扩容策略的[]byte数组
    • 使用bytes.Buffer需要重新申请空间转字符串,性能略差于strings.Builder
  • 预分配strings.Builder或者bytes.Buffer

    • 使用builder.Grow(n * len(str))来预先扩容

空结构体

空结构体不占据任何内存

  • map[int]struct{}来实现map,相比于map[int]bool要节省空间

使用原子变量代替锁

atomic.AddInt32(&i, 1)
//相比于
m.Lock()
i++
m.Unlock()
//原子变量的性能要高一些
  • 一般来说sync.Mutex应该用在保护一段代码逻辑的地方,而不是单纯地保护一个变量

性能优化分析

性能优化原则

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

性能分析工具pprof

pprof简介

功能上可以分为四大部分

  • 工具-Tool

    • runtime/pprof
    • net/http/pprof
  • 采样- Sample

    • CPU
    • 堆内存
    • Goroutine
    • 锁-Mutex
    • 阻塞
    • 线程创建
  • 分析-Profile

    • 网页
    • 可视化终端
  • 展示-View

    • Top
    • 调用图
    • 火焰图
    • Peek
    • 源码&反汇编

pprof排查实战

实践项目