1. Go语言代码格式
代码格式指的是在语法正确的前提下,源代码的书写和组织风格。比如什么时候缩进,什么时候换行,什么时候加空格,表示块边界的花括号是跟上一行放在一起还是自己独占一行等等。这些看似无关紧要的细节其实问题颇多。
- 代码格式常常成为程序员之间争论的主题,这些争论往往消耗大量的时间却毫无成效。
- 在整合遵循不同风格编写的代码时,因格式混乱而导致代码晦涩难懂,甚至引入错误。
- 阅读与自己的习惯风格迥异的代码,对任何人来说都不是一件轻松的工作,效率降低。
在代码格式方面,Go语言采取了强硬的态度,它为代码格式设置了实用而严格的约定。这些约定虽然并非强制性的,但凭借自动化的方法,最终让全天下的Go语言程序格式统一风格一致,减少了不必要的纷争,提高了工作效率。
- 在Go语言的世界里没有人会凭借标新立异的代码风格而凌驾于格式约定之上,得不偿失。
// 糟糕的代码格式
package main;import("fmt";"strings");func main(){for i,c:=range strings.ToUpper("hello world"){if i<5{fmt.Printf("%*c%*c\n",i*2+1,c,22-(i*2+1)*2,c)}else{fmt.Printf("%*c%*c\n",22-(i*2+1),c,(i*2+1)*2-22,c)}}}
// 打印输出:
H H
E E
L L
L L
O O
W W
O O
R R
L L
D D
2. gofmt工具
为了确保按照约定设置代码格式,Go语言提供了代码格式修正工具gofmt。
- 直接对源文件使用gofmt,将格式修正的结果打印到标准输出。
- gofmt main.go
- -d选项(需要系统支持diff命令),显式格式修正前后的差分。
- gofmt -d main.go
- -w选项,用格式修正的结果覆盖原始文件
- gofmt -w main.go
对于格式修正的结果,有些程序员可能一时难以接受,但时间终会抚平一切。
借助格式修正工具,程序员们甚至无需了解有关代码格式的任何约定,仅通过耳濡目染地影响和浸润,最终自然而然地屈从于风格约定的各项要求。
// 使用gofmt格式化代码
// 为把代码调整为Go标准格式,可使用gofmt命令:gofmt -w main.go
package main
import (
"fmt"
"strings"
)
func main() {
for i, c := range strings.ToUpper("hello world") {
if i < 5 {
fmt.Printf("%*c%*c\n", i*2+1, c, 22-(i*2+1)*2, c)
} else {
fmt.Printf("%*c%*c\n", 22-(i*2+1), c, (i*2+1)*2-22, c)
}
}
}
// 打印输出:
H H
E E
L L
L L
O O
W W
O O
R R
L L
D D
3. 配置编辑器
目前主流源代码编辑器都提供支持Go语言开发的第三方插件,可在保存源代码文件的同时,自动运行gofmt进行格式化修正
- Vim - vim-go
- Emacs - go.mode.el
- Sublime - GoSublime
- Atom - go-plus
- Eclipse - goclipse
- Visual Studio Code - vscode-go
4. 命名约定
有一种夸张的说法,在计算机科学里最难的两件事是缓存和命名。
好的命名有助于改善程序代码的可读性和可维护性。
Go语言的命名约定有些是语法规则强制的,有些则是程序员们约定俗成的。
- 以大写字母开头的标识符是导出标识符,或叫公有标识符,可在包的外部访问,而小写字母开头的标识符则仅限于在包的内部访问,称为私有标识符。
- 由两个或两个以上的单词组成的名称,建议采用驼峰命名法(首字母小写,私有,非导出),如:familyName,或帕斯卡命名法(首字母大写,公有,导出),如:FamilyName,不建议使用下划线连接多个单词,如:family_name。
- 变量的声明位置距离使用位置越远,名字越长,反之越短。
Go语言的命名约定有些是语法规则强制的,有些则是程序员们约定俗成的。
- 函数和方法中的局部变量,名字尽量短小,比如用buf表示buffer,用i表示index,甚至可以用其数据类型的首字母命名,如用b表示bool,用s表示string。
- 函数和方法的参数及具名返回值,若其类型已具备可描述性,名字同样尽可能短小,如:func AfterFunc(d Duration, f func()) (t *Timer) { ... }// d时间间隔,f函数。
- 良好的函数和方法名可令其功能不言而喻,如:func (t *Triangle) Area() float64 { ... }。
- 接口名通常是在动词后面加上"er"后缀构成一个名词,表示"……者"或"……器",如:Reader(读取器)、Writer(写入器)、Parser(解析器)。
- 从包中导出的标识符,在使用时是跟在包名后面的,math.Sqrt要好过math.MathSqrt。
// 命名约定:
//
// 1. 以大写字母开头的标识符将被导出,而以小写字母开头的不会
// 2. 多单词标识符名,使用骆驼拼写法(首字母小写)或帕斯卡拼写法(首字母大写)
// 3. 尽量使用能体现数据类型的简短变量名
// 4. 函数和方法的命名要贴切,令其功能不言自明
// 5. 接口名通常采用动词加上“er”后缀的形式
// 6. 从包中被导出的标识符,其命名要尽量避免和包名重叠
package main
import (
"fmt"
"math"
)
func main() {
f := 2.
fmt.Println(math.Sqrt(f)) // 1.4142135623730951
}