《Go 语言编码规范》学习笔记
-
所有的提交代码都应该通过
golint
和go vet
检测建议在代码编辑器上面做如下设置:
- 保存的时候运行
goimports
- 使用
golint
和go vet
去做错误检测。
- 保存的时候运行
-
零值Mutex是有效的
零值
sync.Mutex
和sync.RWMutex
是有效的。所以指向 mutex 的指针基本是不必要的。Bad
mu := new(sync.Mutex) mu.Lock()
Good
var mu sync.Mutex mu.Lock()
-
在边界处拷贝Slices和Maps
slices 和 maps 包含了指向底层数据的指针,因此在需要复制它们时要特别注意。
接收Slices和Maps
请记住,当 map 或 slice 作为函数参数传入时,如果您存储了对它们的引用,则用户可以对其进行修改。
Bad
func (d *Driver) SetTrips(trips []Trip) { d.trips = trips } trips := ... d1.SetTrips(trips) // 你是要修改 d1.trips 吗? trips[0] = ...
Good
func (d *Driver) SetTrips(trips []Trip) { d.trips = make([]Trip, len(trips)) copy(d.trips, trips) } trips := ... d1.SetTrips(trips) // 这里我们修改 trips[0],但不会影响到 d1.trips trips[0] = ...
返回slices和maps
同样,请注意用户对暴露内部状态的 map 或 slice 的修改。
Bad
type Stats struct { mu sync.Mutex counters map[string]int } // Snapshot 返回当前状态。 func (s *Stats) Snapshot() map[string]int { s.mu.Lock() defer s.mu.Unlock() return s.counters } // snapshot 不再受互斥锁保护 // 因此对 snapshot 的任何访问都将受到数据竞争的影响 // 影响 stats.counters snapshot := stats.Snapshot()
Good
type Stats struct { mu sync.Mutex counters map[string]int } func (s *Stats) Snapshot() map[string]int { s.mu.Lock() defer s.mu.Unlock() result := make(map[string]int, len(s.counters)) for k, v := range s.counters { result[k] = v } return result } // snapshot 现在是一个拷贝 snapshot := stats.Snapshot()
-
使用time处理时间
使用
time.Time
表达瞬时时间在处理时间的瞬间时使用
time.Time
,在比较、添加或减去时间时使用time.Time
中的方法。Bad
func isActive(now, start, stop int) bool { return start <= now && now < stop }
Good
func isActive(now, start, stop time.Time) bool { return (start.Before(now) || start.Equal(now)) && now.Before(stop) }
使用
time.Duration
表达时间段Bad
func poll(delay int) { for { // ... time.Sleep(time.Duration(delay) * time.Millisecond) } } poll(10) // 是几秒钟还是几毫秒?
Good
func poll(delay time.Duration) { for { // ... time.Sleep(delay) } } poll(10*time.Second)
-
错误类型
使用
errors.New
表示带有静态字符串的错误。 如果调用者需要匹配并处理此错误,则将此错误导出为变量以支持将其与errors.Is
匹配。无错误匹配
// package foo func Open() error { return errors.New("could not open") } // package bar if err := foo.Open(); err != nil { // Can't handle the error. panic("unknown error") }
有错误匹配
// package foo var ErrCouldNotOpen = errors.New("could not open") func Open() error { return ErrCouldNotOpen } // package bar if err := foo.Open(); err != nil { if errors.Is(err, foo.ErrCouldNotOpen) { // handle the error } else { panic("unknown error") } }
对于动态字符串的错误, 如果调用者不需要匹配它,则使用
fmt.Errorf
, 如果调用者确实需要匹配它,则自定义error
。无错误匹配
// package foo func Open(file string) error { return fmt.Errorf("file %q not found", file) } // package bar if err := foo.Open("testfile.txt"); err != nil { // Can't handle the error. panic("unknown error") }
有错误匹配
// package foo type NotFoundError struct { File string } func (e *NotFoundError) Error() string { return fmt.Sprintf("file %q not found", e.File) } func Open(file string) error { return &NotFoundError{File: file} } // package bar if err := foo.Open("testfile.txt"); err != nil { var notFound *NotFoundError if errors.As(err, ¬Found) { // handle the error } else { panic("unknown error") } }
-
初始化切片和map时指定容量
make([]T, length, capacity) make(map[T1]T2, hint)
Bad
for n := 0; n < b.N; n++ { data := make([]int, 0) for k := 0; k < size; k++{ data = append(data, k) } }
BenchmarkBad-4 100000000 2.48s
Good
for n := 0; n < b.N; n++ { data := make([]int, 0, size) for k := 0; k < size; k++{ data = append(data, k) } }
BenchmarkBad-4 100000000 2.48s
同理:使用map的时候也应该指定容量,减少动态扩容,导致的开销
-
在序列化结构中使用字段标记
任何序列化到JSON、YAML、, 或其他支持基于标记的字段命名的格式应使用相关标记进行注释。
Bad
type Stock struct { Price int Name string } bytes, err := json.Marshal(Stock{ Price: 137, Name: "UBER", })
Good
type Stock struct { Price int `json:"price"` Name string `json:"name"` // Safe to rename Name to Symbol. } bytes, err := json.Marshal(Stock{ Price: 137, Name: "UBER", })
-
优先使用strconv而不是fmt
将原语转换为字符串或从字符串转换时,
strconv
速度比fmt
快Bad
for i := 0; i < b.N; i++ { s := fmt.Sprint(rand.Int()) }
Good
for i := 0; i < b.N; i++ { s := strconv.Itoa(rand.Int())
-
避免字符串到字节的转换
Bad
for i := 0; i < b.N; i++ { w.Write([]byte("Hello world")) }
Good
data := []byte("Hello world") for i := 0; i < b.N; i++ { w.Write(data) }
-
避免过长的行
-
相似的声明放在一起
Bad
const a = 1 const b = 2 var a = 1 var b = 2 type Area float64 type Volume float64
Good
const ( a = 1 b = 2 ) var ( a = 1 b = 2 ) type ( Area float64 Volume float64 )
-
包名命名规范
当命名包时,请按下面规则选择一个名称:
- 全部小写。没有大写或下划线。
- 大多数使用命名导入的情况下,不需要重命名。
- 简短而简洁。请记住,在每个使用的地方都完整标识了该名称。
- 不用复数。例如
net/url
,而不是net/urls
。 - 不要用“common”,“util”,“shared”或“lib”。这些是不好的,信息量不足的名称。
-
函数名命名规范
我们遵循 Go 社区关于使用 MixedCaps 作为函数名 的约定。有一个例外,为了对相关的测试用例进行分组,函数名可能包含下划线,如:
TestMyFunction_WhatIsBeingTested
. -
函数分组与顺序
- 函数应按粗略的调用顺序排序。
- 同一文件中的函数应按接收者分组。
因此,导出的函数应先出现在文件中,放在
struct
,const
,var
定义的后面。在定义类型之后,但在接收者的其余方法之前,可能会出现一个
newXYZ()
/NewXYZ()
由于函数是按接收者分组的,因此普通工具函数应在文件末尾出现。
Bad
func (s *something) Cost() { return calcCost(s.weights) } type something struct{ ... } func calcCost(n []int) int {...} func (s *something) Stop() {...} func newSomething() *something { return &something{} }
Good
type something struct{ ... } func newSomething() *something { return &something{} } func (s *something) Cost() { return calcCost(s.weights) } func (s *something) Stop() {...} func calcCost(n []int) int {...}
-
减少嵌套
代码应通过尽可能先处理错误情况/特殊情况并尽早返回或继续循环来减少嵌套。减少嵌套多个级别的代码的代码量。
Bad
for _, v := range data { if v.F1 == 1 { v = process(v) if err := v.Call(); err == nil { v.Send() } else { return err } } else { log.Printf("Invalid v: %v", v) } }
Good
for _, v := range data { if v.F1 != 1 { log.Printf("Invalid v: %v", v) continue } v = process(v) if err := v.Call(); err != nil { return err } v.Send() }
-
不必要的else
如果在 if 的两个分支中都设置了变量,则可以将其替换为单个 if。
Bad
var a int if b { a = 100 } else { a = 10 }
Good
a := 10 if b { a = 100 }
-
nil是一个有效的slice
nil
是一个有效的长度为 0 的 slice,这意味着,您不应明确返回长度为零的切片。应该返回nil
来代替。Bad
if x == "" { return []int{} }
Good
if x == "" { return nil }
要检查切片是否为空,请始终使用
len(s) == 0
。而非nil
。Bad
func isEmpty(s []string) bool { return s == nil }
Good
func isEmpty(s []string) bool { return len(s) == 0 }
零值切片(用
var
声明的切片)可立即使用,无需调用make()
创建。Bad
nums := []int{} // or, nums := make([]int) if add1 { nums = append(nums, 1) } if add2 { nums = append(nums, 2) }
Good
var nums []int if add1 { nums = append(nums, 1) } if add2 { nums = append(nums, 2) }
记住,虽然 nil 切片是有效的切片,但它不等于长度为 0 的切片(一个为 nil,另一个不是),并且在不同的情况下(例如序列化),这两个切片的处理方式可能不同。
-
缩小变量作用域
如果有可能,尽量缩小变量作用范围。除非它与 减少嵌套的规则冲突。
Bad
err := os.WriteFile(name, data, 0644) if err != nil { return err }
Good
if err := os.WriteFile(name, data, 0644); err != nil { return err }
如果需要在 if 之外使用函数调用的结果,则不应尝试缩小范围。
Bad
if data, err := os.ReadFile(name); err == nil { err = cfg.Decode(data) if err != nil { return err } fmt.Println(cfg) return nil } else { return err }
Good
data, err := os.ReadFile(name) if err != nil { return err } if err := cfg.Decode(data); err != nil { return err } fmt.Println(cfg) return nil
参考:
- https://learnku.com/go/wikis/38426
- 《Go 语言编码规范》中文翻译地址:https://github.com/xxjwxc/uber_go_guide_cn