结构体中的嵌入
嵌入式类型(例如mutex)应位于结构体内的字段列表的顶部,并且必须有一个空行将嵌入式字段与常规字段分隔开。
Bad
type Client struct {
version int
http.Client
}
vs.
Good
type Client struct {
http.Client
version int
}
使用字段名初始化结构体
初始化结构体时,几乎始终应该指定字段名称。现在由go vet强制执行。
Bad
k := User{“John”, “Doe”, true}
vs.
Good
k := User{
FirstName: "John",
LastName: "Doe",
Admin: true,
}
例外:如果有3个或更少的字段,则可以在测试表中省略字段名称。
tests := []struct{
}{
op Operation
want string
}{
{Add, “add”},
{Subtract, “subtract”},
}
本地变量声明
如果将变量明确设置为某个值,则应使用短变量声明形式(:=)。
Bad
var s = “foo”
vs.
Good
s := “foo”
但是,在某些情况下,var 使用关键字时默认值会更清晰。例如,声明空切片。
Bad
func f(list []int) {
filtered := []int{}
for _, v := range list {
if v > 10 {
filtered = append(filtered, v)
}
}
}
vs.
Good
func f(list []int) {
var filtered []int
for _, v := range list {
if v > 10 {
filtered = append(filtered, v)
}
}
}
nil是一个有效的slice
nil是一个有效的长度为0的slice,这意味着:
您不应明确返回长度为零的切片。返回nil 来代替。
Bad
if x == “” {
return []int{}
}
vs.
Good
if x == “” {
return nil
}
要检查切片是否为空,请始终使用len(s) == 0。不要检查 nil。
Bad
func isEmpty(s []string) bool {
return s == nil
}
vs.
Good
func isEmpty(s []string) bool {
return len(s) == 0
}
零值切片可立即使用,无需调用make创建。
Bad
nums := []int{}
// or, nums := make([]int)
if add1 {
nums = append(nums, 1)
}
if add2 {
nums = append(nums, 2)
}
vs.
Good
var nums []int
if add1 {
nums = append(nums, 1)
}
if add2 {
nums = append(nums, 2)
}
缩小变量作用域
如果有可能,尽量缩小变量作用范围。除非它与减少嵌套的规则冲突。
Bad
err := ioutil.WriteFile(name, data, 0644)
if err != nil {
return err
}
vs.
Good
if err := ioutil.WriteFile(name, data, 0644); err != nil {
return err
}
如果需要在if之外使用函数调用的结果,则不应尝试缩小范围。
Bad
if data, err := ioutil.ReadFile(name); err == nil {
err = cfg.Decode(data)
if err != nil {
return err
}
fmt.Println(cfg)
return nil
} else {
return err
}
vs.
Good
data, err := ioutil.ReadFile(name)
if err != nil {
return err
}
if err := cfg.Decode(data); err != nil {
return err
}
fmt.Println(cfg)
return nil
避免裸参数
函数调用中的裸参数可能会损害可读性。当参数名称的含义不明显时,请为参数添加C样式注释(/* … */)。
Bad
// func printInfo(name string, isLocal, done bool)
printInfo(“foo”, true, true)
vs.
Good
// func printInfo(name string, isLocal, done bool)
printInfo(“foo”, true /* isLocal /, true / done */)
更好的作法是,将裸bool类型替换为自定义类型,以获得更易读和类型安全的代码。将来,该参数不仅允许两个状态(true/false)。
type Region int
const (
UnknownRegion Region = iota
Local
)
type Status int
const (
StatusReady = iota + 1
StatusDone
// Maybe we will have a StatusInProgress in the future.
)
func printInfo(name string, region Region, status Status)
使用原始字符串字面值,避免转义
Go支持原始字符串字面值,可以跨越多行并包含引号。使用这些字符串可以避免更难阅读的手工转义的字符串。
Bad
wantError := “unknown name:“test””
vs.
Good
wantError := unknown error:"test"
初始化结构体引用
在初始化结构引用时,请使用&T{}代替new(T),以使其与结构体初始化一致。
Bad
sval := T{Name: “foo”}
// 不一致
sptr := new(T)
sptr.Name = “bar”
vs.
Good
sval := T{Name: “foo”}
sptr := &T{Name: “bar”}
格式化字符串放在Printf外部
如果你为Printf-style函数声明格式字符串,请将格式化字符串放在外面,并将其设置为const常量。
这有助于go vet对格式字符串执行静态分析。
Bad
msg := “unexpected values %v, %v\n”
fmt.Printf(msg, 1, 2)
vs.
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
另请参阅”go vet:Printf家族检查“。
五. 模式
测试表
在核心测试逻辑重复时,将表驱动测试与子测试一起使用,以避免重复代码。
Bad
// func TestSplitHostPort(t *testing.T)
host, port, err := net.SplitHostPort(“192.0.2.0:8000”)
require.NoError(t, err)
assert.Equal(t, “192.0.2.0”, host)
assert.Equal(t, “8000”, port)
host, port, err = net.SplitHostPort(“192.0.2.0:http”)
require.NoError(t, err)
assert.Equal(t, “192.0.2.0”, host)
assert.Equal(t, “http”, port)
host, port, err = net.SplitHostPort(":8000")
require.NoError(t, err)
assert.Equal(t, “”, host)
assert.Equal(t, “8000”, port)
host, port, err = net.SplitHostPort(“1:8”)
require.NoError(t, err)
assert.Equal(t, “1”, host)
assert.Equal(t, “8”, port)
vs.
Good
// func TestSplitHostPort(t *testing.T)
tests := []struct{
give string
wantHost string
wantPort string
}{
{
give: "192.0.2.0:8000",
wantHost: "192.0.2.0",
wantPort: "8000",
},
{
give: "192.0.2.0:http",
wantHost: "192.0.2.0",
wantPort: "http",
},
{
give: ":8000",
wantHost: "",
wantPort: "8000",
},
{
give: "1:8",
wantHost: "1",
wantPort: "8",
},
}
for _, tt := range tests {
t.Run(tt.give, func(t *testing.T) {
host, port, err := net.SplitHostPort(tt.give)
require.NoError(t, err)
assert.Equal(t, tt.wantHost, host)
assert.Equal(t, tt.wantPort, port)
})
}
测试表使向错误消息添加上下文,减少重复的逻辑以及添加新的测试用例变得更加容易。
我们遵循这样的约定:将结构体切片称为tests。 每个测试用例称为tt。此外,我们鼓励使用give和want前缀说明每个测试用例的输入和输出值。
tests := []struct{
give string
wantHost string
wantPort string
}{
// …
}
for _, tt := range tests {
// …
}
功能选项
功能选项是一种模式,您可以在其中声明一个不透明Option类型,该类型在某些内部结构中记录信息。您接受这些选项的可变编号,并根据内部结构上的选项记录的全部信息采取行动。
将此模式用于您需要扩展的构造函数和其他公共API中的可选参数,尤其是在这些功能上已经具有三个或更多参数的情况下。
Bad
// package db
func Connect(
addr string,
timeout time.Duration,
caching bool,
) (*Connection, error) {
// …
}
// Timeout and caching must always be provided,
// even if the user wants to use the default.
db.Connect(addr, db.DefaultTimeout, db.DefaultCaching)
db.Connect(addr, newTimeout, db.DefaultCaching)
db.Connect(addr, db.DefaultTimeout, false /* caching */)
db.Connect(addr, newTimeout, false /* caching */)
vs.
Good
type options struct {
timeout time.Duration
caching bool
}
// Option overrides behavior of Connect.
type Option interface {
apply(*options)
}
type optionFunc func(*options)
func (f optionFunc) apply(o *options) {
f(o)
}
func WithTimeout(t time.Duration) Option {
return optionFunc(func(o *options) {
o.timeout = t
})
}
func WithCaching(cache bool) Option {
return optionFunc(func(o *options) {
o.caching = cache
})
}
// Connect creates a connection.
func Connect(
addr string,
opts …Option,
) (*Connection, error) {
options := options{
timeout: defaultTimeout,
caching: defaultCaching,
}
for _, o := range opts {
o.apply(&options)
}
// …
}
// Options must be provided only if needed.
db.Connect(addr)
db.Connect(addr, db.WithTimeout(newTimeout))
db.Connect(addr, db.WithCaching(false))
db.Connect(
addr,
db.WithCaching(false),
db.WithTimeout(newTimeout),
)
还可以参考下面资料:
Self-referential functions and the design of options
Functional options for friendly APIs