当生成 Go 代码缺少 error handling 时,如何基于 AST 自动修复?

解读

面试官想验证三件事:

  1. 你是否真的写过 Go 代码生成/修复 这类“大模型落地”任务;
  2. 能否把 AST 操作、静态分析、LLM 生成 串成一条自动化链路;
  3. 是否具备 LLMOps 视角:修复脚本要可灰度、可回滚、可监控,而不是一次性脚本。

国内真实场景里,大模型生成的 Go 代码常直接 return resp, nil,把 err 吞掉;线上一旦 panic,就是 P0 故障。所以这道题不是考“写个正则”,而是考“用 AST 精准补 err,并嵌入 CI/CD”。

知识点

  1. go/ast + go/parser + go/token:官方三件套,可无损保留注释与格式化
  2. ssa/ssautil:若需跨函数数据流分析(看 err 是否已向上传递),可二次精修。
  3. dst(decorative syntax tree):比 ast 多承载注释、空行,适合回写代码
  4. errwrap / pkg/errors 风格:国内大厂内部规范普遍要求 errors.Wrap(err, "desc")
  5. LLMOps 钩子:把修复逻辑做成 pre-commit 插件GitHub Action,产出 metrics(修复行数、误报率),写入 Prometheus + Grafana
  6. 灰度策略:按文件/服务维度做 feature flag,先让 5% 仓库试点,再全量。

答案

给出一个可落地的 四阶段方案,可直接写进简历项目经验。

阶段一:AST 扫描
go/parser.ParseFile(fset, filename, src, parser.ParseComments) 得到 ast.File
自写 ast.Inspect 回调,定位到 所有 CallExpr 返回 (T, error)左侧只接收了 T 的 AssignStmt;
记录缺失的 Ident 行号、函数名、error 类型。

阶段二:补 err 变量
同一 Scope 内插入 var err error(若未声明);
把原 a := f() 改写成 a, err := f()
若后续已有 if err != nil { return ... } 则跳过,否则进入阶段三。

阶段三:生成 nil 判断与包裹
按公司规范生成

if err != nil {
    return ..., errors.Wrap(err, "pkg.Method: f failed")
}

利用 dst 把节点插到 StatementList 的下一位置,再 dst.Print 回写文件;
保留原始注释与 import 顺序,避免 review 噪音。

阶段四:LLMOps 闭环

  1. 封装成 errfix-cli,在 CI 的 lint job 里运行;
  2. 结果 diff 若不为空,自动提 MR 并打 tag auto-err,指定 code-owner review;
  3. 把“修复数/扫描数”写入 Prometheus,告警误报率 >2% 时回滚规则;
  4. 每周用 大模型重训 提示模板:把误报片段当负例,减少下次生成裸调用的概率。

拓展思考

  1. 多文件跨包场景:若 err 需要透传给调用者,可用 ssa 构建调用图,只在叶子函数补日志,中间函数只透传,避免重复 Wrap。
  2. 并发模式:Go 中常见 go f() 启动 goroutine 丢失 err;可自动改写为 golang.org/x/sync/errgroup.Group,把 err 回收到主 goroutine。
  3. 与 Service Mesh 联动:修复后的 err 统一带 request-id header,通过 OpenTelemetry 上报,方便在 Kiali 做分布式追踪。
  4. 大模型自我迭代:把“AST 修复结果”再喂给 预训练模型RLHF 奖励模型,让模型原生生成带 err 的代码,最终目标是“生成即正确”,不再需要修复