Go:包和 go 工具

引言

通过对关联特性分类,组成便于理解和修改的单元,使包与程序其他包保持独立,助力大型程序的设计与维护 。模块化让包可在不同项目共享、复用、发布及全球范围使用。

每个包定义不同命名空间作为标识符,关联具体包,便于为类型、函数等选取简洁名字,避免与程序其他部分冲突。包通过控制名字是否导出实现对包外可见性控制,限制包成员可见性,隐藏 API 辅助函数和类型,方便维护者修改包实现而不影响外部代码 ;限制变量可见性,使用者只能通过导出函数访问和更新变量。

修改文件需重新编译所在包及依赖包。Go 程序编译快,原因有三:一是源文件开头显式列出导入,编译器确定依赖性时无需读取处理整个文件;二是包依赖形成有向无环图,可独立甚至并行编译;三是 Go 包编译输出目标文件记录自身及依赖包导出信息,编译时读取导入目标文件不会超出范围。

导入路径

在 Go 语言里,每个包都由一个唯一的字符串标识,这个字符串就是导入路径,用于import声明中。

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

Go 语言规范未定义导入路径字符串的含义及确定方式,而是借助工具解决。go工具是 Go 程序员构建、测试程序的主要工具 ,不过也存在其他工具,像 Google 内部多语言构建系统有不同命名和包定位规则。

对于准备共享或公开的包,导入路径需全局唯一。为避免冲突,除标准库外,其他包导入路径通常以互联网域名(所属组织机构域名或存放包的域名)开头,方便查找包,如上述 Go 团队维护的 HTML 解析器和流行的第三方 MySQL 数据库驱动程序的导入路径。

包的声明

在 Go 源文件开头需进行包声明,其作用是当该包被其他包引入时,作为默认标识符(即包名) 。例如math/rand包中文件开头是package rand ,导入此包后,就能通过rand.Intrand.Float64等访问其成员。

package main

import (
    "fmt"
    "math/rand"
)

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

一般情况下,包名是导入路径的最后一段。像math/randcrypto/rand导入路径不同,但包名都是rand

例外情况

  1. 命令包:若包定义一条命令(可执行 Go 程序 ),无论导入路径如何,包名总是main ,用于告知go build生成可执行文件。
  2. 测试包:目录中以_test.go结尾的文件名,包名会有_test后缀 ,区分普通包和外部测试包,避免测试依赖的循环依赖。
  3. 依赖管理工具:部分依赖管理工具会在包导入路径尾部追加版本号后缀(如gopkg.in/yaml.v2 ),但包名不包含后缀,此例中包名为yaml

导入声明

Go 源文件在package声明后、首个非导入声明语句前,可包含零个或多个import声明。导入方式有两种:一是单个指定导入路径(如import "fmt"import "os" );二是用圆括号括起一次导入多个包(如import ("fmt" "os") ),后一种更常见 。

导入的包可通过空行分组,一般按领域和方面区分 ,导入顺序无严格要求,但习惯上每组按字母顺序排序,go fmtgoimports工具会自动分组排序,例如:

import (
    "fmt"
    "html/template"
    "os"
    "golang.org/x/net/html"
    "golang.org/x/net/ipv4"
)

重命名导入

当要将两个同名包(如math/randcrypto/rand )导入到第三个包时,需为其中一个指定替代名字避免冲突,即重命名导入,例如:

import (
    "crypto/rand"
    mrand "math/rand" // 通过指定不同名称mrand避免冲突
)

替代名字仅在当前文件生效,其他文件(即使同包内 )可按默认或另选替代名导入。重命名导入在无冲突时也有用,尤其处理自动生成代码中冗长包名,且使用统一缩写可规避局部变量冲突。

导入依赖与循环依赖

每个导入声明在当前包与导入包间建立依赖,若依赖形成循环,go build工具会报错。

空导入

通常,导入包却未在文件中引用包名,会引发编译错误。但有时导入包仅为利用其副作用,如执行包级别变量初始化表达式求值及init函数 。为避免 “未使用的导入” 错误,需使用重命名导入,用替代名字_ ,即空导入 ,导入内容为空白标识符,一般不可被引用,示例:import _ "image/png" // 注册PNG解码器

import (
    "database/sql"
    _ "github.com/lib/pq"           // 添加 Postgres 支持
    _ "github.com/go-sql-driver/mysql" // 添加 MySQL 支持
)
db, err := sql.Open("postgres", dbname) // OK
db, err := sql.Open("mysql", dbname)    // OK
db, err := sql.Open("sqlite3", dbname)  // 返回错误消息: unknown driver "sqlite3"

database/sql包为例,可按需空导入数据库驱动包(如_ "github.com/lib/pq"添加 Postgres 支持、_ "github.com/go - sql - driver/mysql"添加 MySQL 支持 ),实现按需加载驱动 。

包及其命名

包名命名

  • 简洁性:包名应简短,但不能过短致含义模糊。标准库中常见包如bufiobytesfmt 等都是简洁范例。
  • 可读性与无歧义:避免使用过于笼统的名称如util ,建议用更具体清晰的名字,像imageutilioutil 。同时,防止包名与常用局部变量名冲突,或让使用者不得不重命名导入。
  • 统一形式:标准包如byteserrorsstrings 用复数形式,避免覆盖预声明类型;go/types 这种形式可规避与关键字冲突。
  • 避免歧义:不要用有其他常见含义的词作包名,如 “temp” 易误解为 “temporary”(临时性的 )包名字从temptemperature 改为tempconv

包成员命名

  • 整体考量:设计包时要综合考虑包名和成员名协同工作,而非仅关注成员名。如bytes.Equalflag.Inthttp.Getjson.Marshal ,通过包名加成员名清晰标识操作所属。
  • 通用模式
    • 操作函数集合包:以strings包为例,提供一系列操作字符串函数,如strings.Indexstrings.NewReplacer 等,包名不会出现在函数名中,用户通过 “包名.函数名” 调用。
    • 单一类型包:像html/templatemath/rand 这类包,导出单一数据类型及其方法,一般有New函数创建实例,如rand.New 。因类型名可能与包名重复,此类包名通常较短。
    • 复杂任务包:如net/http ,虽包含众多类型和函数,但重要成员采用简单命名,如GetPostHandle 等,方便识别和使用。

go 工具

go工具用于下载、查询、格式化、构建、测试和安装 Go 代码包 。它集多种功能于一体,类似aptrpm包管理器,能查询包作者、计算依赖关系、从远程版本控制系统下载包 ;也是构建系统,可计算文件依赖,调用编译器、汇编器和链接器(虽不如标准 UNIX make命令完备 );还是测试驱动程序。

命令行接口

go工具命令行接口采用 “瑞士军刀” 风格,有十几个子命令 ,如getrunbuildfmt 等。运行go help可查看内置文档索引。常用命令及其功能,使用go help [command]获取子命令更多信息:

  • build:编译包及其依赖项。
  • clean:删除目标文件。
  • doc:显示包或符号的文档。
  • env:打印 Go 环境信息。
  • fmt:对包源文件运行gofmt进行格式化。
  • get:下载并安装包及其依赖项。
  • install:编译并安装包及其依赖项。
  • list:列出包。
  • run:编译并运行 Go 程序。
  • test:测试包。
  • version:打印 Go 版本。
  • vet:对包运行go tool vet

go工具高度依赖惯例以简化配置。比如,根据 Go 源文件所在目录能确定其所属包(每个目录对应一个包,包导入路径与工作空间目录结构对应 );根据包导入路径能找到目标文件存放目录及源代码仓库服务器 URL。

工作空间的组织

GOPATH环境变量

  • 作用与配置GOPATH是用户需配置的重要环境变量,用于指定 Go 工作空间的根目录 。切换工作空间时,更新其值即可。例如设置为$HOME/gobook ,命令为export GOPATH=$HOME/gobook ,然后可用go get gopl.io/...下载相关程序。
    • src:存放源文件,包所在目录相对于$GOPATH/src的路径就是包的导入路径 ,如gopl.io/ch1/helloworld 。一个工作空间的src下可包含多个代码版本控制仓库,如gopl.iogolang.org
    • pkg:构建工具存储编译后包的位置 ,会按操作系统和处理器架构分类存放(如darwin_amd64/ )。
    • bin:放置可执行程序,像helloworlddup

GOROOT环境变量

  • 作用与目录结构GOROOT指定 Go 发行版的根目录 ,提供所有标准库的包 。其目录结构与GOPATH类似,比如fmt包源代码存于$GOROOT/src/fmt 。一般用户无需设置GOROOTgo工具默认使用安装路径。

  • 其他环境变量go env命令可输出与工具链相关的环境变量及其值,包括已设置有效值和默认值的变量 。GOOS指定目标操作系统(如androidlinuxdarwinwindows ),GOARCH指定目标处理器架构(如amd64386arm )等 。。

包的下载

包的导入路径不仅用于在本地工作空间定位,还指明通过go get从互联网获取和更新包的位置 。go get命令可下载单个包,也能用...下载子树或仓库,并计算和下载初始包的所有依赖 ,如golang.org/x/net工具包及其依赖会出现在工作空间。

go get使用示例

  • $ go get github.com/golang/lint/golint :获取golint工具,用于检查 Go 源码风格问题。
  • $ $GOPATH/bin/golint gopl.io/ch2/popcount :使用golint检查gopl.io/ch2/popcount代码,指出缺少文档注释问题。

go get支持多个流行代码托管站点,如 GitHub、Bitbucket 和 Launchpad ,可向版本控制系统发送合适请求。对于不知名网站,可能需指明导入路径使用的版本控制协议(如 Git 或 Mercurial ),执行go help importpath获取更多细节。

go get创建的目录是远程仓库真实客户端,非简单文件副本,可使用版本控制命令查看本地编辑差异或更新版本 。以golang.org/x/net为例,在$GOPATH/src/golang.org/x/net目录下,可通过git remote -v查看 Git 远程仓库地址,包导入路径域名与 Git 服务器实际域名可能不同,是go工具特性,通过网页元数据重定向到实际托管地址的 Git 仓库。

更新选项与版本控制

  • -u开关:go get -u确保访问的所有包(包括依赖 )更新到最新版本,再进行构建和安装 ,适合项目开始阶段,但在需精准版本控制的部署项目中不适用。
  • vendor目录:在部署项目中,常添加vendor目录,构建本地必需依赖副本并谨慎更新 。Go 1.5 之前需改变包导入路径,多数新版go工具支持vendor目录,可通过go help gopath查看详细信息。

包的构建

go build命令

  • 基本功能go build用于编译命令行参数中的包。若包是库,编译结果会被舍弃;若包名为main ,则调用链接器在当前目录创建可执行程序,可执行程序名取自包导入路径最后一段 。
  • 目录与参数指定:每个目录对应一个包,可执行程序或 UNIX 命令需有自己的目录,如golang.org/x/tools/cmd/godoc 。包可通过导入路径或相对目录名指定(目录需以...开头 ),不提供参数时,使用当前目录。示例展示了在不同目录下使用go build编译gopl.io/ch1/helloworld包的情况 ,还提到可通过文件列表指定包(适用于小型程序和一次性实验 ),可执行程序名取自第一个.go文件名主体部分。
  • 构建特性
    • 依赖处理:默认构建所有需要的包及其依赖,编译后丢弃除最终可执行程序外的代码。项目规模增大时,重新编译依赖耗时增加 。
    • 即时运行:对于即抛型程序,go run可将构建和运行两步合并。
    • 构建标签:编译因操作系统和 CPU 体系结构而异,可通过构建标签(特殊注释 )实现更细粒度控制,如// +build linux darwin表示仅在 Linux 或 Mac OS X 系统构建时编译该文件,// +build ignore表示不编译 。

go install命令

  • go build相似,但会保存每个包的编译代码和命令 。编译后的包存于$GOPATH/pkg目录(与源文件src目录对应 ),可执行命令存于$GOPATH/bin目录 。go build -i可将包安装在独立于构建目标的地方。
  • go install保存的目录会根据GOOSGOARCH变量命名,如在 Mac 上golang.org/x/net/html编译后的文件存于$GOPATH/pkg/darwin_amd64

包的文档化

Go 语言强调良好的包 API 文档,每个导出的包成员声明及包声明本身都应使用注释描述目的和用途 。文档注释是完整语句,常以包名开头总结 。如fmt.Fprintf的文档注释示例:

// Fprintf根据格式说明符格式化并写入w
// 返回写入的字节数及可能遇到的错误
func Fprintf(w io.Writer, format string, a...interface{}) (int, error)

包声明前的文档注释视为整个包的文档注释,且只能有一个,较长注释可单独放doc.go文件 ,如fmt包注释超 300 行。好的文档简洁明了,行为明确时可不注释。

文档工具

  • go doc:可输出命令行指定内容(包、包成员、方法等 )的声明和完整文档注释 。
  • godoc:提供相互链接的 HTML 页面服务,包含不少于go doc的信息 。

内部包

在 Go 程序中,未导出的标识符只能在包内访问,导出的标识符可在任何地方访问,这是包封装的重要机制。

实际开发中,有时需要一种介于完全私有(包内)和完全公开(导出)之间的访问控制方式 。比如将大包拆分为多个小包时,不想向其他包暴露小包间关系;或在不导出情况下,在项目部分包间共享工具函数;又或试验新包而不将其永久作为公开 API ,此时可通过限定允许访问的包列表来实现。

go build工具对导入路径含internal路径片段的包特殊处理,这类包称为内部包 。内部包只能被位于internal目录父目录为根目录的包导入 。

net/http 
net/http/internal/chunked
net/ur1 
net/http/httputil

net/http/internal/chunked可被net/http/httputilnet/http导入,但不能被net/url导入,而net/url可导入net/http/httputil

包的查询

go list工具用于上报可用包的信息 。最简形式下,可判断包是否存在于工作空间,若存在则输出导入路径,如$ go list github.com/go-sql-driver/mysql

go list命令参数支持...通配符,可匹配包导入路径中的任意子串 。能用于枚举工作空间中的所有包($ go list ... )、指定子树中的所有包(如$ go list gopl.io/ch3/... )或特定主题相关包(如$ go list ...xml... ) 。

输出格式定制

  • JSON 格式-json标记可使go list以 JSON 格式输出包的完整记录 ,包含目录(Dir )、导入路径(ImportPath )、包名(Name )、文档(Doc )、目标文件路径(Target )等信息,还涉及是否来自GOROOTGoroot )、是否为标准库(Standard )、根目录(Root )、Go 源文件(GoFiles )、导入包列表(Imports )、依赖包列表(Deps )等字段 。
  • 模板定制-f标记允许通过text/template包的模板语言定制输出格式 。

参考资料:《Go程序设计语言》

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值