Go语言编码规范与性能调优 | 青训营笔记

97 阅读4分钟

这是我参与「第五届青训营 」伴学笔记创作活动的第3天。今天主要学习了Go语言在代码格式、注释、命名规范、控制流程以及错误和异常处理等方面的编码规范以及性能调优的方法,包括一个性能调优工具pprof和一些调优准则,这篇文章旨在整理Go语言各方面的编码规范、pprof的用法以及Go语言测试工具的使用方法。

1. Go语言编码规范

写一套正确可靠、简洁清晰的代码对于团队协同开发,后续项目维护起着至关重要的作用,我们在编码的时候要时刻遵循简单性、可读性、生产力的原则,由于Go语言自带格式化工具gofmt,因此代码格式不必过多考虑,编码时值得关注的是注释、命名规范、流程的控制以及错误和异常的处理。

1.1 注释

好的注释应当解释代码的作用、如何工作、为什么这样实现以及出错的情况,尽量提供代码未表达出的上下文信息。

1.2 命名规范

  • 简洁
  • 驼峰法
  • 缩略词全大写,位于变量开头且不需要外部引用的话可以全小写,如ServerHTTP XMLHTTPRequest xmlHTTPRequest
  • 全局变量以及距离使用地方较远的变量需要携带更多的上下文信息
  • 函数名不需携带包名的上下文信息
package http
//Good
func Server(l net.Listener, handler Handler) error
//Bad
func ServerHTTP(l net.Listener, handler Handler) error
  • 用有实际意义的单词
//Good
func (c *Client) send(req *Request, deadline time.Time)
//Bad
func (c *Client) send(req *Request, t time.Time)

1.3 流程控制

  • 避免嵌套
  • 保持正常代码路径为最小缩进,既优先处理异常情况,今早返回
//Bad
func OneFunc() error {
    err := doSomething()
    if err == nil {
        err := doAnotherThing()
        if err == nil {
            return nil //正常路径,如果添加需求的话需要再嵌套一层
        }
        return nil
    }
    return err
}

//Good
func OneFunc() error {
    if err := doSomething(); err!=nil{
        return err
    }
    if err := doAnotherthing(); err!=nil{
        return err
    }
    return nil //正常路径
}

1.4 异常处理

  • 仅出现一次的简单错误使用errors.New()创建匿名变量来表示,或fmt.Errorf()进行格式化
  • fmt.Errorf()中使用%w将一个错误关联至错误链中
fmt.Errorf("reading srcfiles list: %w", err)
  • errors.Is可判断错误是否为特定类型;errors.As可获取错误链上某特定类型的错误
  • panic和recover:尽量不在业务代码中使用panic,可以在启动阶段如init() main()中使用panic,recover只能在defer中使用,如果需要更多上下文信息,可以recover后在log中记录当前的调用栈
func (t *treeFS) Open(name string) (f fs.File, err error){
    defer func(){
        if e := recover(); e != nil {
            f = nil
            err = fmt.Errorf("gitfs panic: %v\n%s", e, debug.Stack())
        }
    }()
}

2. 性能优化

2.1 一些优化建议

  • slice和map预分配内存 slice := make([]int, 0, size)
  • copy(result, origin[len(origin)-2:])代替origin[len(origin)-2:]以避免origin的内存得不到释放。
  • 对于在长字符串后追加短字符串的情况,尽量使用strings.Builderbytes.Buffer而不是直接相加,使用bulider.Grow()预分配空间可进一步提高效率。
  • 使用空结构体作为占位符,更节省资源
  • 并发编程中,对于单个变量的保护可以使用原子操作atomic

2.2 性能分析工具pprof

进行性能调优不能一味的依靠经验和直觉,需要有数据的支撑,pprof就是go原生的一个可视化和分析性能数据的工具。

**引入:**对于使用go标准库的web项目来说,使用pprof需要引入net/http/pprof包以便自动注册pprof的handler到http server,使用方法如下

import(
    "net/http"
    _ "net/http/pprof"
)
func main() {
    //启动http server
    if err := http.ListenAndServe(":6060", nil); err != nil {
	log.Fatal(err)
    }
    os.Exit(0)
}

指标: pprof可以对一个时间段的代码运行情况进行采样,并统计出CPU、Heap、Goroutine、Mutex、Block、ThreadCreate的使用情况。通常情况下,我们需要特别关注CPU、Heap、Mutex、Block相关的数据。 常用指令: 终端进行采样

go tool pprof "http://localhost:6060/debug/pprof/profile?seconds=`采样时间`"

(pprof) __

top可查看每个函数占用CPU的情况,list可根据指定的正则式查找代码行,web可在网页输出调用关系的可视化结果。

浏览器端查看各项资源占用情况:

go tool pprof -http=:`port` "http://loaclhost:`http server的端口`/debug/pprofheap"

届时可在浏览器下由localhost:port/ui/进入到总览页面,pprof提供的视图有Top、Source、FlameGraph、Graph等。

3. 测试工具

3.1 单元测试

文件名需要以_test.go结尾、函数命名格式为TestXxx(*testing.T) 、初始化的逻辑放到TestMain中。运行单元测试时需要在终端执行以下指令

go test [flags] [packages]

[flags]一般为-run,从而执行[packages]包下的全部_test.go的所有TestXxx()函数,也可指定测试函数。 若[flags]包括-race,则在运行时会检查数据冲突的情况。 若[flags]包括-cover,则会统计覆盖率。

3.2 基准测试

函数命名需为BenchmarkXxx(b *testing.B),执行命令

go test -v -bench=. xxx_test.go

基准测试下会输出函数执行的时空复杂度。