Go代码规范(Code Review)
总结几个开发过程中经常会犯的代码规范错误
注释语句
// Request 表示运行命令的请求。
type Request struct { ...
// Encode 将 req 的 JSON 编码写入 w 。
func Encode(w io.Writer, req *Request) { ...
上下文
大部分使用上下文的函数都要将其作为第一个参数
func F(ctx context.Context, /* 其他参数 */) {}
不建议把上下文作为成员添加到结构类型中
// 不建议写法
type MyContext struct {
ctx context.Context
}
Crypto Rand
不要使用 math/rand
来生成密钥,即使是一次性密钥。在没有种子(seed)的情况下,生成器是完全可以被预测的。请使用 crypto/rand
的 Reader 作为替代
func Key() string {
buf := make([]byte, 16)
_, err := rand.Read(buf)
if err != nil {
panic(err) // 出于随机性,永远都不会发生
}
return fmt.Sprintf("%x", buf)
// or hex.EncodeToString(buf)
// or base64.StdEncoding.EncodeToString(buf)
}
func main() {
fmt.Println(Key())
}
//输出
//4799b801e7866209593cfaedb03cf8da
声明空的切片
当声明一个空 slice 时,倾向于用
var t []string
而不是
t := []string{}
前者声明了一个 nil slice 值,而后者声明了一个非 nil 但是零长度的 slice。两者在功能上等同,len 和 cap 均为零,而 nil slice 是首选的风格。
请注意,在部分场景下,首选非 nil 但零长度的切片,例如编码 JSON 对象时(nil 切片编码为 null
,而则 []string{}
可以正确编码为 JSON array []
)。
包注释
/*
Package template implements data-driven templates for generating textual
output such as HTML.
....
*/
package template
如果包很简单,包注释可以很简短。
// Package math provides basic constants and mathematical functions.
package math
包名
对包中名称的所有引用都将使用包名完成,因此可以从标识符中省略该名称。 例如,如果你正在使用 chubby
包,则不需键入 ChubbyFile
,因为客户端会将其写为 chubby.ChubbyFile
。 相反,命名为 File
的这种方式,客户端会将它写为 chubby.File
。 避免使用像 util
、 common
、 misc
、 api
、 types
和 interfaces
这样无意义的包名。
不要Panic
错误信息
即使用 fmt.Errorf("something bad")
而不要使用 fmt.Errorf("Something bad")
包的导入
避免包重命名导入,防止名称冲突;好的包名称不需要重命名。如果发生命名冲突,则更倾向于重命名最接近本地的包或特定于项目的包。
包导入按组进行组织,组与组之间有空行。标准库包始终位于第一组中。
package main
import (
"fmt"
"hash/adler32"
"os"
"appengine/foo"
"appengine/user"
"github.com/foo/bar"
"rsc.io/goversion/version"
)
包的匿名导入
即 import _ "pkg"
这种导入语句,最好只存在于main.go文件中
Dot 导入
package foo_test
import (
. "foo"
)
不要在程序中使用 import .
。它将使程序更难阅读
内联错误
不规范写法
// 查找返回键的值,如果没有键的映射,则返回空字符串。
func Lookup(key string) string
规范写法
// 查找并返回键的值,如果没有键的映射,则ok = false。
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)
缩进错误处理
要缩进错误处理逻辑,不要缩进常规代码。这样可以改进代码的可读性,读者可以快速地浏览逻辑主干。例如,不要写:
if x, err := f(); err != nil {
// 错误处理
return
} else {
// 使用变量 x
}
相反,应该这样写:
x, err := f()
if err != nil {
// 错误处理
return
}
// 使用变量 x
这样写的好处可以增加可读性
首字母缩写
名称中的单词是首字母或首字母缩略词(例如 "URL" 或 "NATO" )需要具有相同的大小写规则。例如,"URL" 应显示为 "URL" 或 "url" (如 "urlPony" 或 "URLPony" ),而不是 "Url"。举个例子:ServeHTTP 不是 ServeHttp。对于具有多个初始化 “单词” 的标识符,也应当显示为 "xmlHTTPRequest" 或 "XMLHTTPRequest"。
当 "ID" 是 "identifier" 的缩写时,此规则也适用于 "ID" ,因此请写 "appID" 而不是 "appId"。
protocol buffer 生成的代码是个例外,对人和对机器的要求不能一样,人编写的代码要比机器编写的代码保持更高的标准。
接口
总的来说,Go 的接口要包含在使用方的包里,不应该包含在实现方的包里。实现方只需要返回具体类型(通常是指针或结构体),这样一来可以将新方法添加到实现中,而不需要扩展重构。
不要在 API 的实现者端定义 "for mocking" 接口;反而是要定义公开的 API,用真实的实现进行测试。
不要先定义接口再用它。脱离真实的使用场景,我们都不能确定一个接口是否有存在的价值,更别提设计接口的方法了
建议写法,把接口和接口具体的实现类分开两个go文件
package consumer // consumer_func.go
type Thinger interface { Thing() bool }
func Foo(t Thinger) string { … }
package consumer //consumer.go
type defaultThinger struct{ … }
func (t defaultThinger) Thing() bool { … }
func NewThinger() Thinger { return defaultThinger{ … } }
不规范写法
package producer
type Thinger struct{ … }
func (t Thinger) Thing() bool { … }
func NewThinger() Thinger { return Thinger{ … } }
方法接收者命名
方法接收者的名称应该反映其身份;通常,其类型的一个或两个字母缩写就足够了(例如用 "c" 或 "cl" 表示 "client" )。不要使用通用名称,例如 "me","this" 或 "self",这是面向对象语言的典型标识符,这些标识符赋予该方法特殊的含义。
方法接收者类型
要是把握不准,就用指针。
变量名称
Go 中的变量名称尽可能短为妙,言简意赅,尤其是对于那些处于有限空间中的局部变量更是如此。例如: 用 c 而不是 lineCount
; 用 i
而不是 sliceIndex
。
基本准则:当变量首次被使用时离声明的位置越远,变量名称必须更具描述性。对于方法接收者的名称来说,一个或者两个字母就足够了。普通变量比如 loop indices
、 readers
的名称用一个字母(i
, r
)指代就可以。
当然,对于一些逻辑比较复杂的逻辑,或者非常规事物,或者全局变量则需使用更具描述性的名字。