空导入

如果导入包的名字没有在文件中引用,就会产生一个编译错误。但是,有时候必须导入一个包,这仅仅是为了利用它的副作用:对包级别的变量执行初始化表达式求值,并执行它的 init 函数。这里必须使用一个重命名导入,使用下划线作为替代的名字。这表示导入的内容为空白标识符,通常情况下,空白标识不可能被引用:

import _ "image/png" // 注册 PNG 解碼器

空白导入,多数情况下,使用空白引用导入额外的包,开启主程序中可选的特性。

命名

一些建议,关于遵从 Go 的习惯来给包和它的成员进行命名。

包的命名

包名使用简短的名字,但是不要短到完全看不懂。
尽可能保持可读性和无歧义。例如,不要把一个辅助工具包命名为 util,使用 imageutil 或 ioutil 这样更具体和清晰的名字。
避免使用经常用于相关局部变量的名字作为包名,或者迫使使用者使用重命名导入。例如,path 就要避免用作包名。
命名通常使用统一的形式,使用复数形式来避免和关键字的冲突。例如,标准包 bytes、errors、strings。

包成员的命名

引用包的成员会带上包名,所以设计成员名称的时候,要考虑包名和成员名这两部分协同一起表示的意义,而不只是成员名。
对于包导出的一个数据类型及其方法,通常有一个 New 函数用来创建实例。而导出的类型的名称可能和包名重复。例如,template.Template 或 rand.Rand。
包中最重要的成员使用最简单的命名。

go 工具

go 工具将不同种类的工具集合并为一个命名集。它的命令行接口使用“瑞士軍刀”风格,有十几个子命令。可以运行go help来查看内置文档的索引:

        bug         start a bug report
        build       compile packages and dependencies
        clean       remove object files and cached files
        doc         show documentation for package or symbol
        env         print Go environment information
        fix         update packages to use new APIs
        fmt         gofmt (reformat) package sources
        generate    generate Go files by processing source
        get         download and install packages and dependencies
        install     compile and install packages and dependencies
        list        list packages or modules
        mod         module maintenance
        run         compile and run Go program
        test        test packages
        tool        run specified go tool
        version     print Go version
        vet         report likely mistakes in packages

工作空间的组织

环境变量 GOPATH 执行工作空间的根。当需要切换不同的工作空间时,更新 GOPATH 变量即可。
切换环境变量然后下载代码:

$ export GOPATH=$HOME/gobook
$ go get gopl.io/...

命令最后的三个点的意义,go get 命令下一节会讲。

GOPATH 有三个子目录,分别用于存放不同类型的文件:

  • src:源码文件。每一个包在一个目录中
  • pkg:归档文件。构建工具存储编译后的包的位置,参考后面的 go install 命令
  • bin:可执行文件。许多用户会将该目录添加到可执行程序的搜索列表中

环境变量 GOROOT 指定 Go 发行版的根目录,其中提供所有标准库的包。用户无须设置 GOROOT,因为默认情况下在安装 Go 言语的时候会将其设置为安装路径。(如果机器上装了多个版本的Go,就可以通过这个环境变量来切换了吧。不过Go目前需要使用多个版本的问题。

命令 go env 可以查看环境变量。

包的下载

go get 命令可以下载单一的包,也可以使用 ... 符号来下载子目录或仓库。并且还会计算并下载所有依赖的包。

让包使用一个自定义的导入路径,但是真实的代码却是由更通用的站点提供,例如 github。这只需要在页面的 HTML 中添加如下的元数据,它重定向 go 工具到实际托管地址的 Git 仓库:

go get 指定 -u 参数,将确保命令会访问所有的包(无论本地是否已经有了)更新到最新版本。如果没有这个参数,已经存在在本地的包就做任何处理和确认。

包的构建

命令 go build 编译每一个命令行参数中的包。如果包是一个库,结果会被丢弃。如果是main包,则会创建可执行程序。
命令 go run 则可以构建之后直接运行。适用于即用即抛型的程序。
第一个不是以 .go 结尾的参数会作为 Go 可执行程序的参数列表的开始。

默认情况下,go build 命令构建所有需要的包以及它们所有的依赖,然后丢弃除了最终可执行程序之外的所有编译后的代码。
命令 go install 和 go build 非常相似,区别是它会保存每一个包的编译代码和命令,而不丢弃。编译后的包保存在 $GOPATH/pkg 目录中。这样,之后的 go build 和 go install 对于没有改变的包和命令不需要再重新编译,从而使后续的构建更快完成。
go build 使用 -i 参数,也会安装那些编译目标依赖的且还未被安装的代码包。同样是安装到 pkg 目录。

包的文档化

每一个导出的包成员的声明以及包声明自身应该使用文档注释来描述它的目的和用途。Go 文档注释总是完整的语句,第一行通常是摘要说明,以被注释者的名称(比如函数名)开头。
包声明的前面的文档注释是对整个包进行描述。它可以出现在任何一个文件里,但是每一个包里值能在一个文件开头写包的文档注释。比较长的包注释可以使用一个单独的注释文件,文件名通常叫 doc.go。

go doc
go doc 工具输出在命令行上指定的内容的声明和整个文档注释,可以是一个包、一个包成员、一个方法。

godoc
这是另一个工具,它提供一个 HTML 页面,展示的内容不少于 go doc 命令。
如果想浏览自己的包,可以在自己的工作区目录中运行 godoc。在执行下面的命令后,在浏览器中访问 http://localhost:8000/pkg

$ godoc -http :8000

还可以使用 -analysis=type 和 -analysis=pointer 命令行标志参数,用于打开文档和代码中关于静态分析的结果。

内部包

导入路径中包含 internal 的情况,会被特殊对待。这种包叫内部包,内部包只能被特定范围内的包导入,就是以这个内部包的父目录为根的目录树中的其他的包。

包的查询

go list 工具上报可用包的信息。通过简单的形式,go list 判断一个包是否存在于工作区目录中,如果存在就输出它的导入路径:

PS H:\Go\src\gopl\ch10> go list gopl/ch9/bank1
gopl/ch9/bank1

go list 命令的参数可以包含 “...” 通配符(类似于通配符星号的作用),用来匹配包的导入路径中的任意字符串。这可以枚举当前工作区目录下的所有的包: go list ... ,输出的结果实在太多了。
下面是一个指定的目录中的所有的包:

PS H:\Go\src\gopl\ch10> go list gopl/ch9/...
gopl/ch9/bank1
gopl/ch9/bank2
gopl/ch9/bank3
gopl/ch9/hacker

下面则是查找某个主题:

PS H:\Go\src\gopl\ch10> go list ...netcat...
gopl/ch8/netcat1
gopl/ch8/netcat2
gopl/ch8/netcat3
gopl/exercise8/e8/netcat

这里匹配的并不是包名,而是包的整个导入路径。这也包括从工作区目录为根开始的路径所组成的字符串里的任何内容。

go list 命令获取每一个包的完整元数据,而不仅仅是导入路径,并且提供各种对于用户或者其他工具可访问的格式。

-json 标记使 go list 以 JSON 格式输出每一个包的完整记录:

PS H:\Go\src\gopl\ch10> go list -json gopl/ch9/bank1
{
        "Dir": "H:\\Go\\src\\gopl\\ch9\\bank1",
        "ImportPath": "gopl/ch9/bank1",
        "Name": "bank",
        "Doc": "这是一个只有一个账户的并发安全银行",
        "Target": "H:\\Go\\pkg\\windows_amd64\\gopl\\ch9\\bank1.a",
        "Root": "H:\\Go\\",
        "Match": [
                "gopl/ch9/bank1"
        ],
        "Stale": true,
        "StaleReason": "build ID mismatch",
        "GoFiles": [
                "main.go"
        ]
}

这里展开介绍其中三个字段的意义:

  1. GoFiles :库源码文件或命令源码文件,就是实际保存产品代码的文件列表
  2. TestGoFiles :测试源码文件,都是以以 _test.go 结尾的文件,仅在编译测试的时候才会使用
  3. XTestGoFiles :外部测试包。也是测试源码文件,也是 _test.go 结尾,也是在测试过程中使用的。下篇的测试章节会讲到

-f 标记可以让用户通过 text/temple 包提供的模板语言来定制输出的格式:

PS H:\Go\src\gopl\ch10> go list -json strconv
{
...
        "Deps": [
                "errors",
                "internal/cpu",
                "math",
                "math/bits",
                "unicode/utf8",
                "unsafe"
        ],
...
}

PS H:\Go\src\gopl\ch10> go list -f "{{join .Deps \"" \""}}" strconv
errors internal/cpu math math/bits unicode/utf8 unsafe
PS H:\Go\src\gopl\ch10> go list -f '{{join .Deps \" \"}}' strconv
errors internal/cpu math math/bits unicode/utf8 unsafe
PS H:\Go\src\gopl\ch10> go list -f "{{join .Deps `` ``}}" strconv
errors internal/cpu math math/bits unicode/utf8 unsafe
PS H:\Go\src\gopl\ch10> go list -f '{{join .Deps ` `}}' strconv
errors internal/cpu math math/bits unicode/utf8 unsafe

由于这里 -f 的参数中有空格,必须要用引号包起来。而参数的内容中还包含引号,所以这个参数很难写。这里只给出几个示例,具体什么规则讲不清楚,大概有下面几点:

  • 首先,这个windows的环境。Linux下没有这么坑
  • 双引号内部的特殊符号,需要重复写两遍,两个双引号表示一个双引号
  • 单引号内部的特殊符号,不用重复写两遍
  • 双引号前面要转义,所以在前面要加上斜杠(\)
  • 最后两个命令,内层用的是撇号(`),所以不要加斜杠转义

下面的命令输出标准库的 compress 子树中每个包的直接导入记录:

PS H:\Go\src\gopl\ch10> go list compress/...
compress/bzip2
compress/flate
compress/gzip
compress/lzw
compress/zlib

PS H:\Go\src\gopl\ch10> go list -f "{{.ImportPath}} -> {{join .Imports \"" \""}}" compress/...
compress/bzip2 -> bufio io sort
compress/flate -> bufio fmt io math math/bits sort strconv sync
compress/gzip -> bufio compress/flate encoding/binary errors fmt hash/crc32 io time
compress/lzw -> bufio errors fmt io
compress/zlib -> bufio compress/flate errors fmt hash hash/adler32 io

go list 命令对于一次性的交互查询和构建、测试脚本都非常有用。更多的参数信息,可以通过 go help list 来获取。