跳转至

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 测试,通常比重复阅读更有效。