2.打包和工具链
- 包
- 包名惯例
- main包
- 导入
- 远程导入
- 命名导入
- 函数init
- 使用Go的工具
- 其他Go开发工具
- go vet
- Go代码格式化
- Go语言的文档
- Go开发协作要点
- 依赖管理
- 第三方依赖
- 对gb的介绍
在Go语言中,
包
是个非常重要的概念。其设计理念是
使用包来封装不同的语义单元。
包
所有Go语言的程序都会组织成若干组文件,每组文件被称为一个包。这样每个包的代码都可以作为很小的复用单元,被其他项目引用。每个包都可以单独导入和使用,以便开发者可以根据自己的需要导入特定功能。
所有的.go文件,除了空行和注释,都应该在第一行声明自己所属的包。每个包都在一个单独的目录里。不能把多个包放在同一个目录中,也不能把同一个包的文件分拆到多个不同目录中。这意味着,同一个目录下的所有.go文件必须声明同一个包名。
包名惯例
给包命名的惯例是使用包所在目录的名字。给包及其目录命名时,应该使用简介、请求且全小写的名字。
注意:并不需要所有包的名字都与别的包不同,因为导入包时是使用全路径的,所以可以区分同名的不同包。一般情况下,包被导入后会使用你的包名作为默认的名字,不过这个导入后的名字可以修改。
main包
在Go语言里,命名为main的包具有特殊的含义。Go语言的编译程序会试图把这种名字的包编译为二进制可执行文件。所有用Go语言编译的可执行程序都必须有一个叫main的包。
当编译器发现某个包的名字为main时,一定也会发现名为main()的函数,否则不会创建可执行文件。main()函数是程序的入口,所以如果没有这个函数,程序就没有办法开始执行。**程序编译时,会使用声明main包的代码所在的目录的目录名作为二进制可执行文件的文件名。**在Go语言里,命令是任何可执行程序。
导入
import语句告诉编译器到磁盘哪里去找想要导入的包。如果需要导入多个包,习惯上是将import语句把包装在一个导入块中,例如
import (
"encoding/xml"
"errors"
"fmt"
"log"
"net/http"
"regexp"
"../search"
)
编译器会使用Go环境变量设置的路径,通过引入的相对路径来查找磁盘上的包。标准库中的包也会在安装Go的位置找到。Go开发者创建的包会在GOPATH环境变量指定的目录里查找。GOPATH指定的这些目录就是开发者的个人工作空间。
例如,Go安装在/usr/local/go,并且环境变量GOPATH设置为/home/myproject:/home/mylibraries,编译器就会按照下面的顺序查找net/http包:
/usr/local/go/src/pkg/net/http
/home/myproject/src/net/http
/home/mylibraries/src/net/http
一旦编译器找到一个满足import语句的包,就停止进一步查找。因此,编译器会首先查找GO的安装目录,然后才会按照顺序查找GOPATH变量里列出的目录。
如果在执行run或者build的时候,由于某些包没有导入而报错,可以使用go get命令来修正这种错误。
远程导入
Go工具链会使用导入路径确定需要获取的代码在网络的什么地方。例如:
import "github.com/spfl3/viper"
用导入路径编译程序时,go build命令会使用GOPATH的设置,在磁盘上搜索这个包。事实上,这个导入路径代表一个URL,指向GITHUB上的代码库。如果路径包含URL,可以使用Go工具链从DVCS获取包,并把包的源代码保存到GOPATH指向的路径里与URL匹配的目录里。这个获取过程使用go get命令完成。go get将获取任意指定的URL的包,或者一个已经导入的包所依赖的其他包。由于go get的递归性,这个命令会扫描某个包的源码树,获取能找到的所有依赖包。
命名导入
如果要导入的多个包具有相同的名字,这种情况下,重名的包可以通过命名导入
的方式来导入。命名导入是指,在import语句给出的包路径的左侧定义一个名字,将导入的包命名为新名字。例如:
import (
"fmt"
myfmt "mylib/fmt"
)
当导入了一个不在代码里使用的包,Go编译器会编译失败,并输出一个错误。有时候,用户导入一个包,但是不需要引用这个包的标识符。这种情况下,可以使用空白标识符_
来重命名这个导入。
函数init
每个包可以包含任意多个init函数,这些函数都会在程序执行开始的时候被调用。所有被编译器发现的init函数都会安排在main函数之前执行。init函数用在设置包、初始化变量或者其他要在程序运行前优先完成的引导工作。
以数据库驱动为例,database下的驱动在启动时执行init函数会将自身注册到sql包里,因为sql包在编译时并不知道这些驱动的存在,等启动之后sql才能调用这些驱动。代码如下:
package postres
import (
"database/sql"
)
func init(){
sql.Register("postgres", new(PostgresDriver))
}
如果程序导入了这个包,就会调用init函数,促使PostgresSQL的启动最终注册到Go的sql包里,成为一个可用的驱动。在使用这个新的数据库驱动写程序时,使用空白标识符来导入包,以便新的驱动会包含到sql包。代码如下:
package main
import(
"database/sql"
_ "github.co,/goinaction/code/chapter3/dbdriver/postgres"
)
func main(){
sql.Open("postgres", "mydb") // 调用sql包提供的Open方法,该方法能工作的关键在于postgres驱动通过自己的init函数将自身注册到了sql包
}
使用Go的工具
在命令行提示符下,输入go这个命令:
通过输出的列表可以看到,这个命令包含一个编译器,这个编译器可以通过build命令启动。其中build和clean命令会执行编译和清理的工作。例如:
go build hello.go
go clean hello.go
调用clean后会删除编译生成的可执行文件。
注意:大部分Go工具的命名都会接受一个包名作为参数。在不包含文件名时,go工具会默认使用当前目录来编译。另外也可以指定包名的时候使用通配符。3个点表示匹配所有的字符串,例如:
go build github.com/goinaction/code/chapter3/...
除了指定包,大部分Go命名使用短路径
作为参数。例如:
go build wordcount.go
go build .
要执行程序,需要首先编译,然后执行编译创建的wordcound或者wordcount.exe程序。
但是go run命令可以在一次调用中完成这两个操作:
go run wordcount.go
其他Go开发工具
go vet
vet命令会帮开发人员检测代码的常见错误。其可以捕获的错误类型有:
- Printf类函数调用时,类型匹配错误的参数。
- 定义常用的方法时,方法签名的错误。
- 错误的结构标签
- 没有指定字段名的结构字面量。
例如:
package LearnGo
import "fmt"
func main() {
fmt.Printf("The quick for dogs", 3.14)
}
注意:每次对代码先执行go vet再将其签入源代码库是一个很好的习惯。
Go代码格式化
fmt工具会将开发人员的代码布局成和Go源代码类似的风格,使用go fmt后面跟文件名或者包名,就可以调用这个代码格式化工具。fmt命令会自动格式化并保存。
Go语言的文档
Go语言有两种方法为开发者生成文档:
- 命令行提示符模式:在终端上执行使用go doc命令来打印文档。此时无需离开终端即可浏览
- 浏览器界面模式:使用godoc程序启动一个Web服务器,通过点击的方式查看包的文档
godoc -http=:6060 // 该命令通知godoc在端口6060启动Web服务器。可以通过http://localhost:6060查看
补充:开发人员需要用下面的规则来写代码和注释:
- 用户需要在标识符之前,把自己想要的文档作为注释加入到代码中。这个规则对包、函数、类型和全局变量都使用。注释可以以双斜线开头、也可以用斜线和星号风格。
- 如果想给包写一段文字量比较大的文档,可以在工程里包含一个叫作doc.go的文件,使用同样的包名,并把包的介绍使用注释加包名声明之前。
Go开发协作要点
以分享为目的创建代码库
- 包应该在代码库的根目录中
- 包可以非常小
- 对代码执行go fmt
- 给代码写文档
依赖管理
第三方依赖
像godep和vender这种社区工具已经使用第三方(verdoring)导入路径重写这种特性解决了依赖问题。其思想是把所有的依赖包复制到工程代码库中的目录中,然后使用工程内部的依赖包所在目录来重写所有的导入路径。
对gb的介绍
gb是一个由Go社区成员开发的全新的构建工具。背后的原理源自理解到Go语言的import语句并没有提供可重复构建的能力。import语句可以驱动go get,但是import本身并没有包含足够的信息来决定到底要获取包的哪个修改的版本。因此,go get无法定位待获取代码的问题,导致Go工具在解决重复构建时,不得不使用复杂的方法。
gb既不包装Go工具链,也不使用GOPATH。gb基于工程将Go工具链工作空间的元信息做替换。一个gb工程就是磁盘上一个包含src/子目录的目录。符号$PROJECT导入了工程的根目录中,其下有一个src/的子目录中。这个符号只是一个简写,用来描述工程在磁盘上的位置。事实上,gb根本不需要设置任何环境变量。
gb工程会区分开发人员写的代码和开发人员需要依赖的代码。例如:
$PROJECT/src/ // 工程中存放开发人员携带的代码的位置
$PROJECT/vendor/src/ //存放第三方代码的位置
另外,gb的一个最好的特点是:不需要重写导入路径,且提供了插件系统,可以让社区扩展支持的功能。
注意:gb工程与Go官方工具链(包括go get)并不兼容。
参考资料:
- 《GO语言实战》