AI重复造工具函数,可能绕过关键业务规则
文章以金额格式化为例:智能体新增的辅助函数虽然通过测试,却遗漏了仓库已有实现中的财务舍入规则,留下潜在退款误差。作者建议发现同类工具后立即复用或明确合并,避免形成逐渐分叉的重复实现。
AI 深度解读
AI 编程助手新写了一个工具函数,代码清楚、单测通过、功能也正常,合并时还要查什么?作者在一次结账功能审查中发现,新函数重复了仓库已有的金额工具,却漏掉了财务确认过的舍入规则。
这篇文章的结论很明确:发现已有工具后,就该暂停新增,选择复用或有意识地整合。值得读的地方在于,它把「别重复造轮子」落到了检索、审查和 CI 约束上,也提醒我们:构建通过,不代表业务规则被正确继承。
两个金额函数,为什么会留下退款隐患
作者审查结账功能时,发现 Agent 在新建的 pricing.ts 中加入了 formatMoneyCents()。这个函数有单元测试,功能运行正常,看起来没有明显问题。
但仓库的 packages/currency/src/format.ts 已经导出了 formatCurrency()。它不仅负责金额格式化,还包含一套财务确认过的舍入规则。这套规则来自一次真实的不足一分金额事故,属于代码里积累下来的业务约束。
两个函数的差别是:新函数使用 half-up,已有函数使用 half-even。前者在中点情形向上舍入,后者在中点情形选择末位为偶数的结果。平常输入可能看不出区别,特定金额却会出现不同结果。
作者判断,这个差异可能在几个月后的某笔退款中悄悄暴露。原文没有说事故已经发生,也没有给出具体退款数值,不能把风险描述当成实际损失。
重复工具函数真正危险的地方,是同一条业务规则开始有两个修改入口。
今天两份实现看起来接近,明天其中一份修了边界问题,另一份就可能继续保留旧行为。增加几行代码不是主要成本,失去统一维护的位置才是。
Agent 为什么总能写出仓库已有的东西
作者给出了四个原因。问题在于:Agent 能看到的任务上下文,不等于仓库完整的知识。
首先是局部上下文偏差。共享工具可能隔着几个 package,当前提示和检索结果没有提到它,Agent 就容易在眼前的文件里解决问题。
其次是名称错位。任务说的是「以分为单位处理金额」,Agent 想到 formatMoneyCents();已有函数叫 formatCurrency(),只按预想函数名搜索就可能漏掉。
第三是完成任务的倾向。独立写一个 helper,不必理解共享模块,也不必调整其他调用点,容易让当前变更通过构建。但当前改动容易完成,并不能证明后续维护风险更低。
最后是桶式导出(barrel exports):函数经由 index 统一导出后,Agent 可能没有意识到公共接口已经提供了所需能力。
这里有个看似矛盾的细节:作者后面又建议使用可预测的公共导出。两者并不冲突。统一出口能提供明确的查找位置,但检索也需要继续追到实现,否则只看到入口,仍可能漏掉其中的业务规则。
因此,作者把问题归到检索和代码归属,而不是指望一句「不要重复代码」解决。
合并前要查什么,发现重复后又该怎么改
作者建议,把检索放在写代码之前,并要求 Agent 报告考虑过的已有函数及其路径。接受任何新 helper 前,都应该说明已有实现为什么不能复用。
这比要求 Agent「检查一下仓库」更具体。审查者能看到它找到了什么,也能继续追问:是接口不合适,还是策略确实不同,抑或只是没找到。
对金额、日期、标识符等共享领域,作者进一步建议增加 lint 或架构规则,通过依赖规则、导入边界限定逻辑的归属位置,让 CI 执行约束。不过,这类规则不能识别所有语义重复,只能保护约定好的领域和模块边界。
原文提到了检查方法和提示指令,但提供的正文没有附上具体命令、提示模板或规则配置,因此不能直接照抄成一套现成方案。
发现重复后,作者的处理顺序是:把新增调用点换成共享工具,删除重复实现,再运行完整测试套件。如果共享工具缺少能力,就在现有测试保护下扩展它。
保留两份函数也有合理情形。例如,报表展示采用一种舍入策略,账本采用另一种精度策略。前提是差异属于明确的业务政策,并且体现在命名、两处文档和固定差异行为的测试里。
能否用一句话讲清策略差异,是判断两份实现是否该共存的实用起点。
我的判断
这篇文章最有价值的提醒,是把「已有工具」视为业务知识的载体。金额函数里的舍入方式,往往比函数名和类型签名更重要;新函数的单测如果只验证自己的预期,也可能完全覆盖不到这条历史约束。
但「改共享函数总比维护两份便宜」仍是作者的经验判断。原文没有提供对照数据,也没交代团队规模、共享模块的测试质量和调用范围。面对设计混乱、调用广泛的工具函数,扩展接口同样可能引入风险,需要先核对兼容性。
我会采用它的检索和审查要求,同时把「复用已有行为」与「修改共享行为」分开评估。直接调用已经符合要求的工具,通常很清楚;一旦要改变共享工具的行为,就需要确认已有调用方是否仍然成立。至于局部 helper,合理与否应该由业务边界决定,不能只看代码长得像不像。
能拿来做什么
- 给日常 AI 编程指令增加明确要求:新增 helper 前,先检索相关实现,列出路径,并解释不能复用的具体原因。
- 审查前端结账或 Node 金额处理改动时,除了看展示结果,还要核对是否沿用了共享金额工具及其舍入规则。
- 对已经有统一归属的金额、日期和标识符逻辑,使用依赖规则或导入边界约束新增实现,并明确这些规则无法检查所有语义重复。
- 发现重复后,把替换调用、删除副本和测试验证放进同一次修复;确实需要两种策略时,用命名、文档和测试明确差异。