golang代码实践指南

本篇文章是基于链接的精简翻译,作为Effective Go的补充,主要罗列了一系列golang语言的编程实践规范,对于开发者而言具有很好的参考价值。

Gofmt

gofmt是一款golang的代码格式化组件,推荐在保存代码前运行gofmt来自动格式化代码,这样子能让团队项目有近乎一致的代码风格,从而提高代码的可读性。

Comment Sentences

文档注释代码必须是一条完整的语句,以句号结尾,以被描述的内容(方法名、类型名、变量名、常量名、包名)开头,例如:

1
2
3
4
5
// Request represents a request to run a command.
type Request struct { ...

// Encode writes the JSON encoding of req to w.
func Encode(w io.Writer, req *Request) { ...

Contexts

context.Context可以用来携带信息,通常用来传递请求周期变量。如果有需要用到它,要将其变成方法的第一个参数(推荐命名为ctx),而不是将ctx变成变量存储在一个结构体类型中。

Declaring Empty Slices

当定义一个空的切片的时候,通常有两种方式:

1
2
3
4
5
// 方式1
var t []string

// 方式2
t := []string{}

方式1中的t为nil,而方式2中的t则是一个非nil的零切片,在功能上两者是没有区别的(len(t)、cap(t)、append(t, “”)),但是在序列化成json的时候就有区别,方式1的t会为null,方式2的t会为[],要特别注意这个点。

Crypto Rand

当需要生成随机数的时候,不要使用包 math/rand ,因为其计算得到的是伪随机数,推荐使用包 crypto/rand ,示例用法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import (
"crypto/rand"
"fmt"
"log"
)

func Key() string {
buf := make([]byte, 16)
_, err := rand.Read(buf)
if err != nil {
log.Fatalf(err) // out of randomness, should never happen
}
return fmt.Sprintf("%x", buf)
}

Doc Comments

包中所有对外可用的变量、方法、结构体、常量都应该有注释,一些重要的不对外暴露的变量、方法、结构体也要声明注释。

Don’t Panic

不要使用panic来处理错误,应该使用error返回。

Error Strings

错误描述不要使用大写字母开头(除非是专有名词或缩写),不要用任何标点符号结尾,这样在记录log的时候可以组成一条完整的语句。

Goroutine Lifetimes

当大量启用协程的时候,应该尽量让代码保持简单,使得协程的生命周期很清晰。

Handle Errors

当函数返回error的时候,不要使用_吞掉error,每个error都应该被处理。

Imports

当引入的包出现命名有重复时候,优先对本项目的包起别名;引入的包要分组,组之间留一空行,golang的标准包默认要放在开头位置,例如:

1
2
3
4
5
6
7
8
9
10
package main

import (
"fmt"
"hash/adler32"
"os"

"github.com/foo/bar"
"rsc.io/goversion/version"
)

In-Band Errors

1
2
3
4
5
// (不推荐做法) Lookup returns the value for key or "" if there is no mapping for key.
func Lookup(key string) string

// (推荐做法) Lookup returns the value for key or ok=false if there is no mapping for key.
func Lookup(key string) (value string, ok bool)

Initialisms

对于名词缩写词,命名的时候要使用全部大写或者全部小写,例如”URL”作为命名的一部分的时候,应命名为”urlPony或者URLPony”,而不是”UrlPony”。

Named Result Parameters

1
2
3
// Location returns f's latitude and longitude.
// Negative values mean south and west, respectively.
func (f *Foo) Location() (lat, long float64, err error)

适当地给多返回参数命名,可以提高代码的可读性。

Package Comments

包名的注释通常注释在go文件的首行,注释中间不留空行。如果对”package main”注释,通常使用编程生成的二进制文件的名称开头,例如seedgen二进制文件的注释:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// Binary seedgen ...
package main

or

// Command seedgen ...
package main

or

// Program seedgen ...
package main

or

// The seedgen command ...
package main

or

// The seedgen program ...
package main

or

// Seedgen ..
package main

Package Names

对于包内的变量命名,要尽量精简,无需要带包的名称前缀,例如包chubby:

1
2
3
4
5
6
7
package chubby

// (不推荐)
var ChubbyFile *os.File

// (推荐)
var File *os.File

对于包的命名则要避免使用太泛的名字,例如common、util、api、types、interfaces等。

Pass Values

当函数内部无需使用到参数的指针类型,同时传入的参数不是一个大结构体的时候,尽量不要使用指针类型参数传递。

Receiver Names

1
2
3
4
5
6
7
type UserService struct{}

// (不推荐) 使用me、this、self作为receiver名称
func (me *UserService) AddUser() {}

// (推荐) 直接使用结构体的首字母简写
func (u *UserService) AddUser() {}

Receiver Types

receiver该使用值类型还是指针类型的一些指导原则:
(1) 当receiver是map或者func或者chan的时候,使用值类型;
(2) 当receiver是slice且方法内部无需reslice的时候,使用值类型;
(3) 当方法内部需要改动receiver的内容的时候,使用指针类型;
(4) 当receiver是结构体,同时结构体中包含有sync.Mutex等同步变量的时候,使用指针类型,从而避免同步变量被复制而失效的问题;
(5) 当receiver是大数组或者大结构体的时候,使用指针类型,可以提高性能;
(6) 当然,出现意见分歧的时候,那就使用指针类型。