跳转至

函数、包与模块化 — 从一堆函数到一座有层次的工程

这一章讲什么: 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。

最有特色的是多返回值——一个函数可以一次返回好几个值:

result, err := compute()

原书点明这个形态 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)函数用 ... 声明,调用时可以传任意个同类型参数, 函数内部收到的是一个切片:

func Sum(nums ...int) int { /* nums 是 []int */ }
Sum(10, 20)
Sum(5, 15, 20)
Sum()          // 也合法,nums 是空切片

规则一条:变参必须放在参数表最后。标准库的 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:

module github.com/drishti/myapp

go 1.22

require (
    github.com/google/uuid v1.5.0
)

日常就三条命令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:

$ go get github.com/google/uuid@latest
go.mod 里多出一行:require github.com/google/uuid v1.5.x

(具体版本号随时间变,以执行时为准。)

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:

import (
    "fmt"
    "project/mathutils"   // 模块路径,前缀是 go.mod 里的模块名
)
fmt.Println(mathutils.Add(10, 5))

测。 测试文件以 _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:

  1. 单一职责:config 管配置、auth 管认证、storage 管数据库;避免 utils 这种 「杂物抽屉」;
  2. 名字短、小写、一个词:json、cache 好,stringhelpers、processingmodulev2 差;
  3. 禁止环:p1 import p2、p2 又 import p1 —— 编译器直接报错,解法是 抽一个双方都依赖的接口(§8 的 AI 重构例就是这个);
  4. 依赖单向流动:上层可以知道下层,反过来不行。

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 给运维看

  • fmt:给人看的输出。Sprintf 只格式化不打印,适合拼字符串; %+v 能把整个结构体打出来30。
  • log:给运维看的日志——默认每行自带时间戳、写到标准错误31:
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. 可带走的

  1. 参数是值拷贝;要改调用者的量就传指针。
  2. 多返回值主要是为「结果 + error」设计的;命名返回值慎用。
  3. Go 无尾调用优化;深递归吃栈,性能敏感处写循环。
  4. ... 变参收进来是切片;变参必须排最后。
  5. 大写导出、小写私有;没有 public/private,首字母就是开关。
  6. 一个目录一个包;import _ "driver" 只为 init 自注册。
  7. 初始化次序:依赖 → 包级变量 → init;init 每个包一次、按文件序。
  8. go mod init/get/tidy 三条够用;go.sum 保可复现;最小版本选择—— 升级永远是你显式做的。
  9. SemVer:v2 要写进模块路径。
  10. 测试:_test.go + go test ./...;测公开 API 用 mypkg_test 外部包。
  11. 文件三件套:os 开、bufio 缓冲、io 流式拷贝;defer file.Close() 紧跟打开; Flush 别忘。
  12. fmt 给人看、log 给运维看;log 自带时间戳,log.Fatal 打日志即退出。
  13. 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」)


  1. 出处:「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)。 ↩↩↩

  2. 出处:同章第 80–92 段(text/13-ch03-3-functions-packages-and-modular-code.txt:80,搜「single value or multiple values」;命名返回值见 :87,搜「Named return values」)。 ↩↩

  3. 出处:同章第 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)。 ↩↩

  4. 出处:同章第 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.」 ↩↩

  5. 出处:同章第 1090–1119 段(text/13-ch03-3-functions-packages-and-modular-code.txt:1095,搜「Recursive Fibonacci」;「exponential time」见 :1102)。 ↩

  6. 出处:同章第 172–200 段(text/13-ch03-3-functions-packages-and-modular-code.txt:173,搜「zero or more arguments」;Sum() 空调用见 :189)。 ↩

  7. 出处:同章第 202–233 段(text/13-ch03-3-functions-packages-and-modular-code.txt:203,搜「Anonymous functions in Go are functions」;回调例见 :216)。 ↩

  8. 出处:同章第 235–246 段(text/13-ch03-3-functions-packages-and-modular-code.txt:235,搜「Combining Variadic」)。 ↩

  9. 出处:同章第 316–328 段(text/13-ch03-3-functions-packages-and-modular-code.txt:318,搜「One directory = one package」)。 ↩

  10. 出处:同章第 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)。 ↩

  11. 出处:同章第 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.」 ↩↩

  12. 出处:同章第 363–386 段(text/13-ch03-3-functions-packages-and-modular-code.txt:368,搜「initialization sequence」;拓扑次序的例子 p2→p1 见 :383)。 ↩↩

  13. 出处:同章第 404–426 段(text/13-ch03-3-functions-packages-and-modular-code.txt:404,搜「init() functions」;「Overuse of init()」警告见 :425)。 ↩↩

  14. 出处:同章第 428–447 段(text/13-ch03-3-functions-packages-and-modular-code.txt:428,搜「How Go Modules Work」;go.mod 三要素示例见 :433)。 ↩

  15. 出处:同章第 449–466 段(text/13-ch03-3-functions-packages-and-modular-code.txt:449,搜「Managing Dependencies」;三条命令见 :455)。 ↩

  16. 出处:同章第 966–970 段(text/13-ch03-3-functions-packages-and-modular-code.txt:966,搜「go.sum」)。「必须提交版本控制」为原文明写。 ↩

  17. 出处:同章第 450–453 段(text/13-ch03-3-functions-packages-and-modular-code.txt:450,搜「minimal version selection」)。 ↩↩

  18. 出处:同章第 971–975 段(text/13-ch03-3-functions-packages-and-modular-code.txt:971,搜「Replace Directives」)。 ↩

  19. 出处:同章第 663–675 段(text/13-ch03-3-functions-packages-and-modular-code.txt:667,搜「vMAJOR.MINOR.PATCH」)。 ↩

  20. 出处:同章第 996–999 段(text/13-ch03-3-functions-packages-and-modular-code.txt:996,搜「major versions above 1」)。 ↩↩

  21. 出处:同章第 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)」为原文明写。 ↩

  22. 出处:同章第 634–661 段(text/13-ch03-3-functions-packages-and-modular-code.txt:635,搜「Testing is a first-class」;mypkg_test 见 :657)。 ↩

  23. 出处:同章第 705–714 段(text/13-ch03-3-functions-packages-and-modular-code.txt:705,搜「Module Discovery」;「module proxy system」见 :713)。 ↩

  24. 出处:同章第 696–704 段(text/13-ch03-3-functions-packages-and-modular-code.txt:696,搜「Repository Structure」)。 ↩

  25. 出处:同章第 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」)。 ↩↩↩

  26. 出处:同章第 758–773 段(text/13-ch03-3-functions-packages-and-modular-code.txt:758,搜「Core I/O Abstractions」)。 ↩

  27. 出处:同章第 800–815 段(text/13-ch03-3-functions-packages-and-modular-code.txt:800,搜「How These Packages Work Together」)。 ↩

  28. 出处:同章第 736–745 段(text/14-ch04-4-error-handling-and-debugging-in-go.txt:449,搜「guaranteed to run」)。defer 的完整语义见第 04 章。 ↩

  29. 出处:同章第 747–752 段(text/13-ch03-3-functions-packages-and-modular-code.txt:747,搜「File Permissions and Flags」)。 ↩

  30. 出处:同章第 848–875 段(text/13-ch03-3-functions-packages-and-modular-code.txt:848,搜「fmt Package」;%+v 见 :867)。 ↩

  31. 出处:同章第 877–915 段(text/13-ch03-3-functions-packages-and-modular-code.txt:877,搜「Structured Logging」;log.Fatal 见 :895)。 ↩↩

  32. 出处:同章第 917–927 段(text/13-ch03-3-functions-packages-and-modular-code.txt:917,搜「Choosing between fmt and log」)。 ↩

  33. 出处:同章第 1001–1023 段(text/13-ch03-3-functions-packages-and-modular-code.txt:1001,搜「AI-Assisted Refactoring」)。 ↩↩

  34. 出处:同章第 1025–1088 段(text/13-ch03-3-functions-packages-and-modular-code.txt:1025,搜「Multi-Responsibility Functions」)。 ↩

  35. 出处:同章第 1090–1119 段(text/13-ch03-3-functions-packages-and-modular-code.txt:1090,搜「Improving Algorithmic Efficiency」)。「O(n)」为原文用语。 ↩

  36. 出处:同章第 1121–1150 段(text/13-ch03-3-functions-packages-and-modular-code.txt:1121,搜「Detecting Concurrency Issues」)。 ↩

  37. 出处:同章第 1152–1184 段(text/13-ch03-3-functions-packages-and-modular-code.txt:1153,搜「Config Structs」)。 ↩

  38. 出处:同章第 1186–1213 段(text/13-ch03-3-functions-packages-and-modular-code.txt:1186,搜「Cyclic Dependencies」)。「dependency inversion」为原文用语。 ↩

  39. 出处:同章第 1215–1228 段(text/13-ch03-3-functions-packages-and-modular-code.txt:1215,搜「How AI Enhances Go Refactoring」)。 ↩