函数、包与模块化 — 从一堆函数到一座有层次的工程¶
这一章讲什么: Go 函数的全部形态;程序怎么从「一个 main.go」长成「一堆各司其职的包」; 依赖怎么管版本、包怎么发布;再加上文件读写和日志这两样每个程序都要的活。 它在全书链条里的位置: 第 02 章给了你零件,这一章给你组织零件的图纸。 第 06 章的数据层、第 07 章的 API、第 10 章的微服务,全是按这一章的包结构搭的。 这一章也是「Go 没有类,靠什么做大项目」这个问题的正式回答。
1. 顶层全景:一张项目的生长图¶
一个 main.go(第 02 章的样子)
│ 函数变多之后,按职责切开
▼
main.go + mathutils/ + handlers/ ← 每目录一个包
│ 包之间要控制「谁能看谁」
▼
导出(大写)⇄ 包外 / 不导出(小写)⇄ 仅包内
│ 依赖第三方代码
▼
go.mod 记名字和版本,go.sum 记校验和
│ 要让别人也能用
▼
git tag v1.0.0 → 别人 go get 你的包
主线例子(贯穿本章): 把第 02 章那个「用户注册」小程序长成一座小工程。 每节推进一层:先把处理逻辑抽成函数(§2),再把函数按职责分进两个包(§3–5), 最后用工具链建、测、发(§6)。这个生长过程是我们按原书各节的示例串起来的 (原书每节各举小例,没有这样一个连续工程);每节的原书依据在脚注。
2. 函数:Go 函数的全部形态¶
2.1 参数与多返回值¶
Go 函数的参数是值传递:函数拿到的是实参的一份拷贝,改了不影响调用者;
要改调用者的变量,就传指针(第 02 章的 &)1。类型写在参数名后面,
同类型相邻参数可以合并:func add(a, b int)1。
最有特色的是多返回值——一个函数可以一次返回好几个值:
原书点明这个形态 mostly 为错误处理而生:一个返回值位给结果、一个给错误, 调用方每次都不得不当场处理,这就是第 04 章「errors as values」的语法基础2。 还支持命名返回值(在签名里给返回值起名字),但原书提醒要节制使用, 免得可读性反受其害2。
2.2 递归:能写,但要知道代价¶
递归(recursion)是函数调用自己。任何递归都要有两件:基例(什么时候停)和 递归步(问题怎么变小)3。原书的例子是阶乘:
func factorial(n int) int {
if n == 0 || n == 1 {
return 1 // 基例
}
return n * factorial(n-1) // 递归步
}
factorial(5) 的展开:5×factorial(4) → 5×4×factorial(3) → … → 到基例 1,
再一路乘回来,得 1203。
边界(原书明写):Go 不做尾调用优化——递归深了一层就占一层栈,不会自动变循环。
所以树遍历、分治这类「天然递归」的问题用它,性能敏感的场合优先写循环4。
原书在本章末尾还演示了一个 AI 重构的典型场景:把指数时间(运行次数随输入规模幂次爆炸增长,输入加一、耗时翻倍的级别)的递归斐波那契
(Fib(n-1)+Fib(n-2),同样的子问题反复重算)改写成 O(n) 的循环版——
这是「AI 找得出,但你要验得出」的好例子(§8 细讲)5。
2.3 变参函数:个数不定的参数¶
变参(variadic)函数用 ... 声明,调用时可以传任意个同类型参数,
函数内部收到的是一个切片:
规则一条:变参必须放在参数表最后。标准库的 fmt.Println 和 append 就是变参函数6。
2.4 匿名函数:没有名字的函数¶
匿名函数是不起名的函数,可以存进变量、当参数传、立即执行:
greet := func(name string) string {
return "Hello, " + name
}
ProcessValues([]int{1, 2, 3}, func(n int) { // 当参数传
fmt.Println("Processing:", n)
})
原书说它的价值是「行为就在用的地方定义」——回调(把一个函数当参数传进去,让对方在合适的时机替你调)、事件处理、中间件(第 07 章)、
goroutine 里都要用它7。变参和匿名还能叠用:func(parts ...string) 的匿名函数8。
主线例子第 ① 步: 注册程序的校验逻辑抽成函数,主流程只看见三步:
func ValidateUser(name string, age int) error { /* 见第 04 章 */ }
func ProcessUserData(name string, age int) string {
return strings.ToUpper(name) + fmt.Sprintf("_%d", age)
}
(校验函数返回 error 类型——第 04 章的主角,这里只当「可能出错的标记」用。)
3. 包:Go 组织代码的单位¶
3.1 目录、包名、import¶
硬规则:一个目录一个包(极少例外),目录名通常就是包名9。 原书给的标准项目布局,我们主线例子照抄:
project/
main.go → package main
utils/
strings.go → package utils
numbers.go → package utils
handlers/
auth.go → package handlers
users.go → package handlers
utils 目录下两个文件同属 utils 包——同包的文件彼此可见,不用 import。
import 有三种写法,原书都给了10:
| 写法 | 效果 | 原书的建议 |
|---|---|---|
import m "math" |
起别名 | 长包名可用 |
import . "fmt" |
不用限定名直接调 | 不推荐,看不出函数来自哪 |
import _ "driver" |
只为执行它的 init,不引用名字 | 数据库驱动插件的标准用法 |
3.2 大写出口,小写私有:没有 public/private 关键字¶
Go 用大小写控制可见性,这是全章最要紧的一条规则11:
- 名字大写开头 → 导出(exported),包外可用;
- 名字小写开头 → 不导出(unexported),只有包内可见。
package mathutils
var Pi = 3.14 // 导出:别的包能 mathutils.Pi
var internalPi = 3.14 // 不导出:出了这个包不存在
func Add(a, b int) int { return a + b } // 导出
func subtract(a, b int) int { return a - b } // 不导出
没有 public/private 关键字,看首字母就知道。这条规则强迫你在设计 API 时想清楚
「什么是对外承诺」——导出的名字就是包的合同,改它要负兼容责任(§6 的版本管理)。
主线例子第 ② 步: 把校验和数据处理挪进 validation 包(只导出 ValidateUser),
把文件写入挪进 storage 包(导出 WriteToFile)。main 包只留流程编排。
3.3 包初始化:一个确定的次序¶
程序启动时,Go 按一个确定的、可预测的次序初始化包12:
① 递归装入依赖:你 import 的包先初始化(import 的包还有依赖就再往前)
例:main → handlers → validation,初始化次序是 validation → handlers → main
② 求值包级变量:var config = loadConfig() 这类,按依赖关系排
③ 执行 init() 函数:每个包可有多个,按文件顺序执行,每个包只跑一次
init() 没有参数没有返回值,不能被人调用——它在包加载时自动跑13。
典型用途:注册处理器、初始化连接、预热缓存。原书同时给了警告:init 滥用会让
程序流程难追,留给出乎意料必须做的初始化13。数据库驱动那个 import _ "driver"
就是靠 init 完成自注册的——这是它唯一被认可的「隐身副作用」。
4. 依赖管理:go.mod 与「最小版本选择」¶
模块(module)是比包更大一级的单位:一个仓库根目录一个 go.mod 文件,
里面三样东西——模块名、Go 版本、依赖清单14:
日常就三条命令15:
| 命令 | 干什么 |
|---|---|
go mod init <名字> |
新项目开局 |
go get 包@版本 |
加/升依赖 |
go mod tidy |
删掉不用的、补上缺的 |
为什么可复现: go.sum 文件存每个依赖版本的加密校验和,
构建时逐一核对——别人拿到你的代码,构建出的二进制和你的一致16。
go.mod 和 go.sum 两个文件必须提交版本库(版本库:Git 这类版本控制工具管理的代码仓库,提交一次就是存一份带历史的快照)。
「最小版本选择」(minimal version selection): 别的包管理器遇到两个依赖要求 同一库的不同版本,会尽力挑「最新兼容版」;Go 选满足所有约束的最小版本。 原书说这避免了依赖爆炸和意料之外的破坏17。一个实际后果:升级是显式的, 没有「装 A 顺手把 B 升了半代」这种事。
主线例子第 ③ 步: 注册程序想给每个用户生成 ID,引入 github.com/google/uuid:
(具体版本号随时间变,以执行时为准。)
replace 指令(本地开发):replace github.com/example/math => ../local-math
把依赖临时换成本地路径,改着两个模块联调而不用先发布18。
语义化版本(SemVer)与发布:版本号 v主版本.次版本.修订号——
主版本=不兼容改动、次版本=向后兼容新功能、修订=bug 修复19。
v2 及以上的主版本要写进模块路径(module github.com/you/proj/v2),
从源头防止「不小心升级了不兼容版」20。
5. 自建包:建、测、发全流程¶
原书拿一个 mathutils 包走完生命周期,主线例子照走一遍21:
建。 目录 + 同包名文件;别的包通过模块路径(不是相对路径)import:
测。 测试文件以 _test.go 结尾,函数名 Test 开头,标准库的 testing 包,零依赖:
func TestAdd(t *testing.T) {
result := Add(20, 10)
if result != 30 {
t.Errorf("Expected 30, got %d", result)
}
}
go test ./... 跑全部包的测试。原书的测试哲学:短、独立、报错信息清楚、
测内部逻辑用同包测试、只测公开 API 就用 mypkg_test 外部测试包22。
发。 GitHub 建仓 → git tag v1.0.0 → git push origin v1.0.0 →
别人 go get github.com/example/mathutil@v1.0.0。中间是 Go 官方的模块代理
在缓存和加速下载,并保证已发布版本不可变23。公开库的标准文件:go.mod、
源码、_test.go、README.md、LICENSE24。
包设计的四条准则(原书汇总,值得背)25:
- 单一职责:config 管配置、auth 管认证、storage 管数据库;避免
utils这种 「杂物抽屉」; - 名字短、小写、一个词:
json、cache好,stringhelpers、processingmodulev2差; - 禁止环:
p1importp2、p2又 importp1—— 编译器直接报错,解法是 抽一个双方都依赖的接口(§8 的 AI 重构例就是这个); - 依赖单向流动:上层可以知道下层,反过来不行。
6. 文件处理与日志:os、io、bufio、log¶
6.1 三层分工¶
| 包 | 管什么 | 典型调用 |
|---|---|---|
os |
与操作系统打交道:开、建、删、元信息 | os.Open、os.Create、os.OpenFile |
io |
定义流的抽象接口 | io.Reader、io.Writer、io.Copy |
bufio |
给流包缓冲,减少系统调用 | bufio.NewReader、bufio.Scanner、bufio.NewWriter |
io.Reader/io.Writer 是接口(第 02 章):「能读字节的东西」/「能写字节的东西」。
文件、网络连接、内存缓冲区都实现它们,所以一段 io.Copy(dst, src) 对三种源都管用26。
三件套配合的典型流程27:
src, _ := os.Open("input.txt")
dst, _ := os.Create("output.txt")
buffered := bufio.NewWriter(dst)
io.Copy(buffered, src)
buffered.Flush() // 别忘了:缓冲区的内容要 Flush 才真正落盘
defer 提前出现一下: defer file.Close() 在打开文件后紧跟一行,作用是
「函数退出时无论走哪条路都关文件」。这是 Go 资源管理的惯用法,第 04 章正式讲28。
主线例子第 ④ 步: 注册记录追加写入 users.log,用 os.OpenFile 的
O_APPEND|O_CREATE|O_WRONLY 标志——没有就建、有就接着写29。
6.2 fmt 与 log:给人看 vs 给运维看¶
log.Println("Application started")
log.SetFlags(log.Ldate | log.Ltime | log.Lshortfile) // 加日期、时间、文件名:行号
file, _ := os.Create("app.log")
log.SetOutput(file) // 日志重定向到文件
log.Fatal 打日志后直接退出程序,用于「初始化失败,没救了」的场景31。
选择口诀(原书):用户交互、临时调试用 fmt;运行事件、要时间戳和重定向、跨包统一用 log32。
6.3 一个把三件套用齐的小例子:逐行读日志、挑错误、写汇总¶
原书把 os、io、bufio、log 分节讲,这里串一个完整小工具,把第 04 章的错误检查也带上 (文件名与字段是为演示编的;每一行旁边的注释说清它在干嘛):
package main
import (
"bufio"
"fmt"
"log"
"os"
"strings"
)
func main() {
// os 打开;失败(文件不存在、没权限)立刻带着包装错误退出
src, err := os.Open("app.log")
if err != nil {
log.Fatalf("open log: %v", err) // log.Fatalf = 打日志 + 退出
}
defer src.Close() // 无论后面怎么走,退出前一定关
// 汇总目标:有缓冲的写入器,攒一批再落盘
dst, err := os.Create("errors.txt")
if err != nil {
log.Fatalf("create summary: %v", err)
}
defer dst.Close()
w := bufio.NewWriter(dst) // bufio 包一层,减少写磁盘的次数
defer w.Flush() // 别忘了:不 Flush,缓冲区里的内容不会落盘
// 逐行扫(Scanner 默认按行切),只挑含 ERROR 的行写进汇总
scanner := bufio.NewScanner(src)
count := 0
for scanner.Scan() {
line := scanner.Text()
if strings.Contains(line, "ERROR") {
fmt.Fprintln(w, line) // 写进缓冲,不是直接写盘
count++
}
}
if err := scanner.Err(); err != nil { // 扫到一半文件出错,也要查
log.Printf("scan interrupted: %v", err)
}
log.Printf("summary written, %d error lines", count)
}
每一行几乎都能对应回前三章的一条纪律:if err != nil 是第 04 章的仪式;
defer 是「怎么退出都清理」;bufio 是第 02 章「fmt 给人看、bufio 求快」的选择表;
log 是「给运维看的输出」。Go 的标准库是连贯的——一个 40 行的小工具,
把半本书的机制都用上了,这正是原书把文件处理放在第 3 章的用意。
2.5 多返回值与命名返回:一把快刀和它的刀鞘¶
多返回值是 Go 函数签名里最显眼的特点,result, err := compute() 的写法
把「可能失败」编进了类型。两个进阶点(原书都点到):
- 命名返回值:
func process() (result int, err error)给返回值起名, 裸return会自动把同名的局部变量带出去。原书的告诫是节制——名字起得 不好(或函数太长)时,裸 return 让读者满函数找「到底返回的是谁」; - 错误返回值的约定位置:error 永远放最后一个返回值——这是全生态的统一约定, 违反它会让所有调用者别扭(也会让 AI 的补全变歪,它学的是这个约定)。
6.4 fmt 动词速查(原书清单的整理)¶
| 动词 | 输出 | 例子 |
|---|---|---|
%s |
字符串 | hello |
%d |
十进制整数 | 42 |
%f |
浮点 | 3.140000 |
%t |
布尔 | true |
%v |
「按默认样子」任何值 | 调试用 |
%+v |
结构体带字段名 | {Name:bob Age:3} |
%#v |
Go 语法形式的值 | main.User{Name:"bob"} |
%T |
值的类型 | main.User |
需要「拼好不打印」时用 fmt.Sprintf 拿字符串(日志、错误消息里常用)。
记三个就够日常:%v 调试、%+v 看结构、%T 问类型——其余的用到再查。
7. AI 辅助重构(辅线)¶
这一章的 AI 部分给了一组「改前/改后」对照,是全书质量最高的一组辅线例子。 四个场景,每个都是「AI 提议 + 人验证」33:
场景一:多职责函数拆模块。 ProcessUser 一个函数里塞了校验、处理、写文件三件事;
AI 拆成 ValidateUser、ProcessUserData、WriteToFile 三个单一职责函数,
主函数变三行编排。好处原书列得很具体:可复用、可测、文件操作孤立出来好做错误处理34。
场景二:递归改迭代。 指数时间的递归斐波那契改 O(n) 循环——前面 §2.2 的边界 在这里兑现:AI 找得到这种热点,但「为什么等效」要你自己对着基例和归纳步核一遍35。
场景三:goroutine 泄漏。 原来的 QueryData 起了一个 goroutine 往 channel 写,
若数据库调用卡住、又没人读,goroutine 就永远挂着(泄漏)。AI 的改法引入
context.Context(第 06 章专讲)和 select,让取消信号能把它关掉36。
场景四:参数太多 → 配置结构体。 5 个参数的 CreateAccount 改成一个
AccountOptions 结构体传进去——消掉参数顺序记错的风险,以后加字段不动签名37。
场景五:循环依赖解套。 user 和 auth 互相 import 编译不过;AI 在 auth 侧
定义 UserFetcher 接口,Authenticate 依赖接口而不是具体包——依赖方向立刻单向化,
测试时还能塞假实现(mock)38。
原书对 AI 重构的总评值得原文转述(短句):AI 擅长提出结构性改进而不是逐行改写、 认出常见坑(nil 指针、goroutine 泄漏、漏查错误)、把递归变循环、引入接口39。 但注意:这五例全是「模式教科书式」的重构,你的代码若长得不像教科书,AI 的建议 就更要逐条验。
判断(我们的,不是书里的): 场景五是全章最有含金量的一例—— 「依赖接口而不是具体包」不是 AI 的发明,是 Go 接口设计(第 02 章)的正典用法; AI 的作用是让这层重构的门槛低到随手可做。但反过来,接口也不是免费的: 每多一个接口,读代码的人就要多跳一层。AI 不会替你掂量这层成本,掂量是人的活。 如果错,会错在: 如果项目规模很小(三五文件),接口化带来的跳转成本可能超过 解耦收益——「AI 建议了」不是采用接口的充分理由。
8. 作者的判断与证据¶
语言机制部分(可验证的硬事实):
- 值传递、多返回值、
...变参、大小写可见性、init 次序、最小版本选择—— 全部是语言与工具链的定义行为,可写代码当场验证1111217。 - 「Go 无尾调用优化」「环依赖编译报错」——规范级事实425。
观点与设计建议:
- 「单一职责、短包名、杂物抽屉有害」——社区共识式经验,不是测量结论25。
- AI 重构一节通篇「AI-Generated Improvements」清单是合理推测; 没有对照实验,也没有失败案例(哪些重构建议被人类否决了、为什么)33。
9. 边界与局限¶
- 接口何时该用,本章只给了重构视角(解耦、测试),没给「从第一天就设计接口」 的正面对照——读者容易学成「到处建接口」。
- 测试只讲了单元测试的形状,表驱动测试、mock、集成测试都要到第 06 章才补。
- go.work(多模块工作区)没提,大型 monorepo 的多模块日常开发只说了一半20。
- AI 重构的验证方法(怎么 diff、怎么跑测试兜底)本章没写,第 09 章才补上。
10. 可带走的¶
- 参数是值拷贝;要改调用者的量就传指针。
- 多返回值主要是为「结果 + error」设计的;命名返回值慎用。
- Go 无尾调用优化;深递归吃栈,性能敏感处写循环。
...变参收进来是切片;变参必须排最后。- 大写导出、小写私有;没有 public/private,首字母就是开关。
- 一个目录一个包;
import _ "driver"只为 init 自注册。 - 初始化次序:依赖 → 包级变量 → init;init 每个包一次、按文件序。
go mod init/get/tidy三条够用;go.sum保可复现;最小版本选择—— 升级永远是你显式做的。- SemVer:v2 要写进模块路径。
- 测试:
_test.go+go test ./...;测公开 API 用mypkg_test外部包。 - 文件三件套:os 开、bufio 缓冲、io 流式拷贝;
defer file.Close()紧跟打开;Flush别忘。 - fmt 给人看、log 给运维看;log 自带时间戳,
log.Fatal打日志即退出。 - AI 重构五板斧:拆职责、递归改循环、消 goroutine 泄漏、参数聚成结构体、 接口解循环依赖——每板斧都要自己验。
11. 原文地图¶
| 主题 | 原书章 | 原文位置 |
|---|---|---|
| 值传递与参数合并 | 3. Functions, Packages, and Modular Code | text/13-ch03-3-functions-packages-and-modular-code.txt:72(搜「supplied by value」) |
| 多返回值与命名返回 | 同上 | text/13-ch03-3-functions-packages-and-modular-code.txt:80(搜「multiple values」) |
| 递归与阶乘例 | 同上 | text/13-ch03-3-functions-packages-and-modular-code.txt:94(搜「Recursion」);阶乘代码见 :116 |
| 无尾调用优化 | 同上 | text/13-ch03-3-functions-packages-and-modular-code.txt:108(搜「tail-call」) |
| 变参函数 | 同上 | text/13-ch03-3-functions-packages-and-modular-code.txt:172(搜「Variadic Functions」) |
| 匿名函数 | 同上 | text/13-ch03-3-functions-packages-and-modular-code.txt:202(搜「Anonymous Functions」) |
| 包布局与目录规则 | 同上 | text/13-ch03-3-functions-packages-and-modular-code.txt:316(搜「Directory Structure」) |
| 大写导出规则 | 同上 | text/13-ch03-3-functions-packages-and-modular-code.txt:298(搜「Exported versus Unexported」) |
| 三种 import 写法 | 同上 | text/13-ch03-3-functions-packages-and-modular-code.txt:330(搜「Import Declarations」) |
| 初始化次序与 init | 同上 | text/13-ch03-3-functions-packages-and-modular-code.txt:363(搜「Package Initialization」) |
| go.mod 与最小版本选择 | 同上 | text/13-ch03-3-functions-packages-and-modular-code.txt:428(搜「How Go Modules Work」)text/13-ch03-3-functions-packages-and-modular-code.txt:450(搜「minimal version selection」) |
| go.sum 校验和 | 同上 | text/13-ch03-3-functions-packages-and-modular-code.txt:967(搜「cryptographic checksums」) |
| SemVer 与 v2 路径 | 同上 | text/13-ch03-3-functions-packages-and-modular-code.txt:665(搜「encodes compatibility」)text/13-ch03-3-functions-packages-and-modular-code.txt:998(搜「yourname/project/v2」) |
| 自建包全流程 | 同上 | text/13-ch03-3-functions-packages-and-modular-code.txt:496(搜「Creating a Custom Package」) |
| 测试哲学 | 同上 | text/13-ch03-3-functions-packages-and-modular-code.txt:642(搜「TestAdd」)text/13-ch03-3-functions-packages-and-modular-code.txt:652(搜「Testing Philosophy」) |
| os/io/bufio 分工 | 同上 | text/13-ch03-3-functions-packages-and-modular-code.txt:716(搜「File Handling Using」) |
| fmt 与 log 选择 | 同上 | text/13-ch03-3-functions-packages-and-modular-code.txt:917(搜「Choosing between fmt and log」) |
| AI 重构五例 | 同上 | text/13-ch03-3-functions-packages-and-modular-code.txt:1025(搜「Multi-Responsibility」);text/13-ch03-3-functions-packages-and-modular-code.txt:1095(搜「Recursive Fibonacci」);text/13-ch03-3-functions-packages-and-modular-code.txt:1123(搜「misuse of goroutines」);text/13-ch03-3-functions-packages-and-modular-code.txt:1153(搜「Config Structs」);:1186(搜「Cyclic Dependencies」) |
-
出处:「3. Functions, Packages, and Modular Code」第 63–77 段(
text/13-ch03-3-functions-packages-and-modular-code.txt:72,搜「supplied by value」;合并写法func add(a, b int)见 :68)。 ↩↩↩ -
出处:同章第 80–92 段(
text/13-ch03-3-functions-packages-and-modular-code.txt:80,搜「single value or multiple values」;命名返回值见 :87,搜「Named return values」)。 ↩↩ -
出处:同章第 94–133 段(
text/13-ch03-3-functions-packages-and-modular-code.txt:96,搜「decomposing」;基例/递归步见 见text/13-ch03-3-functions-packages-and-modular-code.txt:102,搜「A base case」;阶乘输出「Factorial of 5」见 :127)。 ↩↩ -
出处:同章第 108–112 段(
text/13-ch03-3-functions-packages-and-modular-code.txt:108,搜「tail-call」)。原文:「Go does not provide tail-call optimization, which means deeply recursive operations may increase stack usage.」 ↩↩ -
出处:同章第 1090–1119 段(
text/13-ch03-3-functions-packages-and-modular-code.txt:1095,搜「Recursive Fibonacci」;「exponential time」见 :1102)。 ↩ -
出处:同章第 172–200 段(
text/13-ch03-3-functions-packages-and-modular-code.txt:173,搜「zero or more arguments」;Sum()空调用见 :189)。 ↩ -
出处:同章第 202–233 段(
text/13-ch03-3-functions-packages-and-modular-code.txt:203,搜「Anonymous functions in Go are functions」;回调例见 :216)。 ↩ -
出处:同章第 235–246 段(
text/13-ch03-3-functions-packages-and-modular-code.txt:235,搜「Combining Variadic」)。 ↩ -
出处:同章第 316–328 段(
text/13-ch03-3-functions-packages-and-modular-code.txt:318,搜「One directory = one package」)。 ↩ -
出处:同章第 330–361 段(
text/13-ch03-3-functions-packages-and-modular-code.txt:330,搜「Import Declarations」;点导入的警告见text/13-ch03-3-functions-packages-and-modular-code.txt:352,搜「not recommended」;下划线导入见 :357)。 ↩ -
出处:同章第 298–314 段(
text/13-ch03-3-functions-packages-and-modular-code.txt:298,搜「Exported versus Unexported Identifiers」)。原文:「In Go, there are no keywords like public or private.」 ↩↩ -
出处:同章第 363–386 段(
text/13-ch03-3-functions-packages-and-modular-code.txt:368,搜「initialization sequence」;拓扑次序的例子 p2→p1 见 :383)。 ↩↩ -
出处:同章第 404–426 段(
text/13-ch03-3-functions-packages-and-modular-code.txt:404,搜「init() functions」;「Overuse of init()」警告见 :425)。 ↩↩ -
出处:同章第 428–447 段(
text/13-ch03-3-functions-packages-and-modular-code.txt:428,搜「How Go Modules Work」;go.mod 三要素示例见 :433)。 ↩ -
出处:同章第 449–466 段(
text/13-ch03-3-functions-packages-and-modular-code.txt:449,搜「Managing Dependencies」;三条命令见 :455)。 ↩ -
出处:同章第 966–970 段(
text/13-ch03-3-functions-packages-and-modular-code.txt:966,搜「go.sum」)。「必须提交版本控制」为原文明写。 ↩ -
出处:同章第 450–453 段(
text/13-ch03-3-functions-packages-and-modular-code.txt:450,搜「minimal version selection」)。 ↩↩ -
出处:同章第 971–975 段(
text/13-ch03-3-functions-packages-and-modular-code.txt:971,搜「Replace Directives」)。 ↩ -
出处:同章第 663–675 段(
text/13-ch03-3-functions-packages-and-modular-code.txt:667,搜「vMAJOR.MINOR.PATCH」)。 ↩ -
出处:同章第 996–999 段(
text/13-ch03-3-functions-packages-and-modular-code.txt:996,搜「major versions above 1」)。 ↩↩ -
出处:同章第 496–533 段(
text/13-ch03-3-functions-packages-and-modular-code.txt:496,搜「Creating a Custom Package」)。「import it using its module path (not the relative directory path)」为原文明写。 ↩ -
出处:同章第 634–661 段(
text/13-ch03-3-functions-packages-and-modular-code.txt:635,搜「Testing is a first-class」;mypkg_test见 :657)。 ↩ -
出处:同章第 705–714 段(
text/13-ch03-3-functions-packages-and-modular-code.txt:705,搜「Module Discovery」;「module proxy system」见 :713)。 ↩ -
出处:同章第 696–704 段(
text/13-ch03-3-functions-packages-and-modular-code.txt:696,搜「Repository Structure」)。 ↩ -
出处:同章第 543–581 段(
text/13-ch03-3-functions-packages-and-modular-code.txt:543,搜「Package Focus」;循环依赖禁令见text/13-ch03-3-functions-packages-and-modular-code.txt:569,搜「does not allow cycles」)。 ↩↩↩ -
出处:同章第 758–773 段(
text/13-ch03-3-functions-packages-and-modular-code.txt:758,搜「Core I/O Abstractions」)。 ↩ -
出处:同章第 800–815 段(
text/13-ch03-3-functions-packages-and-modular-code.txt:800,搜「How These Packages Work Together」)。 ↩ -
出处:同章第 736–745 段(
text/14-ch04-4-error-handling-and-debugging-in-go.txt:449,搜「guaranteed to run」)。defer 的完整语义见第 04 章。 ↩ -
出处:同章第 747–752 段(
text/13-ch03-3-functions-packages-and-modular-code.txt:747,搜「File Permissions and Flags」)。 ↩ -
出处:同章第 848–875 段(
text/13-ch03-3-functions-packages-and-modular-code.txt:848,搜「fmt Package」;%+v见 :867)。 ↩ -
出处:同章第 877–915 段(
text/13-ch03-3-functions-packages-and-modular-code.txt:877,搜「Structured Logging」;log.Fatal见 :895)。 ↩↩ -
出处:同章第 917–927 段(
text/13-ch03-3-functions-packages-and-modular-code.txt:917,搜「Choosing between fmt and log」)。 ↩ -
出处:同章第 1001–1023 段(
text/13-ch03-3-functions-packages-and-modular-code.txt:1001,搜「AI-Assisted Refactoring」)。 ↩↩ -
出处:同章第 1025–1088 段(
text/13-ch03-3-functions-packages-and-modular-code.txt:1025,搜「Multi-Responsibility Functions」)。 ↩ -
出处:同章第 1090–1119 段(
text/13-ch03-3-functions-packages-and-modular-code.txt:1090,搜「Improving Algorithmic Efficiency」)。「O(n)」为原文用语。 ↩ -
出处:同章第 1121–1150 段(
text/13-ch03-3-functions-packages-and-modular-code.txt:1121,搜「Detecting Concurrency Issues」)。 ↩ -
出处:同章第 1152–1184 段(
text/13-ch03-3-functions-packages-and-modular-code.txt:1153,搜「Config Structs」)。 ↩ -
出处:同章第 1186–1213 段(
text/13-ch03-3-functions-packages-and-modular-code.txt:1186,搜「Cyclic Dependencies」)。「dependency inversion」为原文用语。 ↩ -
出处:同章第 1215–1228 段(
text/13-ch03-3-functions-packages-and-modular-code.txt:1215,搜「How AI Enhances Go Refactoring」)。 ↩