Go 语言
项目结构、风格和命名惯例
Go 命名惯例

Go 命名惯例

想要做好 Go 标识符的命名(包括对包的命名),至少要遵循两个原则:

  • 简单且一致
  • 利用上下文辅助明明

简单且一致

最直观的理解就是短小,前提还要包括清晰明确。

例如能使用一个单词命名,不要使用单词组合。Go 命名惯例选择了简洁命名+注释辅助解释的方式。

对于 Go 中的包(package),建议以小写形式的单个单词命名。我们在给包命名时不要有是否与其他包重名的顾虑,因为在 Go 中,包的命名是与导入路径相关联的,而导入路径是唯一的。

对于包名冲突的解决方法:

import "github.com/xxx/log"
import yyylog "github.com/yyy/log"

一些好例子与坏例子:

// bad
"github.com/xxx/go-yyy"
// good
"github.com/xxx/go/yyy"

不仅要考虑包自身的名字,还要兼顾该包导出的标识符的命名。例如,如果一个包的名字是 strings,那么该包导出的标识符的命名就不能是 Strings,因为在 Go 中,导出的标识符的命名是以大写字母开头的。

// bad
strings.StringReader
// good
strings.Reader
// bad
bytes.ByteBuffer
// good
bytes.Buffer

变量、类型、函数和方法

Go 语言官方要求标识符命名采用大驼峰命名法(UpperCamelCase)

一些命名惯例:

1、变量名中不要带有类型信息。

// bad
userSlice := []*User
// good
users []*User

保持变量声明与使用之间的距离越近越好,或者在第一次使用变量之前声明该变量。

2、保持简短命名变量含义上的一致性

Go 语言中有大量单字母,单个词或缩写命名的简短命名变量。这些变量的命名含义上的一致性是非常重要的。 一致意味着代码中相同或相似的命名所传达的含义是相同的或相似的。

变量 k、v、i 的常用含义

for i, v := range s {} // i 为下标变量,v 为元素值
for k, v := range m {} // k 为键变量,v 为元素值
for v := range r {} // v 为元素值
 
case v := <-c: // v: 元素值
v := reflect.ValueOf(x) // v: 反射的结果值

变量 t 的常用含义

t := time.Now() // 时间
t := &Timer{} //定时器
if t := md.typemap[off]; t != nil {} // 类型

变量 b 的常用含义

b := make([]byte, 0, 256) // 字节切片
b := new(bytes.Buffer) // 字节缓冲

常量

在 Go 语言中,常量在命名方式上与变量并无较大差别,并不要求全部大写。只是考虑其含义的准确传递,常量多使用多单词组合的方式命名。

// str/net/http/request.go
const (
    // DefaultMaxHeaderBytes is the maximum permitted size of the headers
    // in an HTTP request.
    DefaultMaxHeaderBytes = 1 << 20 // 1 MB
)
 
const (
    defaultMaxMemory = 32 << 20 // 32MB
)
 
const (
    deleteHostHeader = true
    keepHostHeader = false
)

也可以对名称本身就是全大写的特定常量使用全大写的名字,比如数学计算中的 PI,或是为了与系统错误码、系统信号名称保持一致而用全大写的方式:

const (
    PI4A = 7.8539816339744830962e-01 // π/4
    PI4B = 3.0616169978683829431e-17 // π/4 less π/4A
    PI4C = 2.7492418739972671155e-34 // π/4 less π/4B
)
 
// 错误码
const (
    E2BIG = Errno(0x7)
    EACCES = Errno(0xd)
    EADDRINUSE = Errno(0x61)
    EADDRNOTAVAIL = Errno(0x62)
    ...
)
 
// 信号
const (
    SIGHUP = Signal(0x1)
    SIGINT = Signal(0x2)
    SIGQUIT = Signal(0x3)
    SIGILL = Signal(0x4)
    ...
)

接口

接口为 Go 代码提供了强大的解耦合能力,良好的接口类型设计和接口组合是 Go 程序设计的静态骨架和基础。

对于接口类型优先以单个字母命名。对与拥有多个或唯一方法的接口,惯例是用 er 结尾,例如 ReaderWriterFormatterCloseNotifier 等。

Go 语言推荐尽量定义小接口,并通过接口组合的方式构建程序。

利用上下文辅助命名

在给标识符命名时还要考虑上下文环境的惯例,即在不影响可读性的前提下,兼顾一致性原则,尽可能地用短小的名字命名标识符。

下面我们来看一下命名惯例带来的影响:

// 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 {
            _, size := DecodeRune(b[i:])
            i += size
        }
        count++
    }
    return count
}