Go 博客

组织 Go 代码

Andrew Gerrand
2012 年 8 月 16 日

引言

Go 代码的组织方式与其他语言不同。本文讨论了如何为您的 Go 程序中的元素命名和打包,以最好地服务于用户。

选择好的名称

您选择的名称会影响您对代码的思考方式,因此在为包及其导出的标识符命名时要多加注意。

包的名称为其内容提供了上下文。例如,标准库中的 bytes 包导出了 Buffer 类型。仅凭 Buffer 这个名称并不具有很强的描述性,但与其包名称结合后,其含义就变得清晰了:bytes.Buffer。如果该包的名称不够描述性,例如 util,那么缓冲区很可能会获得一个更长、更笨拙的名称 util.BytesBuffer

在工作中,不要害怕重命名事物。随着您对程序的投入时间的增加,您将更好地理解其各个部分如何协同工作,因此也更清楚它们的名称应该是什么。没有必要过早地锁定您的决定。(gofmt 命令有一个 -r 标志,它提供了一种语法感知的搜索和替换功能,使大规模重构更加容易。)

一个好的名称是软件接口最重要的部分:名称是代码的每个使用者首先看到的东西。因此,精心选择的名称是良好文档的起点。以下许多实践都源于良好的命名。

选择好的导入路径(使您的包“go get”-able)

导入路径是用户导入包的字符串。它指定了包的源代码所在的目录(相对于 $GOROOT/src/pkg$GOPATH/src)。

导入路径应该是全局唯一的,因此请使用您的源代码存储库的路径作为其基础。例如,来自 go.net 子存储库的 websocket 包的导入路径是 "golang.org/x/net/websocket"。Go 项目拥有 "github.com/golang" 这个路径,因此其他作者不能为不同的包使用该路径。因为存储库 URL 和导入路径是一致的,所以 go get 命令可以自动获取和安装该包。

如果您不使用托管的源代码存储库,请选择一个唯一的自定义前缀,例如域名、公司或项目名称。例如,所有 Google 内部 Go 代码的导入路径都以字符串 "google" 开头。

导入路径的最后一个元素通常与包名相同。例如,导入路径 "net/http" 包含包 http。这不是强制要求——如果您愿意,也可以让它们不同——但为了可预测性,您应该遵循约定:用户可能会惊讶于导入 "foo/bar" 会在包名称空间中引入标识符 quux

有时人们会将 GOPATH 设置为其源代码存储库的根目录,并将他们的包放在相对于存储库根目录的目录中,例如 "src/my/package"。一方面,这可以缩短导入路径("my/package" 而不是 "github.com/me/project/my/package"),但另一方面,这会破坏 go get,并迫使用户在尝试使用该包时重新设置他们的 GOPATH。不要这样做。

最小化导出的接口

您的代码很可能由许多有用的小代码片段组成,因此很容易将大部分功能暴露在您包的导出接口中。抵制这种冲动!

您提供的接口越大,您就越需要支持。用户很快就会依赖您导出的每一个类型、函数、变量和常量,从而创建一个隐式的契约,您必须永远遵守,否则就有可能破坏用户的程序。在准备 Go 1 时,我们仔细审查了标准库的导出接口,并删除了我们尚未准备好承诺的部分。您在分发自己的库时也应该同样谨慎。

如有疑问,请省略!

将什么放入包中

将所有内容都扔进一个“杂货包”式的包很容易,但这会稀释包名称的含义(因为它必须涵盖大量功能),并迫使用户使用包的小部分功能时,需要编译和链接大量不相关的代码。

另一方面,将代码拆分成太多小的包也很容易,这样您可能会陷入接口设计的泥潭,而不是仅仅完成工作。

以 Go 标准库为例。它的一些包很大,一些包很小。例如,http 包包含 17 个 go 源文件(不包括测试),并导出了 109 个标识符;而 hash 包由一个文件组成,只导出了三个声明。没有一成不变的规则;两种方法在特定情况下都是合适的。

也就是说,main 包通常比其他包要大。复杂的命令包含大量在可执行文件上下文之外用处不大的代码,通常将所有代码放在同一个地方更简单。例如,go 工具超过 12000 行代码,分布在 34 个文件中。

记录您的代码

良好的文档是可用且可维护代码的基本品质。请阅读 Godoc:文档化 Go 代码一文,了解如何编写良好的文档注释。

下一篇文章:App Engine 1.7.1 中的 Go 更新
上一篇文章:GCC 4.7.1 中的 Gccgo
博客索引