《Go 语言编码规范》学习笔记
-
所有的提交代码都应该通过
golint
和go vet
检测建议在代码编辑器上面做如下设置:
- 保存的时候运行
goimports
- 使用
golint
和go vet
去做错误检测。
- 保存的时候运行
-
零值Mutex是有效的
零值
sync.Mutex
和sync.RWMutex
是有效的。所以指向 mutex 的指针基本是不必要的。Bad
12mu := new(sync.Mutex)
mu.Lock()
Good
12var
mu sync.Mutex
mu.Lock()
-
在边界处拷贝Slices和Maps
slices 和 maps 包含了指向底层数据的指针,因此在需要复制它们时要特别注意。
接收Slices和Maps
请记住,当 map 或 slice 作为函数参数传入时,如果您存储了对它们的引用,则用户可以对其进行修改。
Bad
123456789func
(d *Driver) SetTrips(trips []Trip) {
d.trips = trips
}
trips := ...
d1.SetTrips(trips)
// 你是要修改 d1.trips 吗?
trips[0] = ...
Good
12345678910func
(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
123456789101112131415161718type
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
12345678910111213141516171819type
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
123func
isActive(now, start, stop int) bool {
return
start <= now && now < stop
}
Good
123func
isActive(now, start, stop time.Time) bool {
return
(start.Before(now) || start.Equal(now)) && now.Before(stop)
}
使用
time.Duration
表达时间段Bad
1234567func
poll(delay int) {
for
{
// ...
time.Sleep(time.Duration(delay) * time.Millisecond)
}
}
poll(10)
// 是几秒钟还是几毫秒?
Good
1234567func
poll(delay time.Duration) {
for
{
// ...
time.Sleep(delay)
}
}
poll(10*time.Second)
-
错误类型
使用
errors.New
表示带有静态字符串的错误。 如果调用者需要匹配并处理此错误,则将此错误导出为变量以支持将其与errors.Is
匹配。无错误匹配
123456789101112// 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"
)
}
有错误匹配
1234567891011121314151617// 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
。无错误匹配
123456789101112// 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"
)
}
有错误匹配
12345678910111213141516171819202122232425// 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时指定容量
12make([]T, length, capacity)
make(
map
[T1]T2, hint)
Bad
123456for
n := 0; n < b.N; n++ {
data := make([]int, 0)
for
k := 0; k < size; k++{
data = append(data, k)
}
}
1BenchmarkBad-4 100000000 2.48s
Good
123456for
n := 0; n < b.N; n++ {
data := make([]int, 0, size)
for
k := 0; k < size; k++{
data = append(data, k)
}
}
1BenchmarkBad-4 100000000 2.48s
同理:使用map的时候也应该指定容量,减少动态扩容,导致的开销
-
在序列化结构中使用字段标记
任何序列化到JSON、YAML、, 或其他支持基于标记的字段命名的格式应使用相关标记进行注释。
Bad
12345678type
Stock
struct
{
Price int
Name string
}
bytes, err := json.Marshal(Stock{
Price: 137,
Name:
"UBER"
,
})
Good
123456789type
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
123for
i := 0; i < b.N; i++ {
s := fmt.Sprint(rand.Int())
}
Good
12for
i := 0; i < b.N; i++ {
s := strconv.Itoa(rand.Int())
-
避免字符串到字节的转换
Bad
123for
i := 0; i < b.N; i++ {
w.Write([]byte(
"Hello world"
))
}
Good
1234data := []byte(
"Hello world"
)
for
i := 0; i < b.N; i++ {
w.Write(data)
}
-
避免过长的行
-
相似的声明放在一起
Bad
12345678const
a = 1
const
b = 2
var
a = 1
var
b = 2
type
Area float64
type
Volume float64
Good
1234567891011121314const
(
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
12345678910111213func
(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
12345678910111213type
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
123456789101112for
_, 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
123456789101112for
_, 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
123456var
a int
if
b {
a = 100
}
else
{
a = 10
}
Good
1234a := 10
if
b {
a = 100
}
-
nil是一个有效的slice
nil
是一个有效的长度为 0 的 slice,这意味着,您不应明确返回长度为零的切片。应该返回nil
来代替。Bad
123if
x ==
""
{
return
[]int{}
}
Good
123if
x ==
""
{
return
nil
}
要检查切片是否为空,请始终使用
len(s) == 0
。而非nil
。Bad
123func
isEmpty(s []string) bool {
return
s == nil
}
Good
123func
isEmpty(s []string) bool {
return
len(s) == 0
}
零值切片(用
var
声明的切片)可立即使用,无需调用make()
创建。Bad
12345678910nums := []int{}
// or, nums := make([]int)
if
add1 {
nums = append(nums, 1)
}
if
add2 {
nums = append(nums, 2)
}
Good
123456789var
nums []int
if
add1 {
nums = append(nums, 1)
}
if
add2 {
nums = append(nums, 2)
}
记住,虽然 nil 切片是有效的切片,但它不等于长度为 0 的切片(一个为 nil,另一个不是),并且在不同的情况下(例如序列化),这两个切片的处理方式可能不同。
-
缩小变量作用域
如果有可能,尽量缩小变量作用范围。除非它与 减少嵌套的规则冲突。
Bad
1234err := os.WriteFile(name, data, 0644)
if
err != nil {
return
err
}
Good
123if
err := os.WriteFile(name, data, 0644); err != nil {
return
err
}
如果需要在 if 之外使用函数调用的结果,则不应尝试缩小范围。
Bad
1234567891011if
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
1234567891011data, 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
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 地球OL攻略 —— 某应届生求职总结
· 提示词工程——AI应用必不可少的技术
· Open-Sora 2.0 重磅开源!
· 周边上新:园子的第一款马克杯温暖上架