Go语言常见编码规范 | 青训营笔记

86 阅读4分钟

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

一份清晰简洁的代码有助于提高软件的质量,减少软件的潜在漏洞和安全隐患。高质量的代码不但能减少错误,还能提高开发效率、有助于团队协作。这里总结使用Go语言开发时应当注意的一些编码规范。总的来说,按照重要性排序,Go代码的编写应该遵循清晰、简单、简明、可维护和一致这几个原则。

清晰 Clarity

清晰是指代码的目的和原理对读者来说是清晰的。代码可读性的核心目标是生产对读者清晰的代码,为了达到这一目标,需要有效的命名、有用的注释和有效的代码组织。

一个具有高可读性的代码应当是可以相对直接地看到代码在做什么。因此,可以使用更具有描述性的变量名(例如布尔型变量就命名为isXXX的形式)、添加额外的注释(说明某些不显然的功能)、用空白和注释来分隔代码(从而使代码更具层次性)、将代码重构为独立的函数(而不是直接把一堆相对独立的逻辑堆在各个地方)。

简单 Simplicity

简单是指代码以(能够达到的)最简单的方式完成其目标。对于使用、阅读和维护的人来说,一段Go代码应该是简单的。简单不仅体现在行为上,也体现在性能上。一段简单的代码应该满足下面的几个属性:

  • 从上到下都易于阅读
  • 不假设阅读者了解其工作原理
  • 不假设阅读者了解前面所有的代码
  • 没有不必要的抽象层次
  • 在平凡代码中没有引起不必要注意的命名
  • 让阅读者清楚了解值的传播情况
  • 有注释解释为什么、功能是什么(而不是代码的逻辑)
  • 有独立的文档
  • 拥有有用的测试用例

当然,在某些时候,代码的简单性和实现的API的简单性之间可能存在矛盾,例如有时代码更复杂是为了API更容易被使用,这些时候就需要进行权衡,尽量避免复杂,但也不完全抛弃复杂。

此外,如果有多种方法来实现某个功能,那么应该选择最简单的做法,在实现功能之前最好在标准库中寻找一下,如果没有满足要求的再去引入新的依赖或者自己实现。

简明 Concision

简明是指代码具有较高的信噪比,也就是不要再代码里写一些影响阅读者了解代码重要细节的东西(噪音)。可以通过好的命名和结构来提高代码的信噪比,而像重复的代码(应当抽象成独立的函数)、不相干的语法、难懂的名字、不必要的抽象等将会使代码变得难以理解。

比如,下面这段代码很常见也很简单,所以不需要额外的注释:

// Good:
if err := doSomething(); err != nil {
    // ...
}

但是在经过微小改动后,这段代码变得和常见做法不一致,这时就需要加上注释来提醒阅读者:

// Good:
if err := doSomething(); err == nil { // if NO error
    // ...
}

可维护 Maintainability

一段代码通常不会写完就不改动,而是会随着时间推移不断被迭代。因此代码的可维护性非常重要,拥有良好可维护性的代码能够降低软件迭代的开发成本,一般来说,可维护性良好的代码有下面的几个性质:

  • 易于被修改
  • 具有结构化的API,使其能够优雅地被扩展
  • 选择与问题结构相对应的抽象结构,而不是与代码结构相对应的抽象结构
  • 避免不必要的耦合,不包含未用到的功能特性
  • 拥有全面的测试代码,以确保代码的行为和重要逻辑正确

比如,下面的代码的条件语句比较长,很容易在后续代码维护中忽略细节:

// Bad:
// 这行代码中间的!很容易错过。
leap := (year%4 == 0) && (!(year%100 == 0) || (year%400 == 0))

改成下面的代码,则可以提供代码的清晰度,降低维护时出错的概率:

// Good:
// 格里高利闰年不能仅通过year%4==0来判定。
// 具体请参见https://en.wikipedia.org/wiki/Leap_year#Algorithm.
var (
    leap4   = year%4 == 0
    leap100 = year%100 == 0
    leap400 = year%400 == 0
)
leap := leap4 && (!leap100 || leap400)

一致 Consistency

代码应该和更上层的代码保持风格一致,例如,一个库/一个包的代码看上去应该是风格一致的。可以通过强制使用gofmt工具进行格式化和约定一致的命名规范来实现这一点。