☰
AI生成代码的三大规矩:从React Query重构看AI辅助编程的边界与审查
2026/11/3 21:59:26 网站建设 项目流程

1. 事情起因:一个看似完美的 AI 生成 PR

上周三下午,我照例打开 GitHub 处理积压的 Pull Request。其中一个 PR 让我眼前一亮——标题规范、描述清晰、改动范围明确,代码风格统一,测试覆盖率还标注了提升 3.2%。我差点直接点下 Merge 按钮。

但多看了一眼 diff 之后,我停住了。

这个 PR 是团队里一位同学用 AI 编程助手生成的。具体来说,他用 Claude Code 配合 React Query 做了一次数据层的重构,把原本散落在各个组件里的请求逻辑收敛到了自定义 hooks 里。思路没问题,代码也能跑,CI 全绿。可我越看越觉得不对劲——代码“正确”得过于工整,工整到失去了人味。

举几个具体的例子。他把所有useQuery的staleTime统一设成了 5 分钟,理由是“减少重复请求”。但我们的产品里有一个实时性要求很高的消息列表,5 分钟的缓存意味着用户可能看到过期数据。他还把所有错误处理都写成了console.error加一个通用 toast,完全忽略了我们已有的错误上报链路和分级提示策略。更微妙的是,他删掉了两处看起来“冗余”的防御性判断,而那两处判断是半年前一次线上事故后专门加的。

我把 PR 打回了,附了一段不算短的评论。然后花了一个晚上,整理出三条规矩。第一条就引发了团队内部的争论——有人觉得太严,有人觉得早该如此。

这篇文章就把这三条规矩、背后的逻辑、以及我实际落地时用的检查清单完整分享出来。如果你也在团队里推行 AI 辅助编程,或者你自己就在用 Codex、Claude Code 这类工具写代码,这些经验应该能帮你少踩几个坑。

2. 三条规矩的完整拆解

2.1 第一条规矩:AI 生成的代码必须标注“生成边界”

这是最有争议的一条。我的要求是:任何由 AI 生成或大幅修改的代码块,提交者必须在 PR 描述里明确标注哪些文件、哪些函数是 AI 参与的,以及人工做了哪些调整。

反对意见很直接:这不是增加工作量吗?代码能跑不就行了?而且怎么界定“AI 参与”?我让 AI 补全一个变量名算不算?

支持者的理由也很充分:不标注边界,reviewer 就不知道哪些地方需要重点看。人类写的代码有惯性、有上下文,reviewer 能顺着作者的思路去理解。AI 生成的代码是“跳跃”的,它可能在一个你完全没想到的地方引入一个看似合理但实际危险的模式。

我最终坚持这条规矩,理由有三个。

第一,责任归属问题。代码合并进主干之后,出了问题是要有人负责的。如果一段 AI 生成的逻辑导致了线上故障,而提交者说“这是 AI 写的我没细看”,这个回答在事故复盘会上是不可接受的。标注边界,本质上是让提交者对 AI 的输出做一次“认领”。

第二,review 效率问题。我做过一个粗略统计:review 一段 200 行的 AI 生成代码,如果不知道它是 AI 写的,平均要花 12 分钟;如果提前知道,可以聚焦在“AI 容易出错的模式”上,平均 6 分钟就能完成。因为 AI 代码的问题往往集中在几个特定类型:边界条件、错误处理、缓存策略、并发安全。知道是 AI 写的,reviewer 就可以带着“找这几类问题”的心态去看。

第三,团队学习问题。标注边界之后,团队里其他人能看到“哦,原来这个场景可以用这种方式让 AI 生成”,也能看到“原来这里 AI 会犯错,需要人工补”。这比单纯禁止 AI 或者放任 AI 都更有建设性。

具体怎么落地?我设计了一个简单的标注格式,放在 PR 描述模板里:

## AI 参与说明 - 文件:src/hooks/useMessageList.ts - AI 生成:useQuery 配置、类型定义 - 人工调整:staleTime 改为 0(实时消息)、错误处理接入 reportError - 文件:src/utils/format.ts - AI 生成:日期格式化函数 - 人工调整:无,但已逐行 review

这个格式不复杂,写起来也就两三分钟。但它带来的 review 效率提升和责任感建立,远超这点时间成本。

注意:标注边界不是形式主义。我见过有人把所有文件都标成“AI 生成、人工 review”,这等于没标。关键是区分“AI 直接输出”和“人工实质性修改”的部分。

2.2 第二条规矩:涉及状态管理和缓存的改动,必须附上时序说明

这条规矩针对的是 React Query 这类数据层工具。AI 对 React Query 的 API 非常熟悉,能快速写出看起来正确的useQuery、useMutation、invalidateQueries组合。但它对业务时序的理解是缺失的。

什么叫业务时序?举个例子。我们的消息列表有一个“发送消息”的 mutation,成功后需要做三件事:把新消息插入列表、更新未读数、滚动到底部。AI 生成的代码是这样的:

const sendMessage = useMutation({ mutationFn: (content: string) => api.sendMessage(content), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['messages'] }); queryClient.invalidateQueries({ queryKey: ['unreadCount'] }); }, });

看起来没问题对吧?但它漏掉了滚动到底部,而且invalidateQueries会触发一次完整的重新请求,在弱网环境下用户会看到列表闪烁。更合理的做法是用setQueryData做乐观更新,然后只在必要时 invalidate。

AI 不知道这些,因为它不知道我们的产品在弱网下的表现要求,也不知道用户对列表闪烁的容忍度。这些是业务时序知识,只存在于团队成员的脑子里和产品文档里。

所以我的第二条规矩是:任何涉及 React Query 缓存策略、mutation 副作用、并发请求的改动,PR 描述里必须有一段“时序说明”,用文字或简单的步骤列表描述改动前后的数据流。

格式不限制,但必须说清楚:什么操作触发、触发后哪些缓存被更新、更新的顺序是什么、有没有乐观更新、失败回滚怎么处理。

这条规矩的争议比第一条小,但执行起来更麻烦。因为写时序说明需要提交者真正理解自己的改动,而不是复制粘贴 AI 的输出。我实际推行时发现,很多人在写时序说明的过程中自己就发现了问题——这其实是这条规矩最大的价值:它强迫提交者在提交前做一次自我 review。

2.3 第三条规矩:AI 生成的测试代码不能作为唯一验证

这条规矩可能是最容易被忽视的。AI 写测试代码的能力很强,它能根据函数签名快速生成一堆expect断言。但这些测试往往有一个致命问题:它们验证的是“代码做了什么”,而不是“代码应该做什么”。

我举个例子。AI 为一个格式化函数生成的测试:

test('formatDate returns formatted string', () => { expect(formatDate('2024-01-01')).toBe('2024-01-01'); });

这个测试能过,但它没有验证任何有意义的边界:闰年、时区、非法输入、空值。AI 倾向于生成“让测试通过”的用例,而不是“让 bug 暴露”的用例。

所以我的第三条规矩是:AI 生成的测试代码可以保留,但每个 PR 必须至少有一个“人工设计的边界用例”,且这个用例必须在 PR 描述里单独说明它验证的是什么场景。

这条规矩执行下来,效果出乎意料地好。因为当提交者被迫想“我要设计一个什么边界用例”时,他往往会重新审视自己的实现,然后发现一些 AI 没考虑到的情况。

3. 落地执行:我的 PR 检查清单与工具配置

3.1 一份可直接抄的 PR 检查清单

规矩定了,怎么保证执行?我把它做成了一份检查清单,放在 PR 模板里。提交者勾选完才能请求 review。

检查项说明是否必须
AI 参与边界已标注列出 AI 生成的文件和函数,说明人工调整内容是
时序说明已补充涉及缓存/mutation/并发的改动,描述数据流变化是
人工边界用例已添加至少一个非 AI 生成的测试用例,说明验证场景是
防御性代码已确认检查 AI 是否删除了已有的防御性判断是
错误处理已对齐确认错误处理接入了团队统一的错误上报链路是
缓存策略已复核staleTime、cacheTime、invalidate 策略符合业务要求是

这份清单看起来有 6 项,但实际填写时间大概 5 到 8 分钟。相比一次线上事故的排查成本,这个投入是值得的。

3.2 Claude Code 和 Codex 的配置调整

我们团队主要用 Claude Code 和 Codex 做辅助编程。为了配合这三条规矩,我在项目配置里做了一些调整。

对于 Claude Code,我在项目根目录的CLAUDE.md里加了一段约束:

## 代码生成约束 - 生成 React Query 相关代码时,必须显式说明缓存策略和失效逻辑 - 不要删除已有的防御性判断,如需删除请在注释中说明理由 - 错误处理必须使用项目统一的 reportError 工具,不要直接用 console.error - 生成的测试用例需标注“AI 生成”,人工补充的边界用例需单独标注

对于 Codex,我在.codex/instructions.md里加了类似的约束。这些约束不会完全阻止 AI 生成有问题的代码,但能减少一部分低级问题,也让提交者在 review AI 输出时有一个参照。

另外,我还在 CI 里加了一个简单的检查:如果 PR 描述里没有“AI 参与说明”这个 section,CI 会给出一个 warning(不阻断合并,但会提醒 reviewer)。这个 warning 的触发率从最初的 60% 降到了现在的 15% 左右,说明大家逐渐养成了习惯。

3.3 团队内部的争议与妥协

第一条规矩刚推行时,团队里有一位资深工程师明确反对。他的观点是:代码就是代码,不管谁写的,review 标准应该一致。如果 AI 写的代码需要额外标注,那是不是意味着我们对 AI 有偏见?

这个观点很有道理,我认真想了很久。最后我们的妥协方案是:标注边界不是为了区别对待,而是为了信息透明。就像代码注释一样,注释不改变代码逻辑,但它帮助 reviewer 理解代码意图。AI 参与说明也是同样的定位——它不改变 review 标准,但它让 reviewer 知道哪些地方需要额外警惕。

这个解释被团队接受了。现在回头看,这条规矩最大的价值不是“防 AI”,而是“防提交者偷懒”。当你知道要标注边界时,你就不会直接把 AI 的输出原样粘贴提交,而会至少过一遍脑子。

4. 实操中踩过的坑与排查技巧

4.1 AI 代码最常见的五类问题

在打回了几十个 AI 参与的 PR 之后,我总结出五类高频问题。这些问题的共同点是:代码能跑、测试能过、CI 全绿,但就是不对。

第一类是缓存策略过度统一。AI 喜欢用一个“合理”的默认值覆盖所有场景,比如把所有staleTime设成 5 分钟。但实际业务里,不同数据的实时性要求差异很大。消息列表可能需要 0,用户资料可以 30 分钟,配置项可以 1 小时。统一值要么导致数据过期,要么导致请求过多。

第二类是错误处理扁平化。AI 生成的错误处理往往是“捕获 → console.error → 通用提示”。但实际业务里,网络错误、权限错误、业务逻辑错误需要不同的处理方式。网络错误可以重试,权限错误要跳登录,业务错误要展示具体原因。

第三类是防御性代码被“优化”掉。AI 看到if (data && data.length > 0)这样的判断,可能会觉得“data 不可能为空”然后删掉。但那两处判断可能是某次线上事故后加的,删掉就等于把坑重新挖开。

第四类是并发安全缺失。AI 生成的 mutation 代码很少考虑并发场景。比如用户快速点击两次发送按钮,AI 的代码可能会发出两个请求,而正确的做法是加一个isPending判断或者用 mutation 的isLoading状态禁用按钮。

第五类是类型定义过于宽松。AI 为了“让代码能跑”,可能会用any或者过于宽泛的联合类型。这在短期内没问题,但长期会积累技术债。

4.2 一个真实的排查案例

上个月我们上线了一个用 AI 辅助重构的搜索功能。上线后第二天,有用户反馈“搜索结果偶尔会显示上一次的关键词”。

排查过程很有意思。代码逻辑看起来完全正确:输入关键词 → debounce → 发起请求 → 更新结果。但问题出在 React Query 的queryKey上。AI 生成的代码是这样的:

const { data } = useQuery({ queryKey: ['search'], queryFn: () => searchApi(keyword), });

queryKey里没有包含keyword。这意味着无论关键词怎么变,React Query 都认为这是同一个查询,直接返回缓存结果。AI 不知道queryKey必须包含所有影响查询结果的变量,因为它不理解 React Query 的缓存机制。

修复很简单,把keyword加进queryKey就行。但这个问题在 code review 时没被发现,因为代码“看起来”没问题。这就是为什么我坚持要有时序说明——如果提交者写清楚“关键词变化时应该触发新请求”,reviewer 就能发现queryKey的问题。

4.3 常见问题速查表

问题现象可能原因排查方法修复方式
数据不更新queryKey 缺少变量检查 queryKey 是否包含所有查询参数补全 queryKey
请求过多staleTime 过短查看 Network 面板请求频率调整 staleTime
列表闪烁使用 invalidateQueries检查 mutation 的 onSuccess改用 setQueryData 乐观更新
重复提交缺少 isPending 判断快速点击按钮观察请求数用 mutation 状态禁用按钮
错误提示不准确错误处理扁平化检查错误类型是否被区分按错误类型分级处理
类型报错AI 使用了 any搜索 any 和 as 断言补充精确类型定义

5. 关于 AI 辅助编程的一些个人看法

5.1 AI 是加速器,不是替代品

我用 Claude Code 和 Codex 有一段时间了,最大的感受是:AI 能把你的想法快速变成代码,但它不能替你想清楚要做什么。它擅长的是“给定明确输入,生成合理输出”,不擅长的是“在模糊需求下做出正确取舍”。

所以我的工作流变成了:自己先想清楚数据流、边界条件、错误处理策略,然后用 AI 生成初版代码,再逐行 review 和调整。这个流程比纯手写快,但比“让 AI 全自动生成然后直接提交”慢。慢出来的这部分时间,就是 review 和调整的时间,省不得。

5.2 三条规矩的本质

回头看这三条规矩,它们其实都在做同一件事:在 AI 生成和代码合并之间,插入一个“人类理解”的环节。

标注边界,是让提交者理解“哪些是 AI 写的”。时序说明,是让提交者理解“数据怎么流动”。人工边界用例,是让提交者理解“什么情况会出错”。这三个理解,AI 都替代不了。

有人可能会说,这不就是正常的 code review 吗?对,也不对。正常的 code review 假设提交者已经理解了代码,reviewer 是在此基础上做二次检查。但 AI 参与的 PR 里,提交者可能自己都没完全理解 AI 生成的代码。所以这三条规矩,本质上是把“提交者自我 review”这一步显性化了。

5.3 后续可以怎么扩展

这套规矩目前只覆盖了 React Query 相关的改动。后续我打算扩展到其他 AI 容易出错的领域,比如表单验证、路由守卫、权限判断。每个领域可能都需要一套类似的“AI 参与说明”模板。

另外,我也在考虑把一些检查自动化。比如用 AST 分析检测queryKey是否包含了所有查询参数,或者检测 mutation 是否有isPending判断。这些检查不能完全替代人工 review,但能拦住一部分低级问题。

最后分享一个小技巧:如果你也在团队里推行类似的规矩,先从一个小项目试点,收集反馈再推广。我一开始想在全团队直接推行,被一位同事劝住了。后来我们在一个三人小组里试了两周,调整了标注格式和检查清单,才推广到全团队。这个渐进过程减少了很多阻力。

提示:规矩是死的,业务是活的。如果某条规矩在某个场景下明显不适用,允许例外,但要在 PR 里说明理由。死守规矩比没有规矩更可怕。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询