AI 改祖传模块后,代码评审总绕不开两个问题

AI 能在几分钟里改完一个组件、补齐测试、顺手整理目录。真正让人卡住的,往往不是它写得够不够快,而是那份 diff 摆到评审里以后:我们究竟在确认什么?

碰到维护多年的前端模块,代码评审最容易变成"这段看起来没问题"和"我不敢合"的拉扯。把争论拆开看,核心只有两个问题:这次改动有没有守住原来的业务语义;出了问题,谁能把它说清楚、兜得住。

第一个问题:代码能跑,还是团队还能看懂

老模块最麻烦的地方,不在于代码写得旧,而在于里面藏着很多没有写进文档的默认值。比如列表筛选里,status=all 和不传 status 在接口层可能不是一回事;首页的 page=1 不传,也许是为了让缓存键稳定。

原来的实现看起来很朴素:

ts 复制代码
type ListFilters = {
  keyword: string;
  status: "all" | "open" | "done";
  page: number;
};

export function toQuery(filters: ListFilters) {
  const query = new URLSearchParams();
  if (filters.keyword.trim()) query.set("q", filters.keyword.trim());
  if (filters.status !== "all") query.set("status", filters.status);
  if (filters.page > 1) query.set("page", String(filters.page));
  return query;
}

让 Claude Code、Codex 这类 agent 做"清理"时,很容易拿到一版更通用的写法:

ts 复制代码
export function toQuery(filters: ListFilters) {
  return new URLSearchParams(
    Object.entries(filters)
      .filter(([, value]) => Boolean(value))
      .map(([key, value]) => [key, String(value)]),
  );
}

类型没报错,页面也能请求成功。但新实现会把 status=allpage=1 都带出去。它是不是 bug,取决于后端约定、缓存策略和历史兼容逻辑,而不是取决于代码是否"更优雅"。

这正是第一场争论的来源。一边会说:既然行为看起来正常,就别被旧写法绑住;另一边会问:谁能证明这几个省略条件没有业务含义?两边都不是在反对 AI,而是在用不同标准判断风险。

评审时不妨先把"看懂"翻译成三个可验证的问题:

要确认什么 评审里该看什么
原有语义有没有保留 默认值、空值、边界页、权限态是否仍然一致
差异有没有被说出来 PR 描述能否列出"原来不传什么,现在为什么传"
下次能否快速定位 关键分支是否有用例,而不是只留一段生成后的代码

如果作者只能说"AI 跑过测试了",这份改动还没有达到可评审的状态。测试通过只说明覆盖到的行为没坏,不能替团队解释没被覆盖的约定。

第二个问题:出了问题,谁负责解释和回滚

另一种常见的争论更直接:既然改动是 AI 写的,出了线上问题算谁的?

这个问题问错了对象。AI 不是提交人,也不会参与线上排障。它可以生成一个跨多个文件的方案,却不能替任何人理解埋在老模块里的取舍。责任不会因为代码的产生方式改变,只会因为改动范围变大而更需要被写清楚。

一个能落地的分工应该是这样的:

角色 合并前要承担的事
改动发起人 解释目标、列出不允许变化的行为、补上回归用例
Reviewer 判断风险是否被覆盖,必要时要求拆小或补证据
AI 工具 提供实现候选、定位影响范围、协助生成测试,不拥有合并决定

这里最容易走向两个极端。一个极端是"它生成得快,先合再说";另一个极端是"祖传模块谁都别动,AI 更不能碰"。前者把合并当成试运行,后者把历史包袱当成不可改变的规则。

更稳妥的做法是给每次 AI 参与的老模块改动设一道很具体的门:没有说明不变量、没有最小回归测试、没有回滚方式,就不合。它和人工写的代码一样适用,只是 AI 往往一次触及更多文件,所以这道门更重要。

不要在大 diff 里讨论"AI 好不好用"

当一个 PR 同时改了请求参数、状态管理和 UI 组件,评审很容易失焦。有人盯命名,有人讨论模型能力,真正高风险的行为变化反而被淹没。

我更建议按下面的顺序拆:

  1. 先锁行为。 先写现有模块最不能变的 3 到 5 条规则,例如"默认筛选不发参数""切页时保留关键词"。
  2. 再让 agent 出方案。 在 Claude Code 或 Codex 中先要求它列出会影响哪些状态、接口和测试,再让它改代码;不要一上来就接受整份重写。
  3. 把改动拆成可回退的 PR。 表征现状的测试、逻辑重构、视觉调整分开提交。出了问题,回滚就不需要猜。
  4. 最后才看代码风格。 行为和边界没有共识时,争论 reduce 还是 for...of 没有意义。

前面的例子可以先补一组表征测试,把历史规则钉住:

ts 复制代码
import { expect, it } from "vitest";
import { toQuery } from "./toQuery";

it("首页默认筛选不写入查询参数", () => {
  const query = toQuery({ keyword: "", status: "all", page: 1 });
  expect(query.toString()).toBe("");
});

it("只在非默认状态和翻页时携带参数", () => {
  const query = toQuery({ keyword: "react", status: "open", page: 2 });
  expect(query.toString()).toBe("q=react&status=open&page=2");
});

这不是为了限制工具,而是为了让它的输出有边界。生成速度越快,越不能把"我大概看过了"当作评审结论。

评审里值得留下的四句话

下次碰到 AI 改老模块,可以直接把下面四句话写进 PR 描述或评审意见:

  • 这次明确不改变哪些用户可见行为?
  • 哪些默认值、空值和异常分支已经有回归测试?
  • 如果这个改动要撤回,最小回滚单元是什么?
  • 合并后第一个看告警和问题反馈的人是谁?

这四句话会把"AI 写得行不行"的空泛争论,拉回到团队真正能决策的地方。

AI 接手祖传模块不是不能做。不能接受的是,把一份更大的 diff 当成更高的效率,然后把理解成本留给下一个排障的人。

如果是你,会把底线设在"必须补齐回归测试",还是"必须把改动拆成小 PR"?评论区说说你的做法。

相关推荐
飞翔的熊blabla1 小时前
# Webpack 5 + Jenkins 持久化缓存实战:前端构建从 288 秒降到 30 秒>
前端·webpack·jenkins
风骏时光牛马1 小时前
AI多模态:跨越感官边界的智能新体验 要不要开启工作任务模式,帮你配套对应的文案和配图思路?
前端
IT_陈寒2 小时前
JavaScript的这个隐式转换特性差点让我加班到凌晨
前端·人工智能·后端
Loadings2 小时前
AI时代下的前端安全加固实践:安全、体积与性能之间的取舍
前端
计算机魔术师2 小时前
OpenAI 首个踩红线的 AI 模型来了:能自己挖漏洞,但只给少数人用
前端
闲坐含香咀翠2 小时前
从 132MB 到 25MB:列式存储怎么把 CPU 缓存命中率提上去的
前端·数据结构·性能优化
闲坐含香咀翠2 小时前
把 AntV S2 列头计算从 O(n²) 降到 O(n):一次开源组件的性能改造
前端·性能优化
闲坐含香咀翠2 小时前
Worker 常驻 + 零拷贝:postMessage 的结构化克隆算法与 Transferable 的真实代价
前端·性能优化