Go编码规范
December 6, 2015 · View on GitHub
说明
本规范尽量参考并遵从Go语言官方建议、Go语言标准库以及社区编码规范。
动词解释:
- 不允许:表示该行为或风格在本规范中是严厉禁止的
- 不建议:表示该行为或风格在本规范中是不建议出现的,除非特殊原因,尽量不要出现
- 建议:表示本规范建议实施该行为或风格,但不是强制的
- 必须:表示该行为或风格在本规范中是强制要求的
- 尽量:表示除非特殊原因,尽可能参考该行为或风格
开发环境
- 建议采用mac os或者ubuntu进行代码编译及调试;不建议采用windows开发环境,对于windows pc的情况,建议安装虚拟机
- 不对开发ide进行强制要求,建议使用sublime编辑器+GoSublime插件
目录结构
- 所有业务相关模块都必须放在src/pandocloud.com目录内,并按照程序模块的功能进行命名
- 对于可执行程序包,必须含有main.go文件;对于库包,必须含有库名对应的.go文件,其中包含包功能说明注释以及主要对外到处接口
- 程序目录中不允许出现不相关的文件(比如废弃的代码)以及不相关的文件夹(比如XXX.bak备份文件)
代码缩进
- 所有代码必须经过go fmt进行格式化(推荐使用sublime编辑器+GoSublime插件实现自动格式化)
- 不对代码行宽度进行字符限制,但是对于过长的行建议多行排版并从第二行开始采用tab进行缩进,如:
r.Post("/users/verification",
binding.Json(actions.UserVerifyArgs{}), actions.VendorAuth, actions.ProductAuth,
actions.SendVerifyCode)
注释
- 编码阶段必须同步写好变量、函数和包注释,注释必须采用英文。
- 所有注释全部采用双斜杠,注释内容必须是完整的句子,需要以注释的内容作为开头,句号作为结尾,第一个字母和双斜杠之间空一格。
- 每个程序包必须包含一个包注释,一般在包目录同名的go源码文件或main.go下,如bytes包在bytes.go中的包注释:
// Package bytes implements functions for the manipulation of byte slices.
// It is analogous to the facilities of the strings package.
package bytes
- 程序中每一个大写的(将被导出)的名称(变量或者函数),都必须辅以一个文档注释,如:
// IndexAny interprets s as a sequence of UTF-8-encoded Unicode code points.
// It returns the byte index of the first occurrence in s of any of the Unicode
// code points in chars. It returns -1 if chars is empty or if there is no code
// point in common.
func IndexAny(s []byte, chars string) int {
if len(chars) > 0 {
var r rune
var width int
for i := 0; i < len(s); i += width {
r = rune(s[i])
if r < utf8.RuneSelf {
width = 1
} else {
r, width = utf8.DecodeRune(s[i:])
}
for _, ch := range chars {
if r == ch {
return i
}
}
}
}
return -1
}
- 程序中小写的(不会被导出)的名称(变量或者函数),建议辅以一个说明注释,如:
// explode splits s into a slice of UTF-8 sequences, one per Unicode code point (still slices of bytes),
// up to a maximum of n byte slices. Invalid UTF-8 sequences are chopped into individual bytes.
func explode(s []byte, n int) [][]byte {
if n <= 0 {
n = len(s)
}
a := make([][]byte, n)
var size int
na := 0
for len(s) > 0 {
if na+1 >= n {
a[na] = s
na++
break
}
_, size = utf8.DecodeRune(s)
a[na] = s[0:size]
s = s[size:]
na++
}
return a[0:na]
}
命名
- 建议使用短命名,Go认为文档注释比长名字更容易解释变量意义。
- 由于Go语言通过变量首字母大小写区分是否导出变量,所有首字母大小写完全由变量是否需要导出来决定。(包括结构体中的变量名与结构体名都采用相同的首字母大小写标准)
- 包名统一使用小写字母,可以包含数字,库包不允许使用下划线,可执行程序包可以使用下划线
- 接口名尽量以er结尾,如Reader, Writer, ReadWriter,不允许出现下划线
- 全局变量名、函数名必须采用驼峰命名法,如setContent(), SetContent(), MaxLength, nBytes,不允许出现下划线
- 局部变量名必须采用全小写命名法,如maxlevel, nbytes, 不允许出现下划线
- 函数参数及返回值统一采用全小写,不允许出现下划线
- 对于名称中含有缩写词的变量或者函数,缩写词必须采用全大写形式,如ServerHTTP()
- 对于接收者命名,不允许采用"me", "this" or "self"这种名称,一般采用一两个能代表接收者的名称如"c"或者"cli"表示"Client",并且保持所有成员函数接收者名称统一,如
// sendPackageToDevice retreive package from sending chan and try to send it to device.
func (dev *Device) sendPackageToDevice() {
...
}
错误处理
- 普通情况下不允许使用panic进行错误处理,必须使用error或多返回值进行错误抛出
- 错误字符串必须采用全小写,比如使用fmt.Errorf("something bad")而不是fmt.Errorf("Something bad")
- 对于返回值带error的函数,必须进程错误处理,如
// Calling Close does not close the wrapped io.Reader originally passed to NewReader.
func (z *reader) Close() error {
if z.err != nil {
return z.err
}
z.err = z.decompressor.Close()
return z.err
}
-
实现返回值有error的函数时,遇到出错尽早返回
-
必须将主线逻辑尽可能放在最外层,当进行错误处理时不允许将正常流程放在else中,如:
if err != nil {
// error handling
return // or continue, etc.
}
// normal code
x, err := f()
if err != nil {
// error handling
return
}
// use x
参数传递及返回值
- 对于int这种小数据,必须要传递值。
- 对于比较大的struct,必须传指针。
- 对于本身就是引用类型的参数,尽量直接传值,如map,string,chan,interface接口等
- 对于结构体成员函数,定义的接收者(receiver)的类型建议同上一条
- 对于函数返回值类型都不相同的情况,建议采用匿名返回值;对于返回值类型中有两个或多个相同类型的情况,建议采用命名返回值:
func (n *Node) Parent1() *Node
func (n *Node) Parent2() (*Node, error)
// Location returns f's latitude and longitude.
// Negative values mean south and west, respectively.
func (f *Foo) Location() (lat, long float64, err error)
单元测试
- 每个可复用的工具函数和工具模块必须有单元测试程序(xxx_test.go文件)
- 单元测试case必须覆盖边界值(0值和超大值), 正常值以及异常值
- 测试中出错的错误输出必须打印出输入,错误的输出以及期待的输出,如:
if got != tt.want {
t.Errorf("Foo(%q) = %d; want %d", tt.in, got, tt.want) // or Fatalf, if test can't test anything more past this point
}
- 测试用例比较多的情况下,建议从数组中读取测试输入并依次测试,避免不必要的重复代码拷贝