Golang编程中如何正确命名(最佳实践)

AI 摘要: 本文介绍了编写优雅Go代码的必要条件,其中一个重要的前提是良好的命名。详细讲解了命名规范、驼峰命名、局部变量命名长短取舍、包命名和标准库代码参考等内容。总结包括使用短命名、上下文相关和自行领悟。

Golang很精简、高效,但并不容易写好优雅的Go代码,高质量的代码必要条件是代码的可读性,可读性的一个前提就是良好的命名

  1. https://golang.org/doc/effective_go.html#names

  2. https://talks.golang.org/2014/names.slide#1

  3. https://blog.golang.org/package-names

  4. https://github.com/golang/go/wiki/CodeReviewComments

  5. https://rakyll.org/style-packages/

1. 良好命名规范 - Name Conventions

  1. 一致(易读和猜测)
  2. 简短(不冗长,容易输入)
  3. 准确(没有歧义)

经验法则:变量名申明的长度和他作用的范围正向关(即越长的名字,作用范围应该会越广)

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. 如果类型更不明确(基础类型),则这些名称可以提供文档
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

  1. 错误类型定义模式,Error后缀,如FooError
  2. 错误值定义模式,Err前缀,如ErrFormat
1
2
3
4
5
6
7
// 错误类型定义,XTypeError
type ExitError struct {
    ...
}

// 错误值定义,ErrXval
var ErrFormat = errors.New("image: unknown format")

10. 包命名 - Packages

  • 选择对它们导出的名称有意义的软件包名称,避免使用utilcommon类似混淆的语义命名!
  • 结合导入路径考虑:Import paths
    1. 包路径的最后一个组成部分应与程序包名称相同:"compress/gzip" // package gzip
    2. 避免口吃效果:"code.google.com/p/goauth2/oauth2"
    3. 针对包,通常放在仓库根目录:"github.com/golang/oauth2" // package oauth2
    4. 包命名都采用小写,避免使用大写字母(非所有文件系统都支持大小区分)
    5. 包命不要定义成复数,名为httputils,而是httputil

11. 标准库代码参考 - Std Library

标准库是查找良好的Go代码,寻求灵感的好地方。

Tips: 站在巨人的肩膀上,学习他们优秀的编码部分!

12. 小结

  1. 短命名
  2. 上下文相关
  3. 自行领悟