这是我参与「第五届青训营」伴学笔记创作活动的第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工具进行格式化和约定一致的命名规范来实现这一点。