命名是代码规范中很重要的一部分,统一的命名规范有利于提高代码的可读性,仅仅通过好的命名便可获取足够有用的信息。
通用规则
- 不要用宽泛、无意义的名字,如 util、helper、info、common 等。
- 首字母缩略语要么全小写,要么全大写。
// Bad
Md5
Rsa
// Good
MD5
md5
RSA
rsa
例外,如果是与其他单词构成驼峰命名风格,请使用驼峰风格。
// Bad
GetMD5
GetRSAKey
// Good
GetMd5
GetRsaKey
- 非缩略语应该使用驼峰命名。
- 不要使用 2/4 来表达英文 to/for。
因为这样的命名风格不够清晰易读。代码的可读性和可维护性是非常重要的,应该尽量避免使用令人困惑的命名方式。
- 如无必要,不要起和包相同的名字。
项目命名
- 小写,如果有多个单词使用连字符分隔。
// Bad
goecharts
go_echarts
goEcharts
GoEcharts
// Good
go-echarts
包命名
- 保持包名和目录名一致。
- 尽量采取有意义、简短的包名,不要和标准库冲突。
- 包名应该为小写单词,不要使用下划线或者混合大小写,使用多级目录来划分层级。
- 简单明了的包命名,如 time、list、http。
- 不用复数。如 net/url 而不是 net/urls。
- 包名谨慎使用缩写。当缩写是程序员广泛熟知的词时,可以使用缩写。例如:
- strconv (string conversion)
- syscall (system call)
- fmt (formatted I/O)
- 不要使用大而全无意义的包名。
如 util、common、misc、global
。package 名字应追求清晰且收敛,符合‘单一职责’原则。而不是像common
一样,什么都能往里面放,越来越膨胀,让依赖关系变得复杂,不利于阅读、复用和重构。注意,xxx/utils/encryption
这样的包名是允许的。
- 只有一个源文件的包,包名应该和文件名保持一致。
- 不要轻易使用别名。
更多可参考 Package names - The Go Blog 和 Style guideline for Go packages。
文件命名
- 采用有意义简短的文件名。
- 文件名应该采用小写,并且使用下划线分割各个单词。
函数命名
- 函数名必须遵循驼峰式,首字母根据访问控制决定使用大写或小写。
- 代码生成工具自动生成的代码可排除此规则(如协议生成文件 xxx.pb.go,gotests 工具自动生成文件 xxx_test.go 里面的下划线)。
- 函数应该以动词开头。
// Bad
func panicLinesParsing(){}
func (f VerifyFlow) DataETL(ctx context.Context, datas []Data){}
// Good
func parsePanicLines(){}
func (f VerifyFlow) ETLData(ctx context.Context, datas []Data){}
结构体命名
- 采用驼峰命名方式,首字母根据访问控制采用大写或者小写。
- 结构体名应该是名词或名词短语,如 Customer、WikiPage、Account、AddressParser,它不应是动词。
- 避免使用 Data、Info 这类意义太宽泛的结构体名。
- 结构体的定义和初始化格式采用多行。
// User 多行定义。
type User struct {
Name string
Email string
}
// 多行初始化。
u := User{
UserName: "john",
Email: "john@example.com",
}
接口命名
- 命名规则基本保持和结构体命名规则一致。
- 单个函数的接口名以 er 作为后缀,如 Reader,Writer。
// Reader 字节数组读取接口。
type Reader interface {
// Read 读取整个给定的字节数据并返回读取的长度
Read(p []byte) (n int, err error)
}
- 两个函数的接口名综合两个函数名。
- 三个以上函数的接口名,类似于结构体名。
// Car 小汽车结构申明。
type Car interface {
// Start ...
Start([]byte)
// Stop ...
Stop() error
// Recover ...
Recover()
}
量命名
通用
- 不应该以类型作为前后缀。
// map
filterHandlerMap -> opToHandler
// slice
uidSlice -> uids
// array
uidArray -> uids
// 二维切片或数组。
// 比如多个班级下的学生ID。
uidSliceSlice -> classesUids
- 量名应该是名词,进行时和过去式可以做形容词,成为量名的一部分。
- 尽量不要用拼音命名。
- 遵循驼峰式,根据是否导出决定首字母大小写。
// 导出全局变量。
var AppVersion = "1.0.0"
// 未导出全局变量。
var appVersion = "1.0.0"
// 导出全局常量。
const AppVersion = "1.0.0"
// 未导出全局常量。
const appVersion = "1.0.0"
- 若量类型为 bool 类型,则名称应以 Has,Is,Can 或 Allow 等单词开头。
- 私有量和局部量规范一致,均以小写字母开头。
- 作用域较小的名字(局部变量/函数参数),尽量使用简短的名字。
如 c 比 lineCount 要好,i 比 sliceIndex 要好。
// Bad
lineCount := getlineCount()
for sliceIndex := range msgs {
}
// Good
c := getlineCount()
for i := range msgs {
}
- 作用域较大的名字(全局变量),不要使用缩写,要有明确的意义。
如 lineCount 要比 c 好,sliceIndex 要比 i 好。
// Bad
var c, i int
// Good
var lineCount, sliceIndex int
- 全局量中不要包含格式化字符,因为违反就近原则。
// Bad
var (
tGitHost = "https://git.code.oa.com"
mrCommitsUri = "/api/v3/projects/%s/merge_request/%s/commits"
)
// Good
func getMRCommitsUri() string {
return fmt.Sprintf("/api/v3/projects/%s/merge_request/%s/commits", "foo", "bar")
}
常量命名
- 如果是枚举类型的常量,需要先创建相应类型。
// Scheme 传输协议。
type Scheme string
// 传输协议。
const (
HTTP Scheme = "http" // HTTP 明文传输协议
HTTPS Scheme = "https" // HTTPS 加密传输协议
)
方法接收器命名
- 推荐以类名第一个英文首字母的小写作为接收器的命名。
- 接收器的名称在函数超过 20 行的时候不要用单字符。
- 命名不能采用 me,this,self 这类易混淆名称。
错误命名
对于存储为全局变量的错误值,根据是否导出,使用前缀 Err 或 err。
var (
// 导出以下两个错误,以便此包的用户可以将它们与errors.Is 进行匹配。
ErrBrokenLink = errors.New("link is broken")
ErrCouldNotOpen = errors.New("could not open")
// 这个错误没有被导出,因为我们不想让它成为我们公共 API 的一部分。
errNotFound = errors.New("not found")
)
对于自定义错误类型,请改用后缀 Error。
// 这个错误被导出,以便这个包的用户可以将它与 errors.As 匹配。
type NotFoundError struct {
File string
}
func (e *NotFoundError) Error() string {
return fmt.Sprintf("file %q not found", e.File)
}
// 这个错误没有被导出,因为我们不想让它成为公共 API 的一部分。
// 但我们仍然可以在的包内将其和 errors.As 一起使用。
type resolveError struct {
Path string
}
func (e *resolveError) Error() string {
return fmt.Sprintf("resolve %q", e.Path)
}
避免使用内置名称
Go language specification 概述了几个内置的,不应在 Go 项目中使用的标识符 predeclared identifiers。
Types:
bool byte complex64 complex128 error float32 float64
int int8 int16 int32 int64 rune string
uint uint8 uint16 uint32 uint64 uintptr
Constants:
true false iota
Zero value:
nil
Functions:
append cap close complex copy delete imag len
make new panic print println real recover
在使用预先分配的标识符时编译器不会报告错误,但是诸如go vet
之类的工具会正确地指出这些和其他情况下的隐式问题。
// Bad
// 作用域内隐式覆盖 error interface
var error string
func handleErrorMessage(error string) {
// 作用域隐藏内置 error
}
type Foo struct {
// 虽然这些使用内置标识符的自定义字段可以编译通过,但对 error 或 string 字符串的搜索存在二义性
error error
string string
}
func (f Foo) Error() error {
// error 和 f.error 在视觉上是相似的
return f.error
}
func (f Foo) String() string {
// string and f.string 在视觉上是相似的
return f.string
}
// Good
var errorMessage string
func handleErrorMessage(msg string) {
}
type Foo struct {
// error 和 string 现在是明确的
err error
str string
}
func (f Foo) Error() error {
return f.err
}
func (f Foo) String() string {
return f.str
}