Go 语言测验¶
本页用于检查 Go 1.22+ 的语言、错误处理、并发、文件、正则、模块和工具链知识。 建议先独立回答,再展开答案解析;能解释“为什么”比记住单一命令更重要。
1. 错误包装¶
问题:函数返回 fmt.Errorf("load: %w", fs.ErrNotExist) 后,下面哪种判断可靠?
A. err == fs.ErrNotExist
B. err.Error() == "file does not exist"
C. errors.Is(err, fs.ErrNotExist)
答案:C。
解析:%w 建立错误链,errors.Is 会遍历该链;直接相等通常无法穿透包装,文本也不是稳定机器接口。
2. errors.Is 与 errors.As¶
问题:何时使用 errors.As 而不是 errors.Is?
答案:当调用者需要找到某种错误类型并读取它的结构化字段时使用 errors.As。
解析:Is 主要判断目标错误值或自定义等价语义,As 会把链中的匹配错误赋给目标指针。
3. errors.Join¶
问题:errors.Join(nil, nil) 返回什么?它适合表达哪类失败?
答案:返回 nil;它适合组合多个相互独立的失败,例如批量校验或多个资源关闭失败。
解析:有明确因果层级时应逐层 %w 包装,不应把 Join 当作任意错误堆叠工具。
4. panic 的边界¶
问题:读取用户指定文件失败后是否应立即 panic?
答案:通常不应。 解析:文件不存在、权限不足和临时 I/O 故障属于可预期失败,应返回 error;panic 留给无法维持程序不变量等情况。
5. goroutine 等待¶
问题:为什么不应使用 time.Sleep(time.Second) 等待后台 goroutine 完成?
答案:时间不能证明任务已完成,慢机器上可能过早结束,快机器上又浪费时间。
解析:应使用 sync.WaitGroup、结果 channel 或更高层任务协调机制建立明确的完成关系。
6. channel 关闭责任¶
问题:通常由发送方还是接收方关闭 channel?
答案:通常由发送方关闭。 解析:发送方知道何时不会再产生值;接收方关闭可能与仍在发送的 goroutine 竞争并引发 panic。
7. 缓冲 channel¶
问题:把 channel 容量从 1 改为 1000 是否能从根本上修复消费者永久停止造成的泄漏?
答案:不能。 解析:更大缓冲只推迟阻塞;需要取消传播、关闭协议、背压和明确的 goroutine 退出路径。
8. select default¶
问题:在无限循环的 select 中加入 default 有什么风险?
答案:当没有通信就绪时循环会持续运行,形成忙等并占满 CPU。
解析:如果目标是等待事件,应移除 default;轮询需求也应采用计时器或明确节流。
9. context¶
问题:调用 context.WithTimeout 后,即使操作通常会在截止时间前结束,为何仍应调用 cancel?
答案:及时释放派生 context 持有的计时器和关联资源。
解析:惯用写法是创建成功后立即 defer cancel(),并把 ctx 向下传递。
10. 数据竞争¶
问题:两个 goroutine 并发执行普通 map 的一个读和一个写是否安全?
答案:不安全。
解析:需要 mutex、单一所有者 goroutine 或经评估适合的 sync.Map;并运行 go test -race ./... 检测已覆盖路径。
11. Go 1.22 循环变量¶
问题:Go 1.22 中 for 循环闭包捕获迭代变量与旧版本有何关键差异?
答案:每轮迭代拥有新的变量,闭包不再都意外观察同一个最终值。 解析:显式把值作为 goroutine 参数仍可增强意图表达,并提醒代码审查者数据如何进入任务。
12. 整体读取与流式读取¶
问题:处理可能达到 20 GiB 的上传文件,优先选择 os.ReadFile 还是 io.Reader 流式处理?
答案:使用 io.Reader 流式处理并设置总大小上限。
解析:os.ReadFile 会尝试将整个文件装入内存,可能导致内存耗尽。
13. defer 与循环¶
问题:在一个包含十万次迭代的外层函数中,每轮打开文件并直接 defer f.Close() 有什么问题?
答案:所有 defer 到外层函数返回时才执行,可能耗尽文件描述符。 解析:把单次处理放进独立函数,让 defer 在每轮结束时及时运行。
14. 文件权限¶
问题:存储 API 密钥的 Unix 文件更适合以 0o600 还是 0o777 创建?
答案:通常是 0o600。
解析:遵循最小权限,只允许所有者读写;还要理解 umask 和不同操作系统的权限差异。
15. 路径安全¶
问题:filepath.Join(root, userInput) 是否自动保证结果仍在 root 内?
答案:不保证。
解析:应拒绝绝对路径并通过 filepath.Rel 检查逃逸;高安全场景还要处理符号链接和检查后使用之间的竞态。
16. 原子写入¶
问题:为什么更新配置时常先写同目录临时文件再 os.Rename?
答案:避免直接截断目标后崩溃留下半写文件,并利用同一文件系统上的 rename 替换语义。
解析:严格持久性还需考虑 Sync、父目录同步和不同操作系统的覆盖行为。
17. Scanner 上限¶
问题:bufio.Scanner 能否默认读取任意长度的一行?
答案:不能。
解析:它有默认 token 大小上限;应使用 Scanner.Buffer 设置业务允许的最大值,并检查 Scanner.Err()。
18. RE2 边界¶
问题:Go 标准库正则是否支持 lookbehind 和反向引用?
答案:都不支持。 解析:Go 使用 RE2,通过放弃依赖回溯的特性获得近似线性的可预测匹配时间。
19. Compile 与 MustCompile¶
问题:用户在网页中提交正则表达式,应使用 regexp.Compile 还是 regexp.MustCompile?
答案:使用 regexp.Compile 并向用户返回合法性错误。
解析:MustCompile 会在非法模式时 panic,只适合开发者控制的固定模式。
20. 正则校验边界¶
问题:使用 [0-9]+ 校验整个输入为数字有什么遗漏?
答案:它能匹配包含数字的子串,不保证整个输入都是数字。
解析:可使用 ^[0-9]+$,随后仍要做数值范围检查;解析数字时通常直接用 strconv 更清晰。
21. gofmt 与 go vet¶
问题:执行 gofmt 后是否意味着代码已通过静态语义检查?
答案:不意味着。
解析:gofmt 只负责标准格式,go vet 检查部分可疑构造,测试和其他分析仍需单独执行。
22. race detector¶
问题:go test -race ./... 没有报告竞争,是否证明程序绝对无竞争?
答案:不能证明。 解析:race detector 是动态检测器,只能检查测试实际执行到的路径和调度情况。
23. 基准与 pprof¶
问题:发现接口慢时,应先凭直觉重写算法,还是先建立基准并采集 profile?
答案:先建立可重复基准,再用 pprof 或 trace 定位热点。 解析:优化后重新运行基准并统计比较,避免优化非瓶颈或引入性能回退。
24. go generate¶
问题:go build 是否会自动执行源码中的 //go:generate?
答案:不会。
解析:必须显式运行 go generate;该命令会执行程序,因此运行不可信仓库前要审查生成指令。
25. go.mod 与 go.sum¶
问题:go.sum 是否是“当前构建中所有依赖的固定版本清单”?
答案:不是。
解析:版本选择主要由 go.mod 和最小版本选择决定;go.sum 保存下载模块内容及其 go.mod 的校验值,并可能保留历史条目。
26. replace¶
问题:库模块能否依赖发布到公共代理后仍使用消费者机器上的本地 replace ../dep?
答案:不能依赖这种环境。
解析:主模块的 replace 只在当前主模块构建时生效,本地路径也不可供远程消费者复现,发布前应移除或改为有效版本。
27. workspace¶
问题:go.work 与把所有模块永久写入互相 replace 相比有何优势?
答案:workspace 能在本地同时开发多个模块,而不污染各模块发布用的 go.mod。
解析:CI 必须明确是否启用 workspace,避免开发机因意外发现上级 go.work 得到不同构建结果。
28. 安全升级¶
问题:看到依赖有漏洞后,直接执行 go get -u ./... 是否总是最佳方案?
答案:不是。
解析:应先用 govulncheck 和公告确认影响路径,选择修复版本,审查变更并运行测试、race 和必要的集成验证。
29. 工具版本¶
问题:CI 每次执行 go install tool@latest 有什么工程风险?
答案:工具行为会在未审查时变化,导致构建不可复现或供应链风险增加。 解析:固定明确版本,并通过受控流程更新。
30. 综合设计题¶
问题:设计一个批量处理文件的服务时,至少应明确哪些并发和文件安全边界?
答案:应明确 worker 上限、任务取消、channel 关闭责任、结果与错误收集、路径根目录、符号链接策略、文件大小、权限和原子输出。 解析:还应运行 race detector,测试取消与失败路径,并限制日志中的文件内容和敏感路径。
评分建议¶
- 27 至 30 题正确:已具备较完整的 Go 日常工程心智模型。
- 22 至 26 题正确:基础扎实,建议针对错题编写最小实验验证。
- 16 至 21 题正确:应重点复习错误链、并发生命周期、文件安全和模块解析。
- 15 题及以下:建议按主题运行示例和命令,不要只背答案。
测验不能替代代码审查和实践。 将错题转换为表驱动测试、小型命令或 race 测试,通常比重复阅读更有效。