AI / Agent

清理 AI 写出的过度防御代码,哪些该删,哪些必须留

1933 字约 7 分钟

摘要两个全程由 Codex 写出来的项目,积累了大量重复校验、兜底分支、锁死文案和目录结构的测试,以及永远写不完的计划文档。我让另一轮会话来清理,一次下来净减少了两三千行。这里记下判断删不删的标准,以及清理之后真正改变了行为的几个地方。

我手上有两个项目,从头到尾都是让 Codex 的 GPT 模型写的,一个是 Auto Email Sender,另一个是 Job Market Monitor。用了一段时间之后,我越来越觉得代码里有一种说不清楚的“重”:同一个值被检查好几遍,到处是看起来很谨慎的兜底分支,测试堆得很多,但很多一眼就觉得没什么用。AGENTS.md 和各种 Skill 之类的文档也是一样,不断变长。

9 月 5 日深夜到 6 日凌晨,我分别在两个项目里对助手说:之前写代码的模型有特别多过于繁重的防御式编程,导致代码冗杂,写了一大堆没必要的测试,过度设计,文档也是如此,请你来一波彻底的清理。

先立一条标准

“彻底清理”这句话很危险,容易被理解成“能删多少删多少”。助手一开始就把标准说清楚了:判断一段东西该不该留,看的是它有没有解决真实的问题、保护了实际行为,篇幅长本身不是删除的理由。 凡是涉及数据正确性、邮件发送安全和发布安全的边界,都先划出来保留。

按这个标准,两个项目里找到的东西大致可以分成几类。

类别具体例子
重复的校验和兜底配置在读取时已经校验过,使用的地方又重复做一遍类型转换和默认值兜底
重复的实现前后端各有一套日志脱敏;两个模型客户端各自处理一遍请求重试和错误
没人调用的代码旧的画像生成框架、已经被读模型取代的 JSON 查询封装、前端里没人用的工具函数
为不存在的版本做的兼容桌面端接口兼容一些根本不存在的旧版本组合
锁死写法的测试用整套 AST 扫描强制目录分层,还带着一份例外名单;逐字检查文案、致谢名单的顺序,甚至 SVG 路径
永远维护的计划一份已经完成的 1,650 行重构计划,文档还要求以后每次改动都继续维护批次记录

其中让我印象最深的是“锁死写法的测试”。这类测试表面上是在保护质量,实际上每次重构都要先改它们,它们本身不保护任何用户可见的行为。

留下的东西

同样重要的是不删什么。助手保留了:采集的分页完整性和快照,历史数据迁移,失败审计,进程隔离,以及接口的真实行为测试。它在清理 Job Market Monitor 时,专门说明“分页完整性、历史数据迁移和失败审计都有实际用途,会保留”,这几项在清理前后一直是通过的。

清理之后的结果

项目改动范围净减少验证
Auto Email Sender23 个文件2,739 行五个工作区的测试、前端构建、桌面端类型检查和官网构建通过
Job Market Monitor25 个文件1,981 行,其中代码 615、测试 326、文档 1,040清理前基线是 672 项通过,清理后 671 项通过,另有 1 项需要 PostgreSQL 测试库而跳过

文档的变化也很明显:Auto Email Sender 的 AGENTS.md 从 72 行压到 37 行,两个 Skill 去掉了已有授权下的重复确认和固定流程。后来我又让它清理了存放导师数据的另一个仓库,净减少 1,069 行,Python 测试耗时从 24.6 秒降到 9.5 秒。

删掉兜底之后,露出了什么

我最想记下来的是清理过程中真正改变了行为的几处。如果只是“删了没人用的东西”,那不会有这些发现。

一个测试替身缺了字段。 删除重复兜底之后,后端全量测试出现一个失败。原因是某个测试里伪造的配置对象,缺少真实配置里已有的一个字段。多半是因为以前下游有兜底,缺了字段也能凑合通过,没人发现这个替身是不完整的。兜底不在了,缺口就暴露了。

错误格式变了。 Job Market Monitor 里,毕业年份的校验原来是手写的,现在交给 FastAPI 统一处理,非法输入仍然返回 422,但错误的结构变成了框架的标准格式。原来有一个测试要求出现手写的错误文案,所以失败了。助手没有把代码改回去,而是把测试改成验证出错的字段和范围约束。

坏数据不再被悄悄吞掉。 清理前,一些兜底逻辑会把内部的坏数据静默转成空值,后续代码继续往下跑。清理之后,这类数据会直接报错。助手在总结里特别写了这一点,我觉得这是清理里最有价值的收获,因为问题终于会在出现的地方暴露,而不是被推迟到几个环节之后,变成一个更难查的现象。

这些改动当时没有部署

Job Market Monitor 的这次清理只提交到了本地的 main,没有部署,也没有操作线上的数据库。测试通过、构建通过,只能证明代码和测试之间一致,不能证明线上行为一样。尤其是错误格式变化,如果有外部调用方依赖旧的错误文案,这类问题只有在使用时才会发现。

还有哪些模块可以简化

清理完第一轮,助手在总结里写了一句“大型业务模块仍有进一步简化空间”。我就问它,请列出所有你觉得还需要简化的模块。

它给出了一长串清单,而且特意分成两类:已经有明确冗余证据的,和需要进一步核对调用关系的。比如统计服务里,新的数据库聚合和旧的内存聚合两套实现并存,有几个函数全局搜索只找到定义,没有调用,属于有证据的残留。任务中心前端是单个 7,800 多行的文件,里面有 75 处 useState,这属于“需要核对”,它明确写道“文件大、测试多本身不等于应该删”。

之后又做了一轮重构,把几个最大的文件拆开:Agent 接口、模型调用、网页抓取、邮件协议、命令行、桌面端安装逻辑和几个大页面,复用分页、草稿编辑、任务创建和幂等的规则。接口合同一个都没动,159 个 Agent 接口的路由声明保持一致,原有的 247 项 HTTP 测试完整迁移,没有少。

回头看

我觉得这次清理能顺利,有三个原因。一是开头就定下了“看它保护了什么,而不是看它占了多少行”的标准,每一类冗余都有可以举证的例子。二是保留的边界提前划清了:数据完整性、发送安全、迁移和审计不动。三是每一轮都用全量测试、类型检查和构建兜底,发现不一致就先弄清是代码的问题还是测试的问题。

另一个体会是,让模型自己清理模型写的东西,需要人在前面把标准立好。如果只说“精简一下”,它很可能为了凑数而删掉真正有用的保护。

评论