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

91 阅读5分钟

go.jpg

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

在今天的课程中学习了Go语言的高质量编程和性能调优实战与案例分析,主要讲解了高质量编程中的编码规范和编程原则,包括如何编写可读性高、维护性好的代码,如何编写高效率的代码,以及如何编写可靠性高的代码,还有通过pprof工具来实际对案例进行调优。

一、高质量编程

高质量编码就是指编写的代码能够达到正确可靠、简洁清晰的目标

它要求各种边界条件要考虑完备;异常情况处理,稳定性等要得到保证;并且代码要易读易维护。

在这一节中主要从以下两个方面出发

  • 编程原则
  • 如何编写高质量代码(编码规范)

1. 编程原则

如课程老师所说,在千变万化的实际应用场景中,各种语言的特性和语法各不相同,但高质量编程遵循的原则是相通的。其遵循的主要原则是以下三点:

1) 简单性

简单性是指在编码过程中要消除 ' 多余的复杂性 ',要用简单而且清晰的逻辑来编写代码,因为不能被开发人员很好理解的代码是无法被修复改进的。

2) 可读性

可读性是指每一行代码都是写给开发人员看的,而不是机器。每一行代码只要在经过编译器的处理后形成机器码,那么无论你写的是什么样晦涩难懂的代码,计算机都是能读懂的,因此代码是写给人看的,一段可维护的代码一定是可读的。

3) 生产力

生产力是指在当前开发一个项目更多的是团队合作,所以团队整体的工作效率是非常重要的。

2. 编码规范

如何编写高质量的Go代码,主要从以下五个方面考虑

  • 代码格式
  • 注释
  • 命名规范
  • 控制流程
  • 错误和异常处理
1) 代码格式

一般推荐官方提供的gofmt工具来自动格式化代码,如果某些团队需要有对依赖包引用的格式等可以考虑使用官方提供的goimports工具来自动格式化代码

2) 注释

Good code has lots of comments, bad code requires lots of comments. —— Dave Thomas and Andrew Hunt

好的注释的应该主要有以下四个方面的贡献

  • 解释代码作用
  • 解释代码是如何做的
  • 解释代码实现的原因
  • 解释代码什么情况会出错
3) 命名规范

变量的命名

  • 简洁
  • 缩略词全大写,但位于变量开头且不需导出时,全小写
  • 离被使用的地方越远,就要携带越多的上下文信息

函数的命名

  • 不要携带包名的上下文信息
  • 尽量简短
  • 返回与包名相同的的类型时,省略类型信息
  • 返回与包名不相同的的类型时,加入类型信息

模块(package) 的命名

  • 小写字母,不包含大写字母和下划线等字符
  • 简短,包含上下文信息
  • 不要与标准库同名
4) 控制流程
  • 避免过多的嵌套
// Bad
if foo {
    return x
} else {
    return nil
}

// Good
if foo {
    return x
}
return nil
  • 尽量保持正常代码路径为最小缩进
// Bad
func OneFunc() error {
    err := doSomething()
    if err == nil {
        err := doAnotherThing()
        if err == nil {
            return nil
        }
        return nil
    }
    return nil
}

// Good
func OneFunc() error {
    if err := doSomething(); err != nil {
        return err
    }
    if err := doAnotherThing(); err != nil {
        return err
    }
    return nil
}
5) 错误和异常处理
  • error提供简洁的上下文信息链,定位问题
  • panic用于真正的异常情况
  • recover生效范围(在当前协程被defer的函数中)

二、性能优化

在这一节中主要讲解了性能优化的建议、pprof的基本使用以及性能优化的案例

1. 性能优化的建议

首先要能够看懂基准测试的每一个指标的含义

看下面一段代码

func PreStrBuilder(n int, str string) string {
   var builder strings.Builder
   builder.Grow(n * len(str))
   for i := 0; i < n; i++ {
      builder.WriteString(str)
   }
   return builder.String()
}

image.png

上面终端显示的结果是对这段代码的一个压测,解释一下每一个指标的含义

  • BenchmarkPreStrBuilder-20

    • BenchmarkPreStrBuilder是被测函数名,-20表示GOMAXPROCS(CPU核心数)的值为20
  • 441789

    • 指一共执行了441789次
  • 2488 ns/op

    • 指每次执行花费了2488ns
  • 4096 B/op

    • 指每次执行申请了4096B的内存
  • 1 allocs/op

    • 指每次执行要申请1次内存

下面是性能优化的一些建议

  • slice尽量用make()初始化时预分配好所需的内存
  • 用copy代替re-slice
  • map尽量用make()初始化时预分配好所需的内存
  • strings.Builder拼接字符串的性能最好,其次byte.Buffer(),最后才是 ' + ' 运算符

2. pprof的使用

在性能优化的案例中,老师讲解了pprof的基本使用

image.png

通过 go tool pprof "http://localhost:6060/debug/pprof/profile?seconds=10" 命令可以进入pprof命令行

在pprof中可以对CPU、heap、goroutine、mutex、block、ThreadCreate等几个指标进行性能排查

如果对终端中的命令行形式觉得可读性差,那么我们可以在pprof终端中输入 ' web ' 命令来打开网页的可视化模式进行排查

注意:如果在键入web命令后出现 'Failed to execute dot. Is Graphviz installed? Error: exec: "dot": executable file not found in %PATH%' 的错误提示,那么很有可能是因为你的计算机上没有安装Graphviz或者没有把Graphviz配置到环境变量中

image.png

如图是一个对堆内存进行性能排查的一个图形式

image.png

这是source形式

image.png

这是火焰图形式

三、今日总结

通过今天课程的学习,我了解到原来一个项目可以有这么多需要进行性能优化的地方,同时一个好的编码规范与习惯也能给团队协作开发、后期性能排查带来很大的好处,今后也需要进一步的对pprof工具进行学习,理解pprof的工作原理等。