打包和工具链
包
所有 Go 语言的程序都会组织成若干组文件,每组文件被称为一个包。这样每个包的代码都可以作为很小的复用单元,被其他项目引用。
所有的.go 文件,除了空行和注释,都应该在第一行声明自己所属的包。每个包都在一个单独的目录里。不能把多个包放到同一个目录中,也不能把同一个包的文件分拆到多个不同目录中。这意味着,同一个目录下的所有.go 文件必须声明同一个包名。
包名惯例
给包命名的惯例是使用包所在目录的名字。这让用户在导入包的时候,就能清晰地知道包名。
给包及其目录命名时,应该使用简洁、清晰且全小写的名字,这有利于开发时频繁输入包名。
一般情况下,包被导入后会使用你的包名作为默认的名字,不过这个导入后的名字可以修改。
main 包
在 Go 语言里,命名为 main 的包具有特殊的含义。Go 语言的编译程序会试图把这种名字的包编译为二进制可执行文件。所有用 Go 语言编译的可执行程序都必须有一个名叫 main 的包。
当编译器发现某个包的名字为 main 时,它一定也会发现名为 main()的函数,否则不会创建可执行文件。main()函数是程序的入口,所以,如果没有这个函数,程序就没有办法开始执行。程序编译时,会使用声明 main 包的代码所在的目录的目录名作为二进制可执行文件的文件名。
导入
关于包的导入在 《Go 语言圣经》的包和工具中有记录。这里介绍一下包的远程导入。
远程导入
目前的大势所趋是,使用分布式版本控制系统(Distributed Version Control Systems,DVCS)来分享代码,如 GitHub、Launchpad 还有 Bitbucket。Go 语言的工具链本身就支持从这些网站及类似网站获取源代码。Go 工具链会使用导入路径确定需要获取的代码在网络的什么地方。
import "github.com/spf13/viper"
用导入路径编译程序时,go build 命令会使用 GOPATH 的设置,在磁盘上搜索这个包。事实上, 这个导入路径代表一个 URL,指向 GitHub 上的代码库。如果路径包含 URL,可以使用 Go 工具链从 DVCS 获取包,并把包的源代码保存在 GOPATH 指向的路径里与 URL 匹配的目录里。这个获取过程 使用 go get 命令完成。go get 将获取任意指定的 URL 的包,或者一个已经导入的包所依赖的其 他包。由于 go get 的这种递归特性,这个命令会扫描某个包的源码树,获取能找到的所有依赖包。
Go 开发工具
在 《Go 语言圣经》的包和工具中已经记录了一些 Go 的开发工具,这里介绍一些其中没有记录的。
go vet
这个命令不会帮开发人员写代码,但如果开发人员已经写了一些代码,vet 命令会帮开发人员检测代码的常见错误。
- Printf 类函数调用时,类型匹配错误的参数。
- 定义常用的方法时,方法签名的错误。
- 错误的结构标签。
- 没有指定字段名的结构字面量。
go vet 工具不能让开发者避免严重的逻辑错误,或者避免编写充满小错的代码。不过,这个工具可以很好地捕获一部分常见错误。每次对代码先执行 go vet 再将其签入源代码库是一个很好的习惯。
go 代码格式化
fmt 是 Go 语言社区很喜欢的一个命令。fmt 工具会将开发人员的代码布局成和 Go 源代码类似的风格,不用再为了大括号是不是要放到行尾,或者用 tab(制表符)还是空格来做缩进而争论不休。使用 go fmt 后面跟文件名或者包名,就可以调用这个代码格式化工具。fmt 命令会自动格式化开发人员指定的源代码文件并保存。
当然,一般使用集成的IDE都会有相应的格式化快捷指令
与其他 Go 开发者合作
现代开发者不会一个人单打独斗,而 Go 工具也认可这个趋势,并为合作提供了支持。多亏了 go 工具链,包的概念没有被限制在本地开发环境中,而是做了扩展,从而支持现代合作方式。在分布式开发环境里,想要良好合作,需要遵守的一些惯例。
以分享为目的创建代码库
开发人员一旦写了些非常棒的 Go 代码,就会很想把这些代码与 Go 社区的其他人分享。这其实很容易,只需要执行下面的步骤就可以。
1. 包应该在代码库的根目录中 使用 go get 的时候,开发人员指定了要导入包的全路径。这意味着在创建想要分享的代码库的时候,包名应该就是代码库的名字,而且包的源代码应该位于代码库目录结构的根目录。Go 语言新手常犯的一个错误是,在公用代码库里创建一个名为 code 或者 src 的目录。如果这么做,会让导入公用库的语句变得很长。为了避免过长的语句,只需要把包的源文件放在公用代码库的根目录就好。
2. 包可以非常小 与其他语言相比,Go 语言的包一般相对较小。不要在意包只支持几个 API,或者只完成一项任务。在 Go 语言里,这样的包很常见,而且很受欢迎。
3. 对代码执行 go fmt 和其他开源代码库一样,人们在试用代码前会通过源代码来判断代码的质量。开发人员需要在签入代码前执行 go fmt,这样能让自己的代码可读性更好,而且不会由于一些字符的干扰(如制表符),在不同人的计算机上代码显示的效果不一样。
4. 给代码写文档 Go 开发者用 godoc 来阅读文档,并且会用 http://godoc.org 这个网站来阅读开源包的文档。如果按照 go doc 的最佳实践来给代码写文档,包的文档在本地和线上都会很好看,更容易被别人发现。
依赖管理
现在最流行的依赖管理工具是 Keith Rarik 写的 godep、Daniel Theophanes 写的 vender 和 Gustavo Niemeyer 开发的 gopkg.in 工具。gopkg.in 能帮助开发人员发布自己的包的多个版本。
第三方依赖
godep 和 vender 这种社区工具已经使用第三方(verdoring)导入路径重写这种特性解决了依赖问题。其思想是把所有的依赖包复制到工程代码库中的目录里,然后使用工程内部的依赖包所在目录来重写所有的导入路径。 下图是使用 godep 来管理工程里第三方依赖时的一个典型的源代码树:
可以看到 godep 创建了一个叫作 Godeps 的目录。由这个工具管理的依赖的源代码被放在一个叫作_workspace/src 的目录里。接下来,如果看一下在 main.go 里声明这些依赖的 import 语句就能发现需要改动的地方。
在路径重写之前,import 语句使用的是包的正常路径。包对应的代码存放在 GOPATH 所指定的磁盘目录里。在依赖管理之后,导入路径需要重写成工程内部依赖包的路径。可以看到这些导入路径非常长,不易于使用。引入依赖管理将所有构建时依赖的源代码都导入到一个单独的工程代码库里,可以更容易地重新构建工程。使用导入路径重写管理依赖包的另外一个好处是这个工程依旧支持通过 go get 获取代码库。当获取这个工程的代码库时,go get 可以找到每个包,并将其保存到工程里正确的目录中。
路径重写前
路径重写后
对 gb 的介绍
gb 是一个由 Go 社区成员开发的全新的构建工具。gb 意识到,不一定要包装 Go 本身的工具,也可以使用其他方法来解决可重复构建的问题。
gb 背后的原理源自理解到 Go 语言的 import 语句并没有提供可重复构建的能力。import语句可以驱动 go get,但是 import 本身并没有包含足够的信息来决定到底要获取包的哪个修改的版本。go get 无法定位待获取代码的问题,导致 Go 工具在解决重复构建时,不得不使用复杂且难看的方法。我们已经看到过使用 godep 时超长的导入路径是多么难看。gb 的创建源于上述理解。gb 既不包装 Go 工具链,也不使用 GOPATH。gb 基于工程将 Go 工具链工作空间的元信息做替换。这种依赖管理的方法不需要重写工程内代码的导入路径。而且导入路径依旧通过 go get 和 GOPATH 工作空间来管理。
gb 工程的例子
一个 gb 工程就是磁盘上一个包含 src/子目录的目录。符号$PROJECT 导入了工程的根目录中,其下有一个 src/的子目录中。这个符号只是一个简写,用来描述工程在磁盘上的位置。$PROJECT 不是必须设置的环境变量。事实上,gb 根本不需要设置任何环境变量。gb 工程会区分开发人员写的代码和开发人员需要依赖的代码。开发人员的代码所依赖的代码被称作第三方代码(vendored code)。
工程中存放开发人员写的代码的位置:$PROJECT/src/
存放第三方代码的位置:$PROJECT/vendor/src/
gb 一个最好的特点是,不需要重写导入路径。gb 工具首先会在$PROJECT/src/目录中查找代码,如果找不到。会在$PROJECT/vender/src/目录里查找。与工程相关的整个源代码都会在同一个代码库里。自己写的代码在工程目录的 src/目录中,第三方依赖代码在工程目录的 vender/src 子目录中。这样,不需要配合重写导入路径也可以完成整个构建过程,同时可以把整个工程放到磁盘的任意位置。这些特点,让 gb 成为社区里解决可重复构建的流行工具。
还需要提一点:gb 工程与 Go 官方工具链(包括 go get)并不兼容。因为 gb 不需要设置GOPATH,而 Go 工具链无法理解 gb 工程的目录结构,所以无法用 Go 工具链构建、测试或者获取代码。
很多 Go 工具支持的特性,gb 都提供对应的特性。gb 还提供了插件系统,可以让社区扩展支持的功能。其中一个插件叫作 vender。这个插件可以方便地管理$PROJECT/vender/src/目录里的依赖关系,而这个功能 Go 工具链至今没有提供。想了解更多 gb 的特性,可以访问这个网站:getgb.io。