Golang - Package and Tool 以及相关命令使用

1. 包概述

1.1. 包系统设计的目的

简化大型程序的设计和维护工作,通过模块化分而治之的思想,将一组相关的特性放进一个独立的单元以便于理解和更新,在每个单元更新的同时保持和程序中其它单元的相对独立性。 这种模块化的特性允许每个包可以被其它的不同项目共享和重用,在项目范围内、甚至全球范围统一的分发和复用。

每个包都定义了一个自己的名字空间用于关联到一个特定的包,包通过给类型、函数等选择简短明了的名字,这样可以在使用它们的时候减少和其它部分名字的冲突。

每个包还通过控制包内名字的可见性和是否导出来实现封装特性。通过限制包成员的可见性并隐藏包API的具体实现,将允许包的维护者在不影响外部包用户的前提下调整包的内部实现。通过限制包内变量的可见性,还可以强制用户通过某些特定函数来访问和更新内部变量,这样可以保证内部变量的一致性和并发时的互斥约束。

1.2. 包的编译

当我们修改了一个源文件,我们必须重新编译该源文件对应的包和所有依赖该包的其他包,但该过程是十分快速的!

Go语言的闪电般的编译速度主要得益于三个语言特性:

  1. 导入的包在源文件显式声明,编译器无需解析代码分析推导文件所需依赖; (降低编译器的分析成本)
  2. 包的依赖关系是一个有向无环图,禁止包的环状依赖,每个包可以被独立编译,而且很可能是被并发编译;(依赖成有向无环图,可并发编译)
  3. 编译后包的目标文件不仅仅记录包本身的导出信息,同时还记录了包的依赖关系,编译器能直接导入包的目标文件,而不需要遍历所有依赖的的文件; (编译后的依赖包,可通过间接依赖复用,不用重复编译)

1.3 常见包的来源

2. 工作区域

项目开发中,唯一需要设置和关注的地方

export GOPATH=/data/go

// 下载gopl.io整包
go get gopl.io/...

3. go env - go环境变量相关

其中些许变量举例:

  • GOPATH: 工作区域
  • GOROOT: Go安装目录,自带的标准库包的位置,目录结构和GOPATH类似
  • GOOS:指定目标操作系统
  • GOARCH:CPU架构,如amd64、386或arm

4. go get - 下载单包或整包

  • 使用命令go get可以下载一个单一的包或者用...下载整个子目录里面的每个包
  • go get命令获取的代码是真实的本地存储仓库,而不仅仅只是复制源文件
  • go get命令加-u标识,可能引发代码兼容问题,需要注意;
  • 包的版本依赖,可以基于vendor进行控制,相关查看go help gopath查看vendor文档
// 下载单包,如果包已经在本地存在,那么代码将不会被自动更新
go get github.com/golang/lint/golint

// 下载最新的包,如果有新包,将更新到最新的版本
go get -u github.com/golang/lint/golint

5. go build - 编译命令行参数指定的每个包

  • 包名字是库: go build忽略输出结果,可用于检测包是可以正确编译的
  • 包名字是main: go build将调用链接器在当前目录创建一个可执行程序
  • 如果go build没有指定参数,那么默认指定为当前目录对应的包
  • 为了方便编译依赖的包,go build -i命令将安装每个目标所依赖的包。

5.1. build 二进制文件到cmd目录

cmd目录: 由于每个目录只包含一个包,因此每个对应可执行程序或者叫Unix术语中的命令的包,会要求放到一个独立的目录中。

例如用于提供Go文档服务的golang.org/x/tools/cmd/godoc命令

5.2. 在指定目录build

go build 生成的二进制文件,在当前目录!!

// 在包目录直接build
$ cd $GOPATH/src/gopl.io/ch1/helloworld
$ go build

// 在其他目录build
$ cd anywhere
$ go build gopl.io/ch1/helloworld

// 基于相对地址build
cd ./gopl.io/ch1/helloworld
mkdir cmd
cd cmd
go build ../

6. go run - 一次性运行的程序

go run命令实际上是结合了构建和运行的两个步骤,这一般只用于构建一些小程序或做一些临时性的实验。

如果是main包,将会以第一个Go源文件的基础文件名作为最终的可执行程序的名字。

// 指定运行go的源文件
go run main.go
// 或者
go run *.go

7. go install - 保存编译成果保存,二进制文件构建到GOBIN目录中

  • go install命令和go build命令很相似,但是它会保存每个包的编译成果,被编译的包会被保存到$GOPATH/pkg目录下,而不是将它们都丢弃;
  • go install命令将可执行程序,保存到$GOPATH/bin目录,很多用户会将$GOPATH/bin添加到可执行程序的搜索列表中;
  • go install命令将编译对应不同的操作系统平台和CPU架构,将编译结果安装到GOOS和GOARCH对应的目录:$GOPATH/pkg/darwin_amd64
  • 交叉构建:不同操作系统或CPU的交叉构建也是很简单的,只需要设置好目标对应的GOOSGOARCH,然后运行构建命令即可;
  • 指定平台才构建或者忽略构建
    • // +build linux darwin
    • // +build ignore
  • 更多参考:go doc go/build

7.1. 交叉编译示例 - cross.go

func main() {
    fmt.Println(runtime.GOOS, runtime.GOARCH)
}

$ go build gopl.io/ch10/cross
$ ./cross
darwin amd64

$ GOARCH=386 go build gopl.io/ch10/cross
$ ./cross
darwin 386

注意: go install命令和go build命令都不会重新编译没有发生变化的包;

8. go doc - 包文档

8.1. 文档查阅

查阅实体不区分大小写, go doc IMAGEgo doc image一致

// 包的声明与文档注释
go doc time
// 包成员
$ go doc time.Since
$ go doc time.Time
// 包方法
$ go doc time.Duration.Seconds
$ go doc time.Time.Format
// 在项目目录下,当前包文档
go doc

8.2. 文档撰写

Go语言的编码风格鼓励为每个包提供良好的文档。包中每个导出的包成员和包声明前都应该包含目的和用法说明的注释。

同时,好的文档并不需要面面俱到,文档本身应该是简洁但不可忽略的。

Go语言的风格更喜欢简洁的文档,并且文档也是需要像代码一样维护的;从标准库中发现很多更好的例子,效仿和学习

8.2.1. 示例

// 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)

8.2.2. 相关约定

  • 包文档
    • 对应的注释只能有一个,包注释可以出现在任何一个源文件中,多个会组合成一个包文档注释;
    • 如果包的注释内容比较长,一般会放到一个独立的源文件中:doc.go
  • 文档注释一般是完整的句子;
  • 第一行通常是摘要说明,以被注释者的名字开头;
  • 函数的参数或其它的标识符并不需要额外的引号或其它标记注明;
  • 显而易见的功能则并不需要注释;

8.3. godoc工具

  • godoc,它提供可以相互交叉引用的HTML页面,但是包含和go doc命令相同以及更多的信息。
  • godoc,在线服务 https://godoc.org
  • godoc,可以交互的示例程序
// 生成一个Web服务,查看http://localhost:8000/pkg
godoc -http :8000

9. go list - 包查询

  • go list命令仅可查看当前在工作区域中的包信息相关;
  • go list命令可以查询可用包的信息,检测包是否在工作区并打印它的导入路径;
  • go list命令的参数支持...,列出指定区域中的所有包;
  • go list命令可以获取每个包完整的元信息,可以通过-f,使用text/template模板输出指定字段
// 查看工作区是否有指定包
$ go list github.com/go-sql-driver/mysql
github.com/go-sql-driver/mysql

// 列出当前区域中的所有包
$ go list ./...
$ go list gopl.io/ch3/...

// 列出和指定关键词相关的包(包名需要字符串包含)
$ go list ...crawler...

// 列出包的元信息
$ go list -json         # 当前目录下,包元信息
$ go list -json hash    # 指定hash包元信息
$ go list -f '{{join .Imports "\n"}}'

// 子目录下所有包的导入包列表
$ go list -f '{{.ImportPath}} -> {{join .Imports " "}}' compress/...