Go 的编码规范和性能调优| 青训营笔记

96 阅读4分钟

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

go高质量编程

编程原则

简单性

  • 消除多余的复杂性,以简单清晰的逻辑编写代码
  • 不理解的代码无法修复改进

可读性

  • 代码是写给人看的,而不是机器
  • 编写可维护代码的第一步是确保代码可读

生产力

  • 团队整体工作效率非常重要

编码规范

代码格式

可以使用 gofmt 和 goimports 工具自动格式化代码。对于 GoLand,可以在 Settings - Tools - Actions on Save 中打开 Reformat codeOptimize imports(不出意外的话,它们应当是默认启用的)。

注释

  • 注释应该解释代码作用
  • 注释应该解释代码如何做的
  • 注释应该解释代码实现的原因
  • 注释应该解释代码什么情况会出错
  • 公共符号始终要注释

命名规范

变量(variable)

  • 简洁胜于冗长
  • 缩略词全大写,但当其位于变量开头且不需要导出时,使用全小写
  • 变量距离其被使用的地方越远,则需要携带越多的上下文信息
  • 全局变量在其名字中需要更多的上下文信息,使得在不同地方可以轻易辨认出其含义
    例如:
for index := 0; index < len(s); index++ {
    // do sth.
}
​
// Good
for i := 0; i < len(s); i++ {
    // do sth.
}

i和index的作用范围仅限于for循环内部时,index的额外冗长几乎没有增加对程序的理解

func (c *Client) send(req *Request, deadline time.Time)
​
// Bad
func (c *Client) send(req *Request, t time.Time)

将 deadline 换成 t 降低了变量名的信息量。前者特指截止时间,而后者仅是代表某个时间,故替换不合适

函数(function)

  • 函数名不应携带包名的上下文信息,因为两者总是成对出现;
  • 函数名应当尽量简短;
  • 当名为 foo 包的某个函数返回类型为 Foo 时,可以省略类型信息而不导致歧义;
  • 当名为 foo 包的某个函数返回类型为 T 而不是 Foo 时,应该在函数名中加入类型信息。
    举例如下:
​
// Bad
func ServeHTTP(I net.Listener, handler Handler) error
​
// Good
func Serve(I net.Listener, handler Handler) error

在调用http包的Server方法时,代码是http.Server,携带有包名,故不需额外添加包名信息

包(package)

  • 只由小写字母组成。不包含大写字母和下划线等字符
  • 简短并包含一定的上下文信息。例如 schema、task 等
  • 不要与标准库同名。例如不要使用 sync 或者 strings
    总体来说,命名的大多数规范核心在于考虑上下文,好的命名能让人把关注点留在主流程上,清晰地理解程序的功能,避免频繁切换到分支细节,增加理解成本。

编码规范

控制流程

  • 避免嵌套,保持正常流程清晰
  • 如果两个分支中都包含 return 语句,则可以去除冗余的 else
  • 尽量保持正常代码路径为最小缩进,优先处理错误情况/特殊情况,并尽早返回或继续循环来减少嵌套,增加可读性 举例如下:
if foo {
    return x
} else {
    return nil
}
​
// Good
if foo {
    return x
}
return nil

错误和异常处理

  • 优先使用 errors.New 来创建匿名变量来直接表示该错误。有格式化需求时使用 fmt.Errorf
  • fmt.Errorf 中使用 %w 关键字来将一个错误 wrap 至其错误链中
  • 使用 errors.Is 可以判定错误链上的所有错误是否含有特定的错误
  • 在错误链上获取特定种类的错误,使用 errors.As
  • 当程序启动阶段发生不可逆转的错误时,可以在 init 或 main 函数中使用 panic
  • recover 只能在被 defer 的函数中使用,嵌套无法生效,只在当前 goroutine 生效

性能优化

  • 使用 Benchmark 进行基准测试
  • 在尽可能的情况下,在使用 make() 初始化切片时提供容量信息,特别是在追加切片时,即尽可能为 slice 和 map 预分配内存
  • 多个字符串拼接时,使用 strings.Builder 比直接使用 + 或使用 bytes.Buffer 更快
  • 当需要占位符时,可使用空结构体(struct{})代替
  • 使用 atomic 包代替锁修改变量