10.6. 包和命名

在本节中, 我们将提供一些关于如何遵循Go语言独特的包和成员的命名约定.

当创建一个包, 一般要用短小的包名, 但也不能太短导致难以理解. 标准库中最常用的包有 bufio, bytes, flag, fmt, http, io, json, os, sort, sync, 和 time 等包.

它们的名字都简洁明了. 例如, 不要将一个类似 imageutil 或 ioutilis 的通用包命名为 util, 虽然它看起来很短小. 要尽量避免包名使用经常被用于局部变量的名字, 这样可能导致用户重命名导入包, 例如前面看到的 path 包.

包名同时采用单数的形式. 标准库的 bytes, errors, 和 strings 使用了复数是为了避免和预定义的类型冲突, 同样还有 go/types 是为了避免和关键字冲突.

要避免包名有其他的含义. 例如, 2.5节中我们的温度转换包最初使用了 temp 包名, 虽然并没有持续多久. 这是一个糟糕的做法, 因为 temp 几乎是临时变量的同义词. 然后我们有一段时间使用了 temperature 作为包名, 虽然名字并没有表达包的真是用途. 最后我们改成了 tempconv 包名, 和 strconv 类似也很简洁明了.

现在让我们看看如何命名包的衬衣. 由于是通过包的导入名字引入包里面的成员, 例如 fmt.Println, 同时包含了包和成名的描述信息(翻译障碍). 我们并不需要关注Println的具体内容, 因为 fmt 已经包含了这个信息. 当设计一个包的时候, 需要考虑包名和成员名两个部分如何配合. 下面有一些例子:

bytes.Equal    flag.Int    http.Get    json.Marshal

我们可以看到一些常用的命名模式. strings 包提供了字符串相关的诸多操作:

package strings

func Index(needle, haystack string) int

type Replacer struct{ /* ... */ }
func NewReplacer(oldnew ...string) *Replacer

type Reader struct{ /* ... */ }
func NewReader(s string) *Reader

string 本身并没有出现在每个成员名字中. 因为用户会这样引用这些成员 strings.Index, strings.Replacer 等.

其他一些包, 可能只描述了单一的数据类型, 例如 html/template 和 math/rand 等, 只暴露一个主要的数据结构和与它相关的方法, 还有一个 New 名字的函数用于创建实例.

package rand // "math/rand"

type Rand struct{ /* ... */ }
func New(source Source) *Rand

这可能导致一些名字重复, 例如 template.Template 或 rand.Rand, 这就是为什么这些种类的包的名称往往特别短.

另一个极端, 还有像 net/http 包那样含有非常多的名字和不多的数据类型, 因为它们是要执行一个复杂的复合任务. 尽管有将近二十种类型和更多的函数, 包中最重要的成员名字却是简单明了的: Get, Post, Handle, Error, Client, Server.

有包net/http这样有很多名字没有很多结构,因为他们执行一个复杂任务。尽管二十类型和更多的功能,包最重要的成员最简单的名字:Get、Post、处理、错误,客户端,服务器。