包:Go 的代码组织法,从 fmt 到写自己的包¶
这一章讲 Go 程序怎么「搭积木」:每一块积木是一个包,包就是一个目录。 读完你会走完两条路——用别人的包(import 的四种写法), 和写自己的包(从
go mod init到在另一个程序里调用你的函数), 以及一道 Go 的硬墙:循环导入直接编译报错,没有任何商量。
1. 包是什么,为什么程序必须从包开始¶
书里给的定义:包就是一个组织多个 .go 文件的目录;包里的代码可以在多个 Go 程序间复用1。 命名规矩和系统目录一致(小写字母);带下划线前缀的文件名通常留给测试代码 或绑定特定平台的代码1。
两个必须记住的基本事实:
- 每个 Go 文件都属于某个包——没有「不属于包的代码」;
- main 包是唯一特殊的包:它告诉编译器「这是可执行程序,不是给人当库用的」, 编译产物是一个能直接运行的二进制文件。共享库不需要 main 包、也没有 main 函数2。
早期 Go 的磁盘布局还保留一个历史名词:工作区(workspace)根下分
src(源代码)、pkg(编译后的包对象)、bin(可执行文件)三个目录1。
今天只需要知道 go build 会自己管好这些,不用手工维护——Go 1.11 引入的
模块(见第 3 节)已经取代了手动摆目录的老办法,书里讲 src/GOPATH 的部分是新旧两套并存。
2. import:用别人的代码,四种写法¶
import 声明告诉编译器:这个文件要用到哪些包;包的位置用一个字符串(叫导入路径)指定3。
编译器拿到导入路径后的查找顺序,书里给的原文值得记住:
先看本地目录,再看 vendor 目录,再看 GOROOT/src,最后看 GOPATH/src3。
GOROOT 是 Go 自己的安装目录(装着编译器和标准库);
GOPATH 是你放项目和第三方库的工作区目录,默认在用户主目录4。
用 go env 命令可以看这两个变量的当前值4。
写法一:常规导入¶
import "fmt"
import "math"
// 或一次导多个:
import (
"fmt"
"math"
"github.com/mlowicki/package_name" // 第三方包的导入路径就是它的网址
)
导入了就有义务用:导而不用,编译报错——这个洁癖从变量推广到了包5。
写法二:别名导入¶
导入路径太长或想换个短名,用别名:import m "math",之后写 m.Pow(6, 2)5。
写法三:空白导入 _ "math"¶
只执行这个包的 init 函数,但不用它的任何函数6。
很多数据库驱动、图片格式包靠这个注册自己——你 import 它,只为触发它包里的
init 函数,之后别的代码就能用那些「凭空出现」的能力。书里给的理由:
导了不用要报错,_ 告诉编译器「我知道我在干什么」5。
写法四:点导入 . "math"¶
包里的导出名字直接进来,不用写前缀:Pow(-6, 2) 而不是 math.Pow(-6, 2)7。
书里立刻给了警告:点导入要慎用——两个包有同名函数时你分不清谁是谁,代码可读性也变差8。
3. 主线例子:从零写一个 factFib 包,再在别人手里跑¶
这是本章主线,四步走,照原书的完整例子9:
第 ① 步:建模块。 在项目目录里敲 go mod init myproject——
生成一个 go.mod 文件,这是 Go 模块的出生证明,项目从此有了名字和版本身份9。
第 ② 步:写包。 建目录 custom_package/factFib/,里面放两个文件,
两个文件的第一行都是 package factFib——同一个目录 = 同一个包:
// fact.go
package factFib
func Factorial(num int) int { // 大写 F!导出的,外面才能用
if num > 1 {
return num * Factorial(num-1)
}
return 1
}
// fib.go
package factFib
import "fmt"
func Fibonacci(num int) { // 大写 F!
f1, f2 := 0, 1
for i := 1; i <= num; i++ {
fmt.Println(f1)
f1, f2 = f2, f1+f2
}
}
注意每个函数名都是大写开头——回忆拆解 02 的铁律:小写就是包内私有, 写包的人必须主动「签字」把函数交给外面。
第 ③ 步:用包。 主程序按目录结构引用它:
// myprogram.go
package main
import (
"fmt"
"custom_package/factFib" // 导入路径 = 目录的相对路径
)
func main() {
fmt.Println(factFib.Factorial(10)) // → 3628800
fmt.Println("Fibonacci series is:")
factFib.Fibonacci(10) // → 0 1 1 2 3 5 8 13 21 34
}
第 ④ 步:跑。 go run myprogram.go,两行输出都出来9。
图说:包的边界就是目录的边界;导出规则就是大小写;导入路径就是目录路径。 三件事一句话:包 = 目录 + 首字母大小写管的门禁。
嵌套包¶
包里还能有包——custom_package/factFib/nested_package/,导入路径跟着目录层层写:
import "custom_package/factFib/nested_package"10。
书里补了一条版本信息:Go 1.17 之前,嵌套包得各自维护 go.mod;
1.17 之后,项目根一个 go.mod 管全部依赖,go mod tidy 会清理整个项目里没用的依赖11。
第三方包:导入路径就是网址¶
用别人发布在 GitHub 上的包,导入路径就是它的网址去掉协议头:
import "github.com/user_name/repo_name",工具链会把它下载到本地再编译12。
(下载命令是 go get,书里一带而过;今天模块模式下它已并入 go install 的体系——
机制不变:给路径,工具链取代码。)
4. 循环导入:Go 说「不」的地方¶
两个或多个包互相 import,叫循环导入;Go 语言不允许,编译器直接报错。13
书里给的例子:包 one 调包 two 的函数,包 two 又调包 one 的函数—— 编译器走进 one,发现要 two;走进 two,又发现要 one,原地打转,只能报错13。
解法书里给了两个,都值得记住:
- 抽一个新包:把双方都要用的东西挪进第三个包,两个包都去 import 它;
- 合并成一个包:分析下来如果这俩包谁也离不开谁,说明它们本来就是一个包, 拆开是多余的14。
判断(我们的,不是书里的): 「循环导入编译不过」看起来是限制, 实际是 Go 给所有项目划的一条架构底线:依赖必须能排出先后,代码才有读法。 被这条规矩逼出来的「抽公共包」动作,往往正是设计变好的时刻。 如果错,会错在: 大型项目里偶尔确实需要间接的循环(比如插件回调宿主—— 回调就是「把一个函数交给别人,事情发生时再由它反过来叫你」), 这时业界用接口反转方向的技巧绕过——机制上绕得开,但那是成熟团队的手艺, 不是新手该走的路。
5. top Go libraries:一张生态地图¶
章末列了一批常用第三方库15,按「什么时候会用到」重排:
| 库 | 干什么 | 你会在什么时候遇见 |
|---|---|---|
| Testify | 测试断言、mock、测试套件 | 写单元测试的第一天 |
| GORM | 数据库 ORM(用 Go 对象操作关系数据库) | 项目要连 MySQL/Postgres |
| Go kit | 微服务工具箱 | 搭服务化的系统 |
| cli | 写命令行程序(子命令、flag、帮助文本) | 写命令行工具 |
| glog | 日志(程序运行时逐条留下的记录) | 要给程序加可分级日志 |
| Vegeta | HTTP 压测 | 测接口扛不扛得住 |
| Kubernetes 客户端 | 调 Kubernetes API | 写云原生工具 |
| gocheck | 测试检查的增强版 | Testify 之外的测试选择 |
| Fuzzy | 模糊字符串匹配 | 做搜索建议 |
| color | 终端彩色输出 | 给 CLI 上颜色 |
这张表不是背名录,它的用途是:不知道某个领域该用什么库时,先在这里查有没有 业界的默认答案。
6. 作者的判断与证据¶
| 书里的说法 | 性质 |
|---|---|
| 循环导入「任何 Go 程序都不需要」 | 作者的判断,偏强;见我们的判断块 |
| 点导入「应谨慎使用」 | 语言社区的通行共识 |
| GOROOT 不建议手动改 | 属实,改了会让编译器和标准库版本错位4 |
7. 边界与局限¶
- GOPATH 工作区模型已是旧时代的遗物:今天所有新项目都用模块(go.mod), 书的这一章是新旧两套混讲,读的时候以「go.mod + 模块」为准;
- go.mod 的语义(版本号、依赖选择、代理)本章没有展开,
只给了
go mod init一个命令; - 「Top libraries」名单带 2025 年成书时的口味,库的新旧更替很快, 用法以各库官方文档为准。
8. 可带走的¶
- 包 = 目录;每个 .go 文件第一行声明它属于哪个包;
- main 包 = 可执行程序;没有 main 包的代码是给别人当库用的;
- import 查找顺序:本地 → vendor → GOROOT/src → GOPATH/src;
- 导而不用 = 编译错;不用可以用
_空白导入(为执行 init); - 别名导入换短名,点导入去前缀——点导入要慎用;
- 写自己的包:目录 +
package 名字+ 导出函数大写开头; go mod init 项目名建立模块;1.17 之后一个 go.mod 管全部嵌套包;- 导入路径就是目录相对路径,第三方包就是它的网址;
- 循环导入编译直接报错:抽第三个包,或合并成一个包;
- 库不知道用什么,先查 Testify/GORM/cli/glog 这张生态表。
9. 原文地图¶
| 主题 | 原书章 | 原文位置 |
|---|---|---|
| 包的定义与工作区三目录 | Packages in Go | text/15-fm-learning-go-programming.txt:61(搜「directory/folder」) |
| main 包与共享库 | Packages in Go | text/15-fm-learning-go-programming.txt:67(搜「shared library」) |
| fmt 包 | Packages in Go | text/15-fm-learning-go-programming.txt:73(搜「formatted I/O」) |
| import 与查找顺序 | Packages in Go | text/15-fm-learning-go-programming.txt:85(搜「import path」) · text/15-fm-learning-go-programming.txt:88(搜「vendor directory」) |
| 导出标识符两条规则 | Packages in Go | text/15-fm-learning-go-programming.txt:121(搜「exported identifiers」) |
| go build 与可执行文件 | Packages in Go | text/15-fm-learning-go-programming.txt:151(搜「go build」) |
| 别名导入 | Packages in Go | text/15-fm-learning-go-programming.txt:154(搜「alias of a package」) |
| 空白导入与 init | Packages in Go | text/15-fm-learning-go-programming.txt:187(搜「import _ "math"」) · text/15-fm-learning-go-programming.txt:190(搜「init function」) |
| 点导入及警告 | Packages in Go | text/15-fm-learning-go-programming.txt:193(搜「dot import」) · text/15-fm-learning-go-programming.txt:232(搜「naming conflicts」) |
| GOROOT / GOPATH | Packages in Go | text/15-fm-learning-go-programming.txt:253(搜「JAVA_HOME」) · text/15-fm-learning-go-programming.txt:256(搜「workspace directory」) |
| go mod init 与自定义包 | Packages in Go | text/15-fm-learning-go-programming.txt:262(搜「go mod init」) · text/15-fm-learning-go-programming.txt:361(搜「factFib.Factorial」) |
| 嵌套包与 Go 1.17 | Packages in Go | text/15-fm-learning-go-programming.txt:415(搜「package inside a package」) · text/15-fm-learning-go-programming.txt:466(搜「1.17 and later」) |
| 第三方包导入 | Packages in Go | text/15-fm-learning-go-programming.txt:472(搜「cloning remote code」) |
| 循环导入与两种解法 | Packages in Go | text/15-fm-learning-go-programming.txt:481(搜「not allowed」) · text/15-fm-learning-go-programming.txt:587(搜「merge both the packages」) |
| top Go libraries | Packages in Go | text/15-fm-learning-go-programming.txt:644(搜「rich set of libraries」) |
-
出处:「Packages in Go」第 61 段(
text/15-fm-learning-go-programming.txt:61,搜「directory/folder」)。 ↩↩↩ -
出处:「Packages in Go」第 67 段(
text/15-fm-learning-go-programming.txt:67,搜「shared library」)。 ↩ -
出处:「Packages in Go」第 85、88 段(
text/15-fm-learning-go-programming.txt:85,搜「import path」;text/15-fm-learning-go-programming.txt:88,搜「vendor directory」)。 ↩↩ -
出处:「Packages in Go」第 244、253、256 段(
text/15-fm-learning-go-programming.txt:244,搜「go env」;text/15-fm-learning-go-programming.txt:253,搜「JAVA_HOME」;text/15-fm-learning-go-programming.txt:256,搜「workspace directory」)。 ↩↩↩ -
出处:「Packages in Go」第 121、184 段(
text/15-fm-learning-go-programming.txt:121,搜「exported identifiers」;text/15-fm-learning-go-programming.txt:184,搜「null container」)。 ↩↩↩ -
出处:「Packages in Go」第 190 段(
text/15-fm-learning-go-programming.txt:190,搜「init function」)。 ↩ -
出处:「Packages in Go」第 193–229 段(
text/15-fm-learning-go-programming.txt:217,搜「Pow(-6, 2)」;text/15-fm-learning-go-programming.txt:229,搜「dot import: 36」)。 ↩ -
出处:「Packages in Go」第 232 段(
text/15-fm-learning-go-programming.txt:232,搜「naming conflicts」)。 ↩ -
出处:「Packages in Go」第 259–409 段(
text/15-fm-learning-go-programming.txt:262,搜「go mod init」;text/15-fm-learning-go-programming.txt:274,搜「Factorial(num int)」;text/15-fm-learning-go-programming.txt:361,搜「factFib.Factorial」;text/15-fm-learning-go-programming.txt:376,搜「3628800」)。 ↩↩↩ -
出处:「Packages in Go」第 415–460 段(
text/15-fm-learning-go-programming.txt:448,搜「nested_package」)。 ↩ -
出处:「Packages in Go」第 463、466 段(
text/15-fm-learning-go-programming.txt:466,搜「1.17 and later」)。 ↩ -
出处:「Packages in Go」第 472–475 段(
text/15-fm-learning-go-programming.txt:475,搜「user_name/repo_name」)。 ↩ -
出处:「Packages in Go」第 481、581 段(
text/15-fm-learning-go-programming.txt:481,搜「not allowed」;text/15-fm-learning-go-programming.txt:581,搜「cyclic dependency」)。 ↩↩ -
出处:「Packages in Go」第 584–587 段(
text/15-fm-learning-go-programming.txt:587,搜「merge both the packages」)。 ↩ -
出处:「Packages in Go」第 644–779 段(
text/15-fm-learning-go-programming.txt:644,搜「rich set of libraries」)。 ↩