主要参考了 Google 对外发布的编程规范:Go Code Review Comments 和内部的一些使用习惯。
两个小工具
代码注释的语句
更多参见:https://golang.org/doc/effective_go.html#commentary
尽管有时候会显得比较多余,但是代码注释最好是完整的语句,并且用句号结尾。
这样做的好处是,在自动生成 godoc 文档时,会产生更好的格式。
Contexts
如果需要 Contexts 的话,将它作为方法的第一个参数传入:
func F(ctx context.Context, /* other arguments */) {}
- 不要将 Context 作为一个结构体成员变量
- 不要自定义 Context 类型
- Context 是一个 immutable 不可变对象
复制 Copying
例如,bytes.Buffer
类型包含一个 []byte
切片。当你去复制一个 Buffer
对象时,实际最后都是指向同一个 []byte
。
定义空的切片 Declaring Empty Slices
推崇的方式 var t []string
:定义了个 nil
不推崇的方式 t := []string{}
:不是 nil
,但是长度为 0
不过对上面这两种方式定义的变量,调用 len()
和 cap()
的结果都是 0
。
加密随机数 Crypto Rand
Do not use package math/rand to generate keys, even throwaway ones. Unseeded, the generator is completely predictable. Seeded with time.Nanoseconds(), there are just a few bits of entropy. Instead, use crypto/rand's Reader, and if you need text, print to hexadecimal or base64:
不要使用 math/rand
来产生加密的 keys,因为 math/rand
的随机数算法是可预测的。
推荐使用 crypto/rand
,也可以将结果转换成 hexadecimal
或者 base64
。
示例:
import (
"fmt"
"crypto/rand"
)
func Key() string {
buf := make([]byte, 16)
_, err := rand.Read(buf)
if err != nil {
panic(err) // out of randomness, should never happen
}
return fmt.Sprintf("%x", buf)
// or hex.EncodeToString(buf)
// or base64.StdEncoding.EncodeToString(buf)
}
func main() {
fmt.Println(Key()) // 40ca8b6ff7e65501b097cc0e9aebdc2e
fmt.Println(Key()) // 5faaad1a34483977420349e980954b6f
}
文档注释 Doc Comments
Exported 导出的方法/变量需要有注释。
不要使用 Panic Don't Panic
不要使用 Panic 来进行异常的处理,例如:
import (
"os"
)
func main() {
_, err := os.Create("/tmp/file")
if err != nil {
panic(err) // 不推荐
}
}
异常语句 Error Strings
异常语句不要以大写开头,结尾也不要有标点符号。
推荐:fmt.Errorf("something bad")
不推荐:fmt.Errorf("Something bad")
什么原因呢?因为通常异常都会被捕获,最后作为日志的一部分打印出来,所以就最好不要有大写字母和标点符号。
因此对于日志语句而言,可以大写开头,也可以标点符号结尾,例如 log.Printf("Reading %s: %v", filename, err)
。
示例 Examples
在添加新的 package 时,可以添加一些示例,会自动添加到 godoc 文档中,例如:
Goroutine Lifetimes 生命周期
使用 goroutines 时候,确保他们及时退出。
处理异常 Handle Errors
不要使用 _
来忽略异常,例如 res, _ := func()
。
要检查并处理异常,例如:
res, err := func()
if err != nil {
......
}
包的导入 Imports
尽量不用重命名 package,包的引用最好进行分组,使用空白行分隔,例如:
import (
"fmt"
"hash/adler32"
"os"
"appengine/foo"
"appengine/user"
"github.com/foo/bar"
"rsc.io/goversion/version"
)
ImportBlank
Go 语言要求导入的包必须在后续中使用,否则会报错。
如果想要避免这个错误,可以在包的前面加上下划线 _
,例如 _ "net/http"
。
问题来了,如果一个包不被使用,那为什么要导入呢?
因为导入匿名包仅仅表示无法再访问其内的属性。但导入这个匿名包的时候,会进行一些初始化操作,例如 init()
,如果这个初始化操作会影响当前包,那么这个匿名导入就是有意义的。
例如:
import (
"database/sql"
_ "github.com/lib/pq" // 我们需要的是这个包里面的 init() 方法
)
Import Dot
如果不想在访问包属性的时候加上包名,则导入的时候,可以为其设置特殊的别名:点 .
,例如:
import (
. "fmt"
)
func main() {
Println() // 无需包名,直接访问Println
}
In-Band Errors
不要返回 -1
或者空指针来代表出现了异常。Go 支持多个返回值,因此将异常或者状态作为一个单独的值返回,例如:
// Lookup returns the value for key or ok=false if there is no mapping for key.
func Lookup(key string) (value string, ok bool)
这样的话,我们就可以通过如下的方式来调用该方法:
value, ok := Lookup(key)
if !ok {
return fmt.Errorf("no value for %q", key)
}
return Parse(value)
Indent Error Flow
保持缩进尽量的少。
例如下面一段代码:
if err != nil {
// error handling
} else {
// normal code
}
我们可以修改为如下的方式,从而减少 normal code 的缩进:
if err != nil {
// error handling
return // or continue, etc.
}
// normal code
Initialisms
对于缩略词,保持统一的大小写。
推荐 URL
,不推荐 Url
。
推荐 ServeHTTP
,不推荐 ServeHttp
。
接口 Interfaces
Line Length
Mixed Caps
Named Result Parameters
Naked Returns
Package Comments
Package Names
Pass Values
Receiver Names
Receiver Type
Synchronous Functions
Useful Test Failures
Variable Names
有疑问加站长微信联系(非本文作者)