
第4章 代码结构化与项目管理
4.1 包(package)
4.1.1 包的概念
Go语言使用包(package)来组织管理代码,包是结构化代码的一种方式。和其他语言如Java类似,Go语言中包的主要作用是把功能相似或相关的代码组织在一起,以方便查找和使用。在Go语言中,每个.go文件都必须归属于某一个包,每个.go文件都可有init()函数。包名在源文件中第一行通过关键字package指定,包名要小写。如下所示:

每个目录下面可以有多个.go文件,这些文件只能属于同一个包,否则编译时会报错。同一个包下的不同.go文件相互之间可以直接引用变量和函数,所有这些文件中定义的全局变量和函数不能重名。
Go语言的可执行应用程序必须有main包,而且在main包中必须且只能有一个main()函数,main()函数是应用程序运行开始的入口。在main包中也可以使用init()函数。
Go语言不强制要求包的名称和文件所在目录名称相同,但是这两者最好保持相同,否则很容易引起歧义。因为导入包的时候,会使用目录名作为包的路径,而在代码中使用时,却要使用包的名称。
4.1.2 包的初始化
可执行应用程序的初始化和执行都起始于main包。如果main包的源代码中没有包含main()函数,则会引发构建错误undefined:main.main。
main()函数既没有参数,也没有返回类型,init()函数和main()函数在这一点上一样。
如果main包还导入了其他的包,那么在编译时会将它们依次导入。有时一个包会被多个包同时导入,那么它只会被导入一次(例如很多包可能都会用到fmt包,但它只会被导入一次,因为没有必要导入多次)。
当某个包被导入时,如果该包还导入了其他的包,那么会先将其他包导入进来,再对这些包中的包级常量和变量进行初始化,接着执行init()函数(如果有的话),依此类推。
当所有被导入的包都加载完毕,就会对main包中的包级常量和变量进行初始化,然后执行main包中的init()函数,最后执行main()函数。
Go语言中init()函数常用于包的初始化,该函数是Go语言的一个重要特性,有下面的特征:
■ init函数是用于程序执行前进行包的初始化的函数,例如初始化包里的变量等。
■ 每个包可以拥有多个init函数。
■ 包的每个源文件也可以拥有多个init函数。
■ 同一个包中多个init()函数的执行顺序不定。
■ 不同包的init()函数按照包导入的依赖关系决定该函数的执行顺序。
■ init()函数不能被其他函数调用,其在main函数执行之前,自动被调用。
4.1.3 包的导入
一个Go语言程序通过导入(import)关键字将一组包链接在一起,通过导入包为程序所使用。所谓导入包即等同于包含了这个包的所有的代码对象。程序中未使用的包,并不能导入进来。
导入操作会使用目录名作为包的路径而不是包名,实际应用中一般会保持两者一致。
例如标准包中定义的big包:package big,导入时语句为:import "math/big",导入时源代码在$GOROOT目录下的src/math/big目录中。程序代码使用big.Int时,big指的才是.go文件中定义的包名称。
当导入多个包时,一般按照字母顺序排列包名称,像LiteIDE会在保存文件时自动完成这个动作。
为避免名称冲突,同一包中所有对象的标识符必须唯一。但是相同的标识符可以在不同的包中使用,因为可以使用包名来区分它们。
import语句一般放在包名定义的下面,导入包示例如下:

导入多个包的常见的方式是:

调用导入的包函数的一般方式:

下面介绍三种特殊的import方式。
点操作的含义是某个包导入之后,在调用这个包的函数时,可以省略前缀的包名,如这里可以写成Println("Hello World!"),而不是fmt.Println("Hello World!")。例如:

别名操作就是可以把包命名成另一个容易记忆的名字。别名操作调用包函数时,前缀变成了别名,即f.Println("Hello World!")。在实际项目中有时这样使用,但请谨慎使用,不要不加节制地采用这种形式。例如:

_操作是引入某个包,但不直接使用包里的函数,而是调用该包里面的init函数,例如下面的mysql包的导入。此外在开发中,由于某种原因某个原来导入的包现在不再使用,也可以采用这种方式处理,例如下面fmt的包。代码示例如下:

4.1.4 标准库
在Go语言的安装目录里包含标准库的各种包。在$GOROOT/src中可以看到源码,可以根据情况自行重新编译。
下面是标准库中部分包的简单说明。可访问https://golang.google.cn/pkg/#stdlib了解更多详细情况。


4.1.5 从GitHub安装包
如果想安装GitHub上的项目到本地计算机,可打开终端执行:

现在这台计算机上的其他Go应用程序也可以通过导入路径"github.com/ffhelicopter/tmm" 来使用。开发中一般这样导入:

Go对包的版本管理不是很友好,至少在go1.10前是如此,不过现在有些第三方项目做得不错,有兴趣的读者可以了解一下(glide、godep、govendor)。Gomodules是1.11版本解决“包依赖管理”的实验性技术方案,本书后面章节有详细讲解。
4.1.6 导入外部安装包
如果要在应用中使用一个或多个外部包,可以使用go install在本地计算机上安装它们。go install是自动包安装工具,如需要将包安装到本地,它会从远端仓库下载包,完成检出、编译和安装。
包安装的先决条件是要自动处理包自身依赖关系,被依赖的包也会安装到子目录下。
如果想使用https://github.com/gocolly/colly这种托管在Google Code、GitHub和Launchpad等代码网站上的包,可以通过如下命令安装:Go install github.com/gocolly/colly。
将一个名为github.com/gocolly/colly的包安装在$GoPATH/pkg/目录下。
go install/build用来编译包和其依赖的包。
区别:go build只对main包有效,在当前目录编译生成一个可执行的二进制文件(依赖包生成的静态库文件放在$GOPATH/pkg)。
go install一般生成静态库文件,放在$GOPATH/pkg目录下,文件扩展名为a。
如果为main包,运行Go build则会在$GOPATH/bin生成一个可执行的二进制文件。