“把这段逻辑抽成helper。”“这里加个缓存。”“给那个功能建个service。”“把这个模块拆一下。”“再加一层抽象。”“引入一个新依赖。”

AI编程助手做这些决定时,几乎不会出错。每一步都挑不出毛病,每一个pull request都能通过,每一行diff看起来都干净利落。

打开网易新闻 查看精彩图片

问题恰恰出在这里。

一个系统可以在每个局部决策都正确的情况下,整体变得越来越糟。这不是AI的问题,是一个架构问题——只是AI让它跑得更快了。

危险的地方在于:没有任何一处看起来明显是错的

把时间拉长到几周,事情是这样的:

  • 任务1 → 加校验
  • 任务2 → 抽helper
  • 任务3 → 加缓存
  • 任务4 → 加service
  • 任务5 → 加重试逻辑
  • 任务6 → 重构模块
  • 任务7 → 再接一个集成

每一次改动都通过了。每一处看起来都很干净。每一个测试都是绿的。

但六周之后:

  • 两个service同时持有同一条业务规则
  • 校验逻辑出现在三个地方
  • 缓存策略前后不一致
  • 依赖关系变成双向的
  • 没人说得清哪一层负责什么
  • 一个“简单”的改动要碰八个文件

没有任何一次提交摧毁了架构。系统是自己漂过去的。

局部正确不等于系统正确

这是整件事的核心。

AI agent通常只处理眼前这个任务。你说“加上重试支持”,它找到最合理的那个位置把重试加进去。你说“复用这段逻辑”,它抽出一个helper。你说“把这个模块清理一下”,它引入一层新的抽象。

每一个回答,对那个prompt来说都可能是对的。

但架构不是一堆局部正确选择的集合。架构关心的是这些选择在时间维度上如何相互作用。

开发者和agent承担的责任不一样。agent问的是:我怎么把这个任务成功完成?开发者应该问的是:这个改动对整个系统做了什么?

这不是同一个问题。

agent可能优化的方向是:更少的代码行、更清晰的分隔、更少的重复、更快的实现、更容易复用。

而系统真正需要的可能是:更强的边界、更少的抽象、一个明确的归属方、有意的重复、更少的耦合、更简单的依赖。

一个局部优雅的改动,放到全局依然可能是有害的。

那个“乐于助人”的helper

假设你有一个 calculateInvoiceTotal(),一开始只有一个模块在用。

后来另一个功能需要类似的行为。AI建议:把它移到 shared/utils 里。听起来很合理。

再后来,又一个模块import了它。再后来,有人往里加了客户特定的逻辑。然后是税务规则。然后是折扣行为。

现在你这个“共享helper”里装的是贯穿整个系统的业务逻辑。

当时没有任何一步看起来不合理。但架构已经悄悄变了。它从“发票拥有发票逻辑”,变成了“所有人都依赖 shared/utils”。

这就是架构漂移。

AI让架构漂移变得更快

在AI之前,引入五个新抽象是要花力气的。现在几分钟就能发生。

这改变了成本结构。开发者生成新service、新接口、新层级、新适配器、新helper的速度,已经超过了他们评估这些东西该不该存在的速度。

风险不在于AI生成明显糟糕的架构。更有意思的风险是:它生成看似合理的架构,速度快过团队维持一致设计的能力。

测试全绿,保护不了架构

这一点很重要。

你的测试套件可能会说:一切正常。但这不等于:系统在变好。

测试能抓住的是:行为被破坏、回归、无效输出。

测试通常抓不住的是:归属不清、不必要的抽象、依赖蔓延、重复的业务规则、架构漂移。

你可以做到100%测试通过,同时代码库每周都变得更难改。

代码评审同样可能漏掉它。评审者看到的是一个pull request,也许80行,diff看起来合理。但架构问题往往要跨越很多次改动才显现出来。

PR 1加个helper。PR 2加个service。PR 3再加个依赖。PR 4复用那个helper。PR 5绕过那个service。

单独看:没问题。合起来看:一团乱麻。

这就是为什么只看当前diff的评审并不总是够用。

评审AI代码时,多问一个问题

在评审AI生成的代码时,问一句:如果我们把这个模式重复20次,系统会变成什么样?

这个问题出奇地有用。

如果每个功能都要加一个service、一份config、一个queue、一个共享helper,那么问题可能不在某一次改动,而在这个模式本身。

架构往往取决于一个决策被重复之后会发生什么。

减少漂移最简单的办法之一,是把归属关系写清楚。比如:支付拥有支付规则,订单拥有订单生命周期,用户拥有身份,通知只负责发消息。

然后告诉agent:除非明确要求,不要把业务规则搬过这些边界。

这比让模型每次都从零判断架构归属要好得多。

给AI约束,而不只是目标

差的prompt是:“重构一下,让它更干净。”

更好的写法是:重构这个模块。约束条件——保持现有架构边界、不引入新依赖、不创建新的共享抽象、业务逻辑留在现有领域内、实现前先提方案。

目的不是让AI变得没用,而是阻止每一个任务都变成一次小型架构重设计。

在动手写代码之前,可以先要求它回答:这个改动触及哪条架构边界?哪些模块会依赖这个改动?是否引入了新的抽象?是否产生了新的依赖?是否已经存在类似逻辑?它对未来的改动有什么影响?

这会把讨论逼到语法之上的一层。

还有一个值得盯的信号:依赖方向。健康的系统通常有清晰的关系,比如 Controller 指向 Service,Service 指向 Repository。当AI开始引入反向依赖时,架构的裂缝就已经出现了。