规范
包名
当命名包时,请按下面规则选择一个名称:
- 全部小写。没有大写或下划线。
- 大多数使用命名导入的情况下,不需要重命名。
- 简短而简洁。适当使用缩写。
- 不用复数。例如
net/url
,而不是net/urls
。 - 不要用“common”,“util”,“shared”或“lib”。这些是不好的,信息量不足的名称。
另请参阅 Package Names 和 Go 包样式指南.
对于未导出的顶层常量和变量,使用_作为前缀
在未导出的顶级vars
和consts
, 前面加上前缀_,以使它们在使用时明确表示它们是全局符号。
例外:未导出的错误值,应以err
开头。
基本依据:顶级变量和常量具有包范围作用域。使用通用名称可能很容易在其他文件中意外使用错误的值。
Bad
// foo.go
const (
defaultPort = 8080
defaultUser = "user"
)
// bar.go
func Bar() {
defaultPort := 9090
...
fmt.Println("Default port", defaultPort)
// We will not see a compile error if the first line of
// Bar() is deleted.
}
Good
// foo.go
const (
_defaultPort = 8080
_defaultUser = "user"
)
结构体中的嵌入
嵌入式类型(例如 mutex)应位于结构体内的字段列表的顶部,并且必须有一个空行将嵌入式字段与常规字段分隔开。
Bad
type Client struct {
version int
http.Client
}
Good
type Client struct {
http.Client
version int
}
内嵌应该提供切实的好处,比如以语义上合适的方式添加或增强功能。 它应该在对用户不利影响的情况下完成这项工作(另请参见:避免在公共结构中嵌入类型
Avoid Embedding Types in Public Structs)。
嵌入 不应该:
- 纯粹是为了美观或方便。
- 使外部类型更难构造或使用。
- 影响外部类型的零值。如果外部类型有一个有用的零值,则在嵌入内部类型之后应该仍然有一个有用的零值。
- 作为嵌入内部类型的副作用,从外部类型公开不相关的函数或字段。
- 公开未导出的类型。
- 影响外部类型的复制形式。
- 更改外部类型的API或类型语义。
- 嵌入内部类型的非规范形式。
- 公开外部类型的实现详细信息。
- 允许用户观察或控制类型内部。
- 通过包装的方式改变内部函数的一般行为,这种包装方式会给用户带来一些意料之外情况。
简单地说,有意识地和有目的地嵌入。
一种很好的测试体验是, “是否所有这些导出的内部方法/字段都将直接添加到外部类型” 如果答案是some
或no
,不要嵌入内部类型-而是使用字段。
Bad
type Book struct {
// Bad: 指针更改零值的有用性
io.ReadWriter
// other fields
}
// later
var b Book
b.Read(...) // panic: nil pointer
b.String() // panic: nil pointer
b.Write(...) // panic: nil pointer
// 左边嵌入的 io.ReadWriter ,他的零值是没意义的,
// 因为他是 interface,没有具体的 Read 方法实现
// 如果我们嵌入一个东西,但嵌入后还得自己去实现它的方法才能用,那么这个嵌入其实是没有任何意义的
type Client struct {
sync.Mutex
sync.WaitGroup
bytes.Buffer
url.URL
}
Good
type Book struct {
// Good: 有用的零值
bytes.Buffer
// other fields
}
// later
var b Book
b.Read(...) // ok
b.String() // ok
b.Write(...) // ok
// 右边嵌入的那个,他的零值是有意义的,
// 因为他是个 struct,并且实现了Read方法
// 如果我们嵌入一个东西,但嵌入后还得自己去实现它的方法才能用,那么这个嵌入其实是没有任何意义的
type Client struct {
mtx sync.Mutex
wg sync.WaitGroup
buf bytes.Buffer
url url.URL
}
使用原始字符串字面值,避免转义
Go 支持使用 原始字符串字面值,也就是 " ` " 来表示原生字符串,在需要转义的场景下,我们应该尽量使用这种方案来替换。
可以跨越多行并包含引号。使用这些字符串可以避免更难阅读的手工转义的字符串。
Bad
wantError := "unknown name:\"test\""
Good
wantError := `unknown error:"test"`
Printf 相关
go vet
可以检查 Printf
相关函数的类型是否对应,相见:go-vet-printf-family-check
字符串 string format
如果你在函数外声明Printf
-style 函数的格式字符串,请将其设置为const
常量。
这有助于go vet
对格式字符串执行静态分析(go vet 会分析)。
Bad
msg := "unexpected values %v, %v\n"
fmt.Printf(msg, 1, 2)
Good
const msg = "unexpected values %v, %v\n"
fmt.Printf(msg, 1, 2)
命名 Printf 样式的函数
声明Printf-style
函数时,请确保go vet
可以检测到它并检查格式字符串。
这意味着您应尽可能使用预定义的Printf-style
函数名称。go vet
将默认检查这些。有关更多信息,请参见 Printf 系列。
如果不能使用预定义的名称,请以 f 结束选择的名称:Wrapf
,而不是Wrap
。go vet
可以要求检查特定的 Printf 样式名称,但名称必须以f
结尾。
go vet -printfuncs=wrapf,statusf