本篇文章是基于链接的精简翻译,作为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
14import (
"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
10package main
import (
"fmt"
"hash/adler32"
"os"
"github.com/foo/bar"
"rsc.io/goversion/version"
)
In-Band Errors
1 | // (不推荐做法) Lookup returns the value for key or "" if there is no mapping for key. |
Initialisms
对于名词缩写词,命名的时候要使用全部大写或者全部小写,例如”URL”作为命名的一部分的时候,应命名为”urlPony或者URLPony”,而不是”UrlPony”。
Named Result Parameters
1 | // Location returns f's latitude and longitude. |
适当地给多返回参数命名,可以提高代码的可读性。
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
7package chubby
// (不推荐)
var ChubbyFile *os.File
// (推荐)
var File *os.File
对于包的命名则要避免使用太泛的名字,例如common、util、api、types、interfaces等。
Pass Values
当函数内部无需使用到参数的指针类型,同时传入的参数不是一个大结构体的时候,尽量不要使用指针类型参数传递。
Receiver Names
1 | type UserService struct{} |
Receiver Types
receiver该使用值类型还是指针类型的一些指导原则:
(1) 当receiver是map或者func或者chan的时候,使用值类型;
(2) 当receiver是slice且方法内部无需reslice的时候,使用值类型;
(3) 当方法内部需要改动receiver的内容的时候,使用指针类型;
(4) 当receiver是结构体,同时结构体中包含有sync.Mutex等同步变量的时候,使用指针类型,从而避免同步变量被复制而失效的问题;
(5) 当receiver是大数组或者大结构体的时候,使用指针类型,可以提高性能;
(6) 当然,出现意见分歧的时候,那就使用指针类型。