Golang很精简、高效,但并不容易写好优雅的Go代码,高质量的代码必要条件是代码的可读性,可读性的一个前提就是良好的命名
https://golang.org/doc/effective_go.html#names
https://talks.golang.org/2014/names.slide#1
https://blog.golang.org/package-names
https://github.com/golang/go/wiki/CodeReviewComments
https://rakyll.org/style-packages/
1. 良好命名规范 - Name Conventions
- 一致(易读和猜测)
- 简短(不冗长,容易输入)
- 准确(没有歧义)
经验法则:变量名申明的长度和他作用的范围正向关(即越长的名字,作用范围应该会越广)
2. 驼峰命名 - MixedCase
- 使用大小写,不要使用下划线
- 缩写字符应该都大写:
ServeHTTP、IDProcessor
3. 局部变量命名长短取舍 - Local variables
- 使用短命名:长命名让代码冗长难读
- Prefer
i
to index
- Prefer
r
to reader
- Prefer
b
to buffer
- 结合上下文考虑
- RuneCount函数中:内部使用
count
,而非RuneCount
- 迭代Map使用
ok
表示状态:v, ok := m[k]
- 长命名的多参数函数,意味着应该重构了
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
28
| // BAD
func RuneCount(buffer []byte) int {
runeCount := 0
for index := 0; index < len(buffer); {
if buffer[index] < RuneSelf {
index++
} else {
_, size := DecodeRune(buffer[index:])
index += size
}
runeCount++
}
return runeCount
}
// GOOD
func RuneCount(b []byte) int {
count := 0
for i := 0; i < len(b); {
if b[i] < RuneSelf {
i++
} else {
_, n := DecodeRune(b[i:])
i += n
}
count++
}
return count
}
|
4. 函数变量命名长短的取舍 - Parameters
函数参数就像局部变量一样,但它们也可以作为文档:
- 如果类型是描述性的(类型已经说明的参数相关特性),则应简短
- 如果类型更不明确(基础类型),则这些名称可以提供文档
1
2
3
4
5
6
7
8
9
| // 类型描述,参数考虑短命名规则
func AfterFunc(d Duration, f func()) *Timer
func Escape(w io.Writer, s []byte)
// 类型是基础类型,考虑通过命名来指明参数含义
func Unix(sec, nsec int64) Time
func HasPrefix(s, prefix []byte) bool
|
5. 函数返回值命名取舍 - Return values
- 导出函数的返回值应仅出于文档目的而命名
- 不要仅仅为了避免在函数内部声明变量而命名结果参数
1
2
3
4
5
6
7
8
9
10
11
| // GOOD
func Copy(dst Writer, src Reader) (written int64, err error)
func ScanBytes(data []byte, atEOF bool) (advance int, token []byte, err error)
// BAD - 口吃效果
func (n *Node) Parent1() (node *Node)
func (n *Node) Parent2() (node *Node, err error)
// GOOD
func (n *Node) Parent1() *Node
func (n *Node) Parent2() (*Node, error)
|
6. 类型接收器命名 - Receivers
接收者是一种特殊的论点:
- 按照惯例,它们是一两个字符,因为它们通常和类型在同行前后
- 接收者名称应在类型的方法之间保持一致
1
2
3
4
5
6
| // GOOD
func (b *Buffer) Read(p []byte) (n int, err error)
func (sh serverHandler) ServeHTTP(rw ResponseWriter, req *Request)
func (r Rectangle) Size() Point
|
7. 成员导出命名 - Exported - variables/constants/functions/types
导出的名称由其包名称限定,命名导出的变量,常量,函数和类型时,请记住这一点。
因此,结合上下文对比以下命名:
1
2
3
4
5
6
7
| // BAD
bytes.ByteBuffer
strings.StringReader
// GOOD
bytes.Buffer
strings.Reader
|
8. 接口命名 - Interface Types
- 包含仅单一方法的接口:
- 通常只是在函数名称后附加
er
- 有时候,不是正确的英语命名,但语义更精简,我们也可以采用
- 有时候,适当单词组合,效果更好
- 包含多个方法的接口:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| // 加er后缀
type Reader interface {
Read(p []byte) (n int, err error)
}
type Execer interface {
Exec(query string, args []Value) (Result, error)
}
// 单词组合
type ByteReader interface {
ReadByte() (c byte, err error)
}
// 多方法接口
net.Conn
http.ResponseWriter
io.ReadWriter
|
9. 错误类型与错误值命名 - Errors
- 错误类型定义模式,
Error
后缀,如FooError
- 错误值定义模式,
Err
前缀,如ErrFormat
1
2
3
4
5
6
7
| // 错误类型定义,XTypeError
type ExitError struct {
...
}
// 错误值定义,ErrXval
var ErrFormat = errors.New("image: unknown format")
|
10. 包命名 - Packages
- 选择对它们导出的名称有意义的软件包名称,避免使用
util
、common
类似混淆的语义命名! - 结合导入路径考虑:
Import paths
- 包路径的最后一个组成部分应与程序包名称相同:
"compress/gzip" // package gzip
- 避免
口吃
效果:"code.google.com/p/goauth2/oauth2"
- 针对包,通常放在仓库根目录:
"github.com/golang/oauth2" // package oauth2
- 包命名都采用小写,避免使用大写字母(非所有文件系统都支持大小区分)
- 包命不要定义成复数,名为
httputils
,而是httputil
11. 标准库代码参考 - Std Library
标准库是查找良好的Go代码,寻求灵感的好地方。
Tips: 站在巨人的肩膀上,学习他们优秀的编码部分!
12. 小结
- 短命名
- 上下文相关
- 自行领悟