当生成 Go 代码缺少 error handling 时,如何基于 AST 自动修复?
解读
面试官想验证三件事:
- 你是否真的写过 Go 代码生成/修复 这类“大模型落地”任务;
- 能否把 AST 操作、静态分析、LLM 生成 串成一条自动化链路;
- 是否具备 LLMOps 视角:修复脚本要可灰度、可回滚、可监控,而不是一次性脚本。
国内真实场景里,大模型生成的 Go 代码常直接 return resp, nil,把 err 吞掉;线上一旦 panic,就是 P0 故障。所以这道题不是考“写个正则”,而是考“用 AST 精准补 err,并嵌入 CI/CD”。
知识点
- go/ast + go/parser + go/token:官方三件套,可无损保留注释与格式化。
- ssa/ssautil:若需跨函数数据流分析(看 err 是否已向上传递),可二次精修。
- dst(decorative syntax tree):比 ast 多承载注释、空行,适合回写代码。
- errwrap / pkg/errors 风格:国内大厂内部规范普遍要求
errors.Wrap(err, "desc")。 - LLMOps 钩子:把修复逻辑做成 pre-commit 插件 或 GitHub Action,产出 metrics(修复行数、误报率),写入 Prometheus + Grafana。
- 灰度策略:按文件/服务维度做 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 闭环
- 封装成 errfix-cli,在 CI 的
lintjob 里运行; - 结果 diff 若不为空,自动提 MR 并打 tag
auto-err,指定 code-owner review; - 把“修复数/扫描数”写入 Prometheus,告警误报率 >2% 时回滚规则;
- 每周用 大模型重训 提示模板:把误报片段当负例,减少下次生成裸调用的概率。
拓展思考
- 多文件跨包场景:若 err 需要透传给调用者,可用 ssa 构建调用图,只在叶子函数补日志,中间函数只透传,避免重复 Wrap。
- 并发模式:Go 中常见
go f()启动 goroutine 丢失 err;可自动改写为golang.org/x/sync/errgroup.Group,把 err 回收到主 goroutine。 - 与 Service Mesh 联动:修复后的 err 统一带 request-id header,通过 OpenTelemetry 上报,方便在 Kiali 做分布式追踪。
- 大模型自我迭代:把“AST 修复结果”再喂给 预训练模型 做 RLHF 奖励模型,让模型原生生成带 err 的代码,最终目标是“生成即正确”,不再需要修复。