Packages and the Go Tool


你可以通过 http://godoc.org 来找到社区发布的 packages。

1. Introduction

当我们改变一个文件时,我们必须重新编译文件的 package 和潜在地编译所有依赖于它的 package。Go 的编译显著的快于大部分的其他编译语言,编译的 Go package 的对象文件记录导出信息,导出信息不仅仅是为了 package 自己,也是为了它的依赖。当编译一个 package 时,编译器必须读取每个导入 package 的对象文件,但不必查看这些文件之外的东西。

2. Import Paths

每一个 package 都被一个唯一的字符串标识,称为 import path。导入路径是出现在 import 声明中的字符串。

import (
	"fmt"
	"math/rand"
	"encoding/json"
	"golang.org/x/net/html"
	"github.com/go-sql-driver/mysql"
)

对于你打算共享或发布的 package,导出路径需要全球唯一。为了避免冲突,所以的 packages,除了来自标准库的 package,应该以组织的网络域名作为开头,这也使找到 packages 成为可能。

3. The Package Declaration

每个 Go 源文件在开头都需要一个 package 声明。它的主要目的是在它其他的 package 导入时,用于决定默认的 package 标识符,称为 package name

package main
import (
	"fmt"
	"math/rand"
)

func main() {
	fmt.Println(rand.Int())
}

按照惯例,package name 是 import path 的最后一段,结果,两个 package 可能有相同的 package name,即使它们的 import path 是必定不同的。

对于 “last segment” 的惯例,有三个主要的例外。第一个是定义一个 command(一个可执行的 Go 程序)的 package 总是有名字 main,无论 package 的 import path。这是对 go build 的信号,让它调用 linker 来创建一个可执行文件。

第二个例外是一些目录中的文件的 package name 中有后缀 _test ,如果文件名是以 _test.go 结束的话。这样的目录可能定义两个 package:一个普通的 package,加上另一个 package,被称为 external test package_test 后缀提示 go test 必须两个 package 都要构建,且它表明哪个文件属于某个 package。额外的测试包被用于避免在由测试的依赖产生的 import graph 中循环。

第三种例外是一些用于依赖管理的工具会为 package 导入路径追加版本号后缀,比如 “gopkg.in/taml.v2”。package name 不包含后缀,所以在这种情况下,它只有 yaml。

4. Import Declarations

Go 文件可能有一个或多个导入声明,它们处于 package 声明之后,任何非导入声明之前。导入声明指定 pakcage 的导入路径。

import "fmt"
import "os"

等价于

import (
	"fmt"
	"os"
)

导入的 package 可能通过引入 blank lines 进行分组,这些组通常表示不同的域。

import (
	"fmt"
	"html/template"
	"os"

	"golang.org/x/net/html"
	"golang.org/x/net/ipv4"
)

如果我们需要导入两个名字相同的 package,比如 math/rand 和 crypto/rand,到第三个 package 中,导入声明必须至少为他们中的一个指定一个其他的名字以避免冲突。这被称为 renaming import

import (
	"cypto/rand"
	mrand "math/rand"	//alternative name mrand avoids conflict
)

选择的名字只影响导入的文件。其他的文件,甚至是在同一个 package 中的文件,可能使用包的默认名字或其他名字来导入它。

重命名导入可能在没有冲突的时候也十分有用。如果导入的 package 是不易于使用的,比如在一些自动生成代码的情况下,一个缩写的名字可能更便利。同样,使用短名字时也要注意避免冲突。选择一个其他的名字能够在使用常用的局部变量名字时帮助避免冲突。例如,在一个文件中有很多局部变量命名为 path,我们必须在导入标准库 path 时使用 pathpkg。

每个导入声明在当前 package 和导入的 package 中建立依赖。如果有循环依赖,Go build 工具会报告错误。

5. Blank Imports

将一个 package 导入一个文件中,但不引用它是一个错误。然而,有时我们必须导入一个 package,为了 package-level 的变量的初始化器表达式的求值和它的 init 函数的执行。为了避免 “unused import” 错误,我们必须使用重命名导入,其选择的名字是 _,空白标识符。如平常一样,空白标识符不能被引用:

import _ "image/png"	// register PNG decoder

这被称为 blank import。主程序在编译器可以通过空白导入额外的 package 来激活可选的特性,这是一个常见的用法。
gopl.io/ch10/jpeg


// The jpeg command reads a PNG image from the standard input
// and writes it as a JPEG image to the standard output.
package main
import (
	"fmt"
	"image"
	"image/jpeg"
	_ "image/png" // register PNG decoder
	"io"
	"os"
)
func main() {
	if err := toJPEG(os.Stdin, os.Stdout); err != nil {
		fmt.Fprintf(os.Stderr, "jpeg: %v\n", err)
		os.Exit(1)
	}
}

func toJPEG(in io.Reader, out io.Writer) error {
	img, kind, err := image.Decode(in)
	if err != nil {
		return err
	}
	fmt.Fprintln(os.Stderr, "Input format =", kind)
	return jpeg.Encode(out, img, &jpeg.Options{Quality: 95})
}

注意 import/png 的空白导入。没有这一行,程序依然正常编译和链接,但是不能识别和解码 PNG 格式的输入。

它是这样工作的。标准库提供 GIF,PNG,和 JPEG 的解码,用户也可以提供其他格式,但是为了保持可执行文件很小,解码器不会包含在应用中,除非显式请求。image.Decode 函数咨询支持格式的表格。表中每一条目支持 4 样东西:格式的名字;以此方式编码的所有图片的前缀,以字符串形式,用于检测编码;解码编码图像的函数 Decode;另一个仅解码图像的 metadata 的函数 DecodeConfig,比如解码图像的大小和 RGB 空间。通过调用 image.RegisterFormat 一个条目被添加到表中,通常来每种格式的支持 package 的 自初始化器之中,正如在 image/png 中的这个:

package png
func Decode(r io.Reader) (image.Image, error)
func DecodeConfig(r io.Reader) (image.Config, error)

func init() {
	const pngHeader = "\x89PNG\r\n\x1a\n"
	image.RegisterFormat("png", pngHeader, Decode, DecodeConfig)
}

影响是应用仅需要 package 的 blank-import 来格式化它,以使 image.Decode 函数能够解码它。

6. Packages and Names

本节讨论一些关于 package 及其成员的命名在 Go 中的独特惯例。

当创建一个 package 时,我们保持它的名字尽量短,但不至于意义不明。标准库中最常用的 package 是 bufio,bytes,flag,fmt,http,io,json,os,sort,sync,和 time。

包命名尽量可描述和意义明确。

同时考虑 package 名字和成员名字来表示成员的含义,比如:

bytes.Equal flag.Int http.Get json.Marshal

7. The Go Tool

Go Tool 被用于下载,查询,格式化,构建,测试,和安装 package。

$ go
...
	build 		compile packages and dependencies
	clean 		remove object files
	doc 		show documentation for package or symbol
	env 		print Go environment information
	fmt 		run gofmt on package sources
	get 		download and install packages and dependencies
	install 	compile and install packages and dependencies
	list 		list packages
	run 		compile and run Go program
	test 		test packages
	version 	print Go version
	vet 		run go tool vet on packages
Use "go help [command]" for more information about a command.
...

7.1 Workspace Organization

大部分用户一直需要的唯一配置是 GOPATH 环境变量,它指定了 workspace 的 root。当转换到不同的 workspace,用户更新 GOPATH 的值。例如,我们将 GOPATH 设置为 $HOME/gobook

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

调用上述命令后,GOPATH的目录结构为:

GOPATH/
	src/
		gopl.io/
			.git/
			ch1/
				helloworld/
					main.go
				dup/
					main.go
				...
		golang.org/x/net/
			.git/
			html/
				parse.go
				node.go
				...
	bin/
		helloworld
		dup
	pkg/
		darwin_amd64/
				...

GOPATH 有三个子目录。src 目录保存源码,存在于该目录中的每个 package 相对于 $GOPATH 的路径就是它的导入路径,比如 gopl.io/ch1/helloworld。单个 GOPATH 工作空间中包含多个版本控制的仓库,它们在 src 目录下,比如 gopl.io 或 golang.org。pkg 子目录是编译工具存储被编译的 package 的地方,bin 子目录包含可执行的程序,比如 helloworld。

第二个环境变量是 GOROOT,指定 Go 发行版的根目录,它提供了标准库的所有 package。GOROOT下的目录结构与 GOPATH类似,用户永远不需要设置 GOROOT,因为 Go tool 将默认使用它被安装的位置。

go env命令打印与工具链相关的环境变量的有效值,包括确实部分的默认值。GOOS 指定目标操作系统,GOARCH指定目标处理器架构。虽然 GOPATH 是唯一你必须设置的变量,其他的偶尔出现在我们的解释中。

$ go env
GOPATH="/home/gopher/gobook"
GOROOT="/usr/local/go"
GOARCH="amd64"
GOOS="darwin"
...

7.2 Downloading Packages

当使用 Go tool 时,package 的导入路径不仅仅表明在本地的 workspace 中如何找到它,也表示如果在 Internet 中找到它,这样 go get 就能检索和更新它了。

go get 命令能够使用 … 符号来下载单个 package 或者整个子树或者整个仓库。工具也会下载 package 的所有依赖。

如果你指定 -u flag,go get 将保证所有它访问的 packages,包括依赖的 package 都被更新到最新版本,然后进行构建和安装。没有该 flag,本地存在的 packages 不会被更新。

7.3 Building Packages

go bulid 命令编译每个参数 package。如果 package 是一个 libraries,结果是弃置的,仅为了检测 package 是否有编译错误。如果 package 被命名为 main,go build 触发 linker 在当前路径创建一个可执行文件,可执行未见得名字取自 package 的导入路径的最后一段。

$ cd $GOPATH/src/gopl.io/ch1/helloworld
$ go build

等价于

$ cd anywhere
$ go build gopl.io/ch1/helloworld

等价于

$ cd $GOPATH
$ go build ./src/gopl.io/ch1/helloworld

但不等价于

$ cd $GOPATH
$ go build src/gopl.io/ch1/helloworld
Error: cannot find package "src/gopl.io/ch1/helloworld".

默认情况下,go build 命令构建请求的 package 和所有依赖,然后丢弃所有的编译代码,除了最终的可执行文件,如果有的话。

go install 命令与 go build 命令十分类似,除了它保存每个 package 和 command 的编译代码,不是将它们丢弃。编译的 package 被存放在 $GOPATH/pkg 目录中,可执行文件被存放在 $GOPATH/bin 目录中。go build -i 安装构建目标依赖的 packages。

因为编译的 package 因平台和架构不同而不同,go install 将它们保存在子目录之中,目录名由 GOOS 和 GOARCH 环境变量组合而成,比如 $GOPATH/pkg/darwin_amd64

7.4 Documenting Packages

Go doc comment 总是完整的语句,第一句通常是总结,以被声明的名字开始。函数参数和其他标识符不使用引号或标记特殊标识。

// Fprintf formats according to a format specifier and writes to w.
// It returns the number of bytes written and any write error encountered.
func Fprintf(w io.Writer, format string, a ...interface{}) (int, error)

pacjage 声明之前的注释被认为是整个 package 的 doc comment,必须仅有一个,虽然它可能出现在任何文件中。更长的 package 注释可能有自己的文件,文件通常被称为 doc.go。

go doc 工具打印命令行中指定的实体的声明和文档注释,它可能是一个 package:

$ go doc time
package time // import "time"
Package time provides functionality for measuring and displaying time.
const Nanosecond Duration = 1 ...
func After(d Duration) <-chan Time
func Sleep(d Duration)
func Since(t Time) Duration
func Now() Time
type Duration int64
type Time struct { ... }
...many more...

或者 package 成员:

$ go doc time.Since
func Since(t Time) Duration
	Since returns the time elapsed since t.
	It is shorthand for time.Now().Sub(t).

或者一个 method:

$ go doc time.Duration.Seconds
func (d Duration) Seconds() float64
	Seconds returns the duration as a floating-point number of seconds.

工具不需要完整的导入路径或正确的标识符大小写:

$ go doc json.decode
func (dec *Decoder) Decode(v interface{}) error
	Decode reads the next JSON-encoded value from its input and stores
	it in the value pointed to by v.

第二个工具,名为 godoc,服务于交叉链接的 HTML 页面,页面提供与 go doc 相同的信息甚至更多。 godoc 服务器位于 https://golang.org/pkg,它覆盖了标准库。

7.5 Internal Packages

为了解决这些需求,go build 工具特殊对待一个 package,如果这个 package 的导出路径包含 internal 的路径段。这样的 package 被称为 internal packages。一个 internal package 仅能被以 internal 目录的父目录为根目录的其他 package 导入。例如,net/http/internal/chunked 能被导入到 net/http/httputil 或 net/http,但是不能导入到 net/url,然而,net/url 能被导入到 net/http/httputil。

7.6 Query Package

**go list ** 工具报告可用的 package 的信息。在最简单的格式中,go list 测试包是否在 workspace 中存在,如果存在打印它的导入路径:

$ go list github.com/go-sql-driver/mysql
github.com/go-sql-driver/mysql

go list 的参数可能包含 “…” 通配符,它匹配任何 package 导入路径的子字符串。 我们可以使用它枚举 Go workspace 中的所有 package:

$ go list ...
archive/tar
archive/zip
bufio
bytes
cmd/addr2line
cmd/api
...many more...

或者在特定子树内:

$ go list gopl.io/ch3/...
gopl.io/ch3/basename1
gopl.io/ch3/basename2
gopl.io/ch3/comma
gopl.io/ch3/mandelbrot
gopl.io/ch3/netflag
gopl.io/ch3/printints
gopl.io/ch3/surface

或者相关于特定主题:

$ go list ...xml...
encoding/xml
gopl.io/ch7/xmlselect

go list 命令包含每个 package 的完整 metadate,json 格式:

$ go list -json hash
{
	"Dir": "/home/gopher/go/src/hash",
	"ImportPath": "hash",
	"Name": "hash",
	"Doc": "Package hash provides interfaces for hash functions.",
	"Target": "/home/gopher/go/pkg/darwin_amd64/hash.a",
	"Goroot": true,
	"Standard": true,
	"Root": "/home/gopher/go",
	"GoFiles": [
		"hash.go"
	],
	"Imports": [
		"io"
	],
	"Deps": [
		"errors",
		"io",
		"runtime",
		"sync",
		"sync/atomic",
		"unsafe"
	]
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值