Uber 是一家美国硅谷的科技公司,也是 Go 语言的早期 adopter。其开源了很多 golang 项目,诸如被 Gopher 圈熟知的 zap、jaeger 等。2018 年年末 Uber 将内部的 Go 风格规范 开源到 GitHub,经过一年的积累和更新,该规范已经初具规模,并受到广大 Gopher 的关注,本文是对规范的整理

来源:https://github.com/xxjwxc/uber_go_guide_cn

1. 参数和返回(slice & map)

slicemap 包含了指向底层数据的指针,因此当在函数中使用他们作为参数和返回值时,需要特别注意。

1.1 作为参数

a. 反面示例

type User struct {
Likes []string
}

func (u *User) SetLikes(likes []string) {
u.Likes = likes
}
func TestRun(t *testing.T) {
like := []string{"篮球", "旅游", "听歌"}
user := new(User)
// 切片作为参数传递
user.SetLikes(like)
fmt.Println("user Like1: ", user.Likes)
// 这里修改变量,会影响到user对象中的属性
like[1] = "学习"
fmt.Println("user Like2 : ", user.Likes)
}
/**输出
=== RUN TestRun
user Like1: [篮球 旅游 听歌]
user Like2 : [篮球 学习 听歌]
--- PASS: TestRun (0.00s)
PASS
*/

b. 推荐写法

// 只需要修改这个方法,其他不变
func (u *User) SetLikes(likes []string) {
u.Likes = make([]string, len(likes))
// 这里使用了复制
copy(u.Likes, likes)
}
/**输出
=== RUN TestRun
user Like1: [篮球 旅游 听歌]
user Like2 : [篮球 旅游 听歌]
--- PASS: TestRun (0.00s)
PASS
*/

1. 2 作为返回值

a. 反面示例

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()

b. 推荐写法

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()

2.结构体嵌入

嵌入的类型,应放在结构体内字段列表的顶部,并且必须有一个空行将嵌入式字段与常规字段分隔开。

2.1 反面示例

type Client struct {
version int
http.Client
}

2.2 推荐写法

// 1.嵌入类型在顶部 
// 2.空行将嵌入式字段与常规字段分隔开
type Client struct {
http.Client

version int
}

3. 初始化结构体

3.1 使用字段名初始化

// 反面示例
k := User{"John", "Doe", true}

// 推荐写法
k := User{
FirstName: "John",
LastName: "Doe",
Admin: true,
}

3.2 对零值结构使用 var

如果在声明中省略了结构的所有字段,请使用 var 声明结构。

// 反面示例
user := User{}

// 推荐
var user User

3.3 初始化Struct 引用

在初始化结构引用时,请使用&T{}代替new(T),以使其与结构体初始化一致。

// 反面示例
sval := T{Name: "foo"}

// inconsistent
sptr := new(T)
sptr.Name = "bar"

// 推荐
sval := T{Name: "foo"}
sptr := &T{Name: "bar"}

4. nil 是一个有效的slice

4.1 当返回长度为零的切片时

// 反面示例
if x == "" {
return []int{}
}

// 推荐写法
if x == "" {
return nil
}

4.2 检查切片是否为空

要检查切片是否为空,请始终使用len(s) == 0。而非 nil

// 反面示例
func isEmpty(s []string) bool {
return s == nil
}

// 推荐示例
func isEmpty(s []string) bool {
return len(s) == 0
}

4.3 零值切片

零值切片(用var声明的切片)可立即使用,无需调用make()创建。

a. 反面示例

nums := []int{}
// or, nums := make([]int)

if add1 {
nums = append(nums, 1)
}

if add2 {
nums = append(nums, 2)
}

b. 推荐写法

var nums []int

if add1 {
nums = append(nums, 1)
}

if add2 {
nums = append(nums, 2)
}