乐趣区

关于golang:06命名惯例见名知意

本文视频地址

日常工作中,命名这件事看似简略,如果在大规模软件开发中,做出好的命名并非易事。

命名是编程语言的要求:好的命名是为了进步程序的可读性和可维护性。什么是好的命名呢?无论哪门编程语言,良好的命名应该遵循一些通用的准则,不同编程语言在命名上还会有一些个性化的命名习惯。

要想做好 Go 标识符命名(包含 package 命名),起码要遵循两个准则:

1 简略且统一
2 利用上下文辅助命名

1. 简略且统一

对于简略,咱们最直观地了解就是越短越好,但这里的简略还蕴含着清晰明确。短意味着能用一个单词命名的,就不要应用单词组合;能用 1 个字母(在特定上下文)表白标识符的用处,就不必残缺单词。
如下是 Go 语言一些常见的命名标准。

1) 包

Go 中的包(package)个别倡议以小写模式的单个单词命名,Go 规范库在这方面给咱们做出了很好的示范:

在给包命名时不要有是否与其余包重名的顾虑,因为在 Go 中,包名是能够不惟一的,比方 project1 我的项目有名为 work 的包,project2 我的项目也能够有本人的名为 work 的包。然而每个包的导入门路是惟一的,对于包名抵触的状况,能够通过包别名 (package alias) 语法来解决:

import "github.com/i-coder-robot/project1/work"
import barlog "github.com/i-coder-robot/project2/work" // package import alias

Go 语言倡议:包名应尽量与包导入门路 (import path) 的最初一个门路分段保持一致。比方:包导入门路 golang.org/x/text/encoding 的最初门路分段是 encoding,该门路下包名就应该为 encoding。但在理论状况中,包名与导入门路最初分段不同的也有很多,比方:实时分布式音讯队列 nsq 的官网客户端包的导入门路为:github.com/nsqio/go-nsq,然而该门路上面的包名却是 nsq。比方 go-nsq 的代表这是一份 Go 语言实现的 nsq 客户端 API 库,为的是和 nsq-java、pynsq、rust-nsq 等其余语言的客户端 API 做出显式辨别。

那如果将 nsq 的 Go 客户端 API 放入 github.com/nsqio/go-nsq/nsq 上面,会是怎么呢?显然在导入门路中呈现两次 ”nsq” 字样的景象,不被 Go 官网举荐。明天看来如果能将所有 Go 实现放入 github 账号顶层门路上面的 golang 或 go 门路下应该是更好的计划,比方:

"github.com/nsqio/go/nsq”"github.com/nsqio/golang/nsq"

不仅要思考包本身的名字,还要同时思考兼顾到该包导出的标识符 (如变量、常量、类型、函数等) 的命名。因为对这些这些包导出标识符的援用是必须以包名作为前缀的,因而对包导出标识符命名时,在名字中不要再蕴含包名,比方:

strings.Reader 举荐
strings.StringReader 不举荐
strings.ReaderString 不举荐
strings.NewReader 举荐
strings.NewStringReader 不 举荐
strings.ReaderNewString 不 举荐

bytes.Buffer 举荐
bytes.ByteBuffer 不举荐
bytes.BufferByte 不举荐
bytes.NewByteBuffer 不举荐
bytes.BufferNewByte 不举荐

2) 变量、类型、函数和办法

Go 工程中包命名绝对少,变量、类型、函数和办法的命名是日常工作的粗茶淡饭。

在 Go 中变量分为包级别的变量和局部变量(函数或办法内的变量)。函数或办法的参数、返回值肯定水平上都能够视为局部变量。

Go 语言官网要求标识符命名采纳驼峰命名法(CamelCase),以变量名为例,如果变量名由一个以上的词组合形成,那么这些词之间严密相连,不应用任何连接符(比方:下划线)。驼峰命名法有两种模式,一种是第一个词的首字母小写,前面每个词的首字母大写,叫做“小骆峰拼写法”(lowerCamelCase),这也是在 Go 中最常见的标识符命名法;而第一个词的首字母以及前面每个词的首字母都大写,叫做“大驼峰拼写法”(UpperCamelCase),又称“帕斯卡拼写法”(PascalCase)。因为首母大写的标识符在 Go 语言中被视作包导出标识符,因而只有在波及包导出的状况下,才会用到大驼峰拼写法。不过首字母缩略词要放弃全副大写,比方 HTTP(Hypertext Transfer Protocol)、CBC(Cipher Block Chaining) 等。
给变量、类型、函数和办法命名仍然要以简略短小为首要思考的准则,咱们看一下 Go 规范库中标识符名称,很多单字母的标识符命名,这是 Go 在命名上的一个常规。Go 标识符一般来说仍以单个单词作为命名首选。不同类别标识符的命名呈现出上面一些特色:

  • 循环和条件变量多采纳单个字母命名(具体见下面的统计数据);
  • 函数 / 办法的参数和返回值变量个别以单个单词或单个字母为主;
  • 办法名因为在调用时会绑定类型信息,因而命名多以单个单词为主;
  • 函数名则多以多单词的复合词进行命名;
  • 类型名也多以多单词的复合词进行命名;

如下
catSlice []*Cat [不举荐]
cats []*Cat [举荐]

带有类型信息的命名除了让变量看起来更长之外,让浏览代码的人感觉臃肿。

有人会问:catSlice 能够得悉变量所代表的底层存储是一个切片,这样便能够在 catSlice 上利用切片的各种操作了。有这样疑难的人然遗记了命名的通用常规:放弃变量申明与应用之间的间隔越近越好或者说将变量申明在第一次应用该变量之前。如果在一屏之内能看到 cats 的申明,那 -Slice 这个类型信息显然是不用放在变量的名称中了。

  • 放弃简短命名变量含意上的一致性

Go 规范库中常见短变量名字所代表的含意,在整个规范库范畴内不会产生歧义。

变量 v, k, i 的罕用含意:// 循环语句中的变量
        for i, v := range s {...} // i: 下标变量;v:元素值
        for k, v := range m {...} // k: key 变量;v: 元素值
        for v := range r {...} // v: 元素值

        // if、switch/case 分支语句中的变量
        if v := foods[name]; v != "" { } // v: 元素值
        switch v := e.Elem(); v.Kind() {... ...}

        case v := <-c: // v: 元素值

        // 反射的后果值
        v := reflect.ValueOf(y)

变量 t 的罕用含意:t := time.Now() 
        t := &Timer{}
        if t := md.map[k]; t != nil { }

变量 b 的罕用含意:b := make([]byte, n) 
        b := new(bytes.Buffer) 

3) 常量

在 Go 语言中,常量在命名形式上与变量并无较大差异,并不要求全副大写。只是思考其含意的精确传递,常量多应用多单词组合的命名。上面是规范库中的例子:

// $GOROOT/src/net/http/request.go

const (defaultMaxTimes = 6)

const (keepAlive   = false)

当然,你也能够为自身就有着大写名称的特定常量应用全大写的名字,比方数学计算中的 PI。
在 Go 中数值型常量无需显式赋予类型,常量会在应用时依据左值类型和其余运算操作数的类型做主动的转换。

4) 接口

Go 语言中的 interface 是 Go 在编程语言层面上的一个翻新,它为 Go 代码提供了弱小的“解耦合”能力,因而良好的接口类型设计和接口组合是 Go 程序设计的动态骨架和根底。良好的接口设计必须有好的接口命名。在 Go 语言中 interface 名字依然以单个词为优先。对于领有惟一办法 (method) 或通过多个领有惟一办法的接口组合而成的接口,Go 语言的常规是个别用 ” 办法名 +er” 的形式为 interface 命名。比方:

type Writer interface {Write(p []byte) (n int, err error)
}

type Reader interface {Read(p []byte) (n int, err error)
}

type Closer interface {Close() error
}

type ReadWriteCloser interface {
    Reader
    Writer
    Closer
}

2. 利用上下文环境,用最短的名字携带足够的信息

Go 在给标识符命名时还有着思考上下文环境的常规,即在不影响可读性前提下,联合一致性的准则。在 Go 中别离使用这两个命名计划(index、value)和 (i、v),并做比对:

for index := 0; index < len(s); index++ {value := s[index]
        ... ...
}

vs.

for i := 0; i < len(s); i++ {v := s[i]
        ... ...
}

咱们看到:至多在 forLoop 这个上下文中,index、value 并没有比 i、v 携带更多额定信息。

退出移动版