Go编码规范&性能优化思路 | 青训营笔记

35 阅读6分钟

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

前言

今天的学习内容很有意义,熟悉一下Go的语法规范以及一些性能优化上的思路,毕竟Go对比Java较大的两个优势就是并发性和性能了,从我个人角度来说,这也是培养我对Go的编程思维的一个方式,因此简单分享下今日的上课笔记和心得。

知识点内容

1.编码规范

实现高质量编程——即编写的代码能够达到正确可靠、简洁清晰的目标可称之为高质量代码。

1-1.代码格式

使用gofmt(Go官方提供的工具,能自动化格式化Go代码为官方统一风格)自动格式化代码。Goland已经配置好了,会把项目中的代码自动格式化,因而我使用Goland只需要专心编码即可。

1-2.注释

原则:

注释应该解释代码的作用

注释应该解释代码是如何做的

注释应该解释代码实现的原因(提供额外上下文)

注释应该解释代码什么情况会出错

公共符号始终要注释—>

包中声明的每个公共符号:

变量、常量、函数以及结构都需要添加注释;

任何既不明显也不简短的公共功能必须予以注释;

无论长度或复杂程度如何,对库中的任何函数都必须进行注释;有一个例外->不需要注释实现接口的方法。

其实注释这里的规范,大部分语言都一致,因为注释的作用都是一样的,都是为了解释代码而出现的。

1-3.命名规范

变量命名规范

简洁;

缩略词全大写,但当其位于变量开头且不需要导出时,使用全小写;

变量距离被使用的地方越远,命名需要携带更多的上下文信息。

函数命名规范

函数名不携带包名的上下文信息,因为包名和函数名总是成对出现的;

函数名尽量简短;

当名为foo的包某个函数返回类型Foo时,可以省略类型信息而不导致歧义;

当名为foo的包某个函数返回类型T时(T并不是Foo),可以在函数名加入类型信息。

包命名规范

只由小写字母组成,不包含大写字母和下划线等字符;

简短并包含一定的上下文信息;

不要与标准库同名。

以下规则尽量满足:

不使用常用变量名为包名;

使用单数而不是复数;

谨慎地使用缩写。

1-4.控制流程

避免嵌套,保持正常流程清晰。

尽量保持正常代码路径为最小缩进,这里考虑优先处理错误和特殊情况,尽早返回或继续循环来减少嵌套。

代码示例:

//错误处理 
func test() bool {                                     ```
func test() bool {
   name := "aa"
   if len(name) != 0 {
      return true
   } 
   return false
}
//正确处理
func test() bool {
   name := "aa"
   if len(name) != 0 {
      return true
   } 
   return false
}

1-5.错误和异常处理

简单错误(仅出现一次的错误且在其他地方不需要捕获该错误)

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

如果有格式化的需求,使用fmt.Errorf()。

复杂错误

使用Wrap和Unwrap,Wrap可以将一个error嵌套到另一个error,从而生成一个error的跟踪链;

在fmt.Errorf()中使用“%w”关键字来将一个错误关联到错误链中;

使用errors.Is()可以判定一个错误是否为特定错误;

使用errors.As()在错误链上获取特定种类的错误。

panic

当程序启动阶段发生不可逆转的错误时,可以在init方法或main函数中使用,若问题可以被屏蔽或解决,建议使用error代替panic。

recover(只能在被defer的函数中使用,嵌套不生效)

defer的语句是后见先出

只在当前goroutine生效

如果需要更多上下文信息,可以让recover后在log中记录当前的调用栈

2.性能优化建议

原则:

要依靠数据不是猜测

要定位最大瓶颈而不是细枝末节

不要过早优化

不要过度优化

2-1.基准性能测试工具benchmark

测试代码时加上以下参数就可以看到当前代码的执行次数、执行时间、每次执行申请的内存大小和每次执行申请的内存次数,就可以根据这些数据分析当前代码的性能。

go test -bench=. -benchmem

2-2.slice预分配内存

尽可能在使用make()初始化切片时提供容量信息。

2-3.字符串处理

使用strings.Builder()拼接字符串。

2-3.空结构体

使用空结构体节省内存,空结构体struct{}实例不占据任何内存空间。

2-4.atomic包

在时间上,它的性能比直接加锁好很多,atomic包通过硬件实现,效率比锁高,因为锁是通过操作系统实现的。

2-5.性能分析工具pprof

用一个小Demo试用一下pprof,这里我选用web的方式查看分析结果:

package main

import (
   "log"
   "net/http"
   _ "net/http/pprof"
)

func main() {
   // 性能分析
   go func() {
      log.Println(http.ListenAndServe(":8080", nil))
   }()

   // 实际业务代码
   for {
      Add("test")
   }
}

func Add(str string) string {
   data := []byte(str)
   sData := string(data)
   var sum = 0
   for i := 0; i < 10000; i++ {
      sum += i
   }
   return sData
}

在浏览器的地址栏输入:http://localhost:8080/debug/pprof/ 就可以使用pprof提供的功能: 1.jpg 各个部分对应的功能上图已标注。 2.jpg 3.jpg

小结

通过今天的学习,小结一下:代码是最好的注释,注释应该提供代码未表达出的上下文信息;命名的核心目标是降低阅读理解代码的成本,重点考虑上下文信息;处理逻辑应尽量走直线,避免复杂的嵌套分支,实际生产中大多数故障问题出现在复杂的语句条件和循环语句中(Ps:这点真是血泪经验啊,曾经实习的时候维护的那个老项目,那一大串的if嵌套出了问题根本无从下手);error尽可能提供简明的上下文信息链,方便定位问题,而panic则用于真正异常的情况;recover在当前goroutine的被defer的函数中生效。最后一句话总结下性能优化这件事:避免常见的性能陷阱就是最好的优化,不要一味地追求性能,毕竟一个系统能稳定运行是最重要的!

参考文档

青训营学习资料