这是我参与「第五届青训营」伴学笔记创作活动的第4天。青训营的第四次课程中讲解了Go语言编码规范、性能优化、pprof性能分析三个部分。下面是我个人整理的本次课程知识点梳理笔记。
编程规范
一、注释
编写注释是编写项目过程中必不可少的一部分,不仅能够提供项目使用者调用代码时的相关信息,也能帮助开发过程中个人及合作者对代码的理解。那么,什么是编写高质量注释所需要做的呢?课程中给出了以下几点提议:
- 公共符号解释作用:当函数需要对外提供时,需要对于函数或常量等公共符号的注释,对于既不明显也不简短的公共功能,需要在函数声明之处添加代码功能的详细介绍。同时,也需要对代码的限制条件,即什么情况下会出错进行介绍。
- 解释实现过程:当代码中出现一些比较复杂的逻辑时,需要对实现的原理进行介绍,然而对于一些简单的代码可以直接体现的流程,则不应进行注释,以防止信息冗余及迭代中注释与代码不一致的情况;
- 解释代码原因:在编码或调试过程中,难免会对代码增加一些用于实现功能的语句,此时就应当对该种语句进行介绍,以防止代码阅读者的困惑。对于一些位置与发生功能较远的语句,则更需要对其进行注释;
二、命名规范
代码本身永远是用于理解代码最好的工具,因此代码的命名对于代码可理解性有很大的影响。
- 变量:变量距离其所使用的地方较远时,需要携带能辨识出其含义的上下文信息,对于全局变量则需要使其在不同的地方被辨识出含义。对于如for循环中索引的小作用域变量,则不许进行冗长的解释;
- 函数:由于包名与函数名总是成对出现,所以函数名中不应当带有包名的上下文信息,同时当函数名与其返回值类型不相同时,可以在函数名中加入返回类型信息;
- 包名:包名应当仅由小写字母组成,且携带一定的上下文信息。需要注意包名不应当与标准库或常用变量名重名;
三、控制流程
// Good
func Func() error {
if err := doSomeThing(); err != nil {
return err;
}
if err := doAnotherThing(); err != nil {
return err;
}
}
// Bad
func Func() error {
err = doSomeThing();
if err != nil {
err = doSomeThing();
if err == nil {
return nil
}
}
return err
}
在代码的控制流程中,应当使得正常的流程尽可能清晰,即当错误发生时,应当优先处理错误情况,保证正常流程的统一。
四、异常处理
- 异常打包:在返回错误时,可以使用
fmt.Errorf使得一个错误关联至整体错误链中,以方便跟踪排查问题; - 错误判定:可以通过
errors.Is和errors.As来判定错误链上是否出现某种类型的错误,以及提取对应的错误; - panic:在业务流程中,推荐不使用
panic终结程序,如果错误可以被屏蔽,建议使用error代替panic。
性能优化
在性能优化的过程中,应当结合真实的性能数据进行衡量。Go语言中提供了基准性能测试的benchmark工具,可以用于量化性能结果。在Go语言中,比较常见的几种优化性能的方法如下:
- 预分配内存:对于
slice和map结构类型,如果可以已知预计的容量,应当在初始化时定义容量信息,以避免元素扩容时的额外复杂度; - 释放大切片:对于
slice类型而言,其实现原理为使得切片引用一个底层数组,因此当在已有切片上创建切片时,不会创建新的底层数组。因此,当函数返回切片中的小切片时,可以使用copy命令创建新的小切片,使得旧的大切片可以被释放,以节约内存。 - 字符串处理:在字符串拼接过程中,使用
+的性能较差,应当使用string.Builder或bytes.Buffer进行拼接; - 空结构体:在Go语言中,空结构体不占用任何的内存空间,因此当实现
set类型时,可以使用空结构体作为map的键类型; - 原子类型数据:当业务逻辑仅需对某一个数据加锁时,应当使用更轻量的
atomic包,而不是使用加锁的方式;
pprof性能调优
在一个大型项目中,我们可以使用pprof函数查看各个函数的资源消耗量,以确定项目的性能瓶颈。通过pprof功能,可以可视化的显示项目运行过程中内存、CPU占用、阻塞操作、锁争用、内存分配、协程创建等资源消耗情况。
golang pprof 实战 | Wolfogre's Blog提供了一个pprof的基本使用样例,其编写了一个炸弹程序,并在其中隐藏了若干占用资源的子函数。
package main
import (
...
_ "net/http/pprof" // 会自动注册 handler 到 http server,方便通过 http 接口获取程序运行采样报告
...
)
func main() {
runtime.GOMAXPROCS(1) // 限制 CPU 使用数,避免过载
runtime.SetMutexProfileFraction(1) // 开启对锁调用的跟踪
runtime.SetBlockProfileRate(1) // 开启对阻塞操作的跟踪
go func() {
// 启动一个 http server,注意 pprof 相关的 handler 已经自动注册过了
if err := http.ListenAndServe(":6060", nil); err != nil {
log.Fatal(err)
}
os.Exit(0)
}()
...
// 执行炸弹程序
}
在炸弹程序中,通过调用net/http/pprof自动注册pprof handle至HTTP服务器,并在运行炸弹程序之前,使用子协程启动HTTP服务器并监听特定地址,在这里地址为localhost::6060。
在炸弹程序运行一段时间后(用于采集信号),即可访问
http://localhost:6060/debug/pprof/查看具体的资源占用情况。当然,还有另外的一些可视化方法,可以参考博客,在这里不重复讲解。