知

模块五 · AI 协作与表达

第 17 / 18 节19 分钟阅读型

AI 协作与代码评审:把生成变成产出

生成成本掉到接近零,审查、验证与责任一分没少。这一节给出上下文四件套、任务拆解粒度、读 diff 的四个检查面,以及一份能算清"到底省了多少"的账。

读完这一节,你能做到

  1. 说清 AI 改变了哪一步、没改变哪一步,不再用"效率提升"这种没口径的说法。
  2. 用「目标 / 现状 / 约束 / 验收」四件套写出一段 AI 真能用的上下文。
  3. 把一个大需求拆成"一次 code review 能看完"的粒度,并排出正确顺序。
  4. 用四个检查面审 AI 写出的 React 代码,当场指出它错在哪。
  5. 用指标算清一次 AI 协作是净赚还是净亏。

一、AI 改的是生成成本,不是判断责任

先把边界划清,否则后面所有结论都会跑偏。

环节以前现在变了吗
写出第一版30-90 分钟几十秒变了,缩了两个数量级
读别人的代码有注释可循无上下文、风格随机、量更大变难了
验证是否真的对自己写的,心里有数不知道它省略了什么变难了
出事谁负责作者还是作者没变
需求判断、边界取舍人还是人没变

由此得到三条工作纪律:

  • AI 负责"给出一个候选实现",你负责"证明它对这个场景是对的"。 候选可以错,证明不能缺。
  • 生成量越大,审查成本越高。 一次让 AI 写 800 行,你审不动,等于把 bug 直接合进主干。
  • 不上测试的 AI 代码不是快,是提前透支。 上线后排查的时间,一定大于当时省下的时间。

一句话:AI 让"写"接近免费,于是"判断"变成唯一的成本项。 这也是这一节要练的唯一能力。

二、上下文是新的需求文档

模型只看得到你给它的东西。它不知道你的 React 版本、你的目录约定、你不许引入 lodash、你的列表页要支持服务端分页。你不说,它就按训练数据里最常见的写法猜——最常见不等于适合你。

上下文四件套,缺一件就会返工:

  1. 目标:要解决谁的什么问题,验收时看哪个页面/哪个行为。
  2. 现状:React 与依赖版本、相关文件路径、现有实现长什么样、已有约定(状态放哪、请求走哪个 Hook)。
  3. 约束:不许新增依赖、不许改动公共组件、必须用 TS 严格模式、错误展示要复用现有 ErrorBanner。
  4. 验收:什么算完成。要具体到"输入 2 个字不发请求、清空后列表恢复全量、请求失败显示重试按钮"。

反例,也是绝大多数人的起手式:

帮我写一个订单列表页,要分页和搜索。

它多半会给你一个自带 useState + useEffect + fetch + 手写 loading 的组件。语法没错,但和你项目里 TanStack Query 的约定冲突,你需要几乎重写。不是它不行,是你没告诉它规则。

正例(同一件事,多写四行):

目标:订单列表页,支持关键词搜索与分页。
现状:React 19 + TS 严格模式;请求用 @tanstack/react-query,
      列表已有 <OrderTable />(src/components/order-table.tsx),
      项目里没有 axios,用 fetch 封装在 src/lib/http.ts。
约束:不新增依赖;搜索词进 URL query;不改 OrderTable 的 props 签名。
验收:输入停顿 300ms 才发请求;翻页保留旧数据;失败显示重试按钮;空结果有空态。

同一个模型,同一件事,产出质量差一个数量级。上下文不是礼貌寒暄,它是接口定义。

三、拆任务:一次只让它做一件可审的事

判断粒度只看一个标准:这次 diff 我能不能在 10 分钟内逐行看完并说清每块为什么这么写。 看不住,就再拆。

推荐顺序,因为它让每一步都能被上一步约束住:

① 定类型/接口 → ② 写最小实现 → ③ 补测试与边界 → ④ 重构与命名
  • 先定类型,AI 后面写的实现就被类型约束住了,幻觉字段会立刻报错。
  • 先写最小实现,再补边界,比"一次要求写完所有异常分支"更容易审。
  • 最后单独做一轮重构,不要和功能混在同一轮,否则 diff 里两种意图搅在一起,无法判断。

一个真实的反例:让 AI「一次做完列表页 + 搜索 + 分页 + 错误处理 + 单测」,产出 460 行。你审了 40 分钟,发现它把分页状态放进了本地 state 而不是 URL,导致刷新丢失;测试是对着自己写的 mock 测的,改实现就红。返工重来。拆成四轮,总耗时反而少。

四、读 diff 的四个检查面

按顺序检查,前一项不过就不用看后一项。

第一面:正确性(边界与竞态)

AI 写的 React 代码,错误高度集中在几个地方:

  • 列表 key 用下标,遇到插入、删除、排序就状态错位。
  • useEffect 依赖漏项,或为了消警告加 eslint-disable 而不是改成函数式更新。
  • 请求竞态:先发的请求后返回,覆盖了新数据。它通常不写 AbortController 或忽略标记。
  • 卸载后 setState:卸载时没清理定时器、订阅或未完成的 then。
  • 把可派生的值放进 state:useState(props.value * 2) 这类,后续不同步,成为最难查的 bug。
  • 受控组件只给 value 不给 onChange,输入框直接变只读。

第二面:依赖与真实性(幻觉)

AI 会编出"看起来很像"的 API。审的时候逐项确认:

  • 它 import 了包里不存在的导出吗?版本没装的属性吗?
  • 它用的 API 在你的版本里存在吗?(use、useActionState、useOptimistic 都是 React 19 才有)
  • 它悄悄新增了依赖吗?package.json 有没有被动过?
  • 它引用的文件路径、组件名,项目里真的存在吗?

第三面:一致性

命名、目录、错误处理方式、请求层、样式方案,和你项目现有代码是否同一套。风格分裂的代价不是难看,是下一个改这块的人要多读两套心智模型。

第四面:可测性

副作用有没有隔离出来?能不能在不 mock 组件内部的前提下写测试?如果它把请求、状态、渲染全揉在一个 200 行组件里,就是不可测,要求它拆。

四个面过完,你才拿到一个"可提交"的候选,而不是"能跑"的惊喜。

五、验证闭环:让"能跑"变成"有基线"

AI 写得越快,验证就是唯一的瓶颈。三道闸,按成本从低到高:

  1. 静态闸:tsc --noEmit + ESLint + 构建。能拦住幻觉 API、类型不匹配、未使用变量,几十秒。
  2. 行为闸:关键路径的单测/组件测试,外加一次真实手测。测试要测行为而不是实现——否则 AI 改实现你就得改测试。
  3. 指标闸:这次改动有没有把某条线弄坏。至少看两个数:渲染次数或交互延迟(用 Profiler / Performance 面板)、首屏或包体积(用构建产物对比)。

第三道闸最容易被跳过,也最容易在面试里被追问。"性能没变差"不是感觉,是前后两个数。

六、算例:同一个任务的两种做法

任务:给订单列表加"标记完成",要求乐观更新、失败回滚。以下时长为示意值,用来说明记账方式,不是行业基准。

做法 A:一把梭

项耗时
写提示词、等生成3 分钟
逐行 review 180 行25 分钟
发现分页状态放错位置,返工30 分钟
手测时发现失败不回滚,再改20 分钟
合计78 分钟

做法 B:拆四轮

轮次交给 AI 的部分自己做的部分耗时
① 类型mutation 的入参与返回定接口8 分钟
② 实现乐观更新的骨架补 onError 回滚与 cancelQueries12 分钟
③ 测试用例骨架补失败路径断言15 分钟
④ 重构抽 Hook命名与目录归位10 分钟
合计45 分钟

净收益公式:

净收益 = 生成节省的时间 − (审查时间 + 返工时间 + 线上事故成本)

做法 A 的"生成节省"更夸张,但审查与返工把它吃干净了;做法 B 把 AI 放在"产出候选"的位置,把判断留在自己手里,所以总额更小、风险更低。

还有一个隐性收益:做法 B 里那 15 分钟写测试、10 分钟改命名,是你在长本事;做法 A 里那 25 分钟 review 加 50 分钟返工,多半只是在收拾别人(模型)的烂摊子。

七、常见坑

  1. 用 AI 的答案去面试。 背下来的答案经不起一层追问:你说"快照",他问"那为什么函数式更新能读到新值",立刻断线。用 AI 学可以,用 AI 答不行。
  2. 把它当编译器用,不当实习生用。 实习生要交代背景、要给验收标准、要抽查;AI 同理,只给一句话就只能给你通用答案。
  3. 幻觉 API 直接上生产。 不存在的参数在开发环境可能静默失效,到线上才炸。
  4. 依赖被悄悄改动。 合代码前必看 package.json 与 lock 文件有没有变。
  5. 测试自己骗自己。 让 AI 写测试时,它常对 mock 断言而不对行为断言,实现一改就红、bug 一留就绿。
  6. 把业务数据贴进去。 订单号、用户手机号、内部接口地址、未公开的费率,都不该出现在公网模型里;脱敏或改用私有部署。
  7. 每人一套写法。 团队没有统一约定时,AI 会按每个人各自的提示词生成各自的风格,三个月后没人认得出这是同一个项目。

八、面试怎么讲

面试官现在稳定会问:"你平时怎么用 AI 写代码?"他不想听工具名,他想确认三件事:你有没有判断、有没有验证、有没有踩过坑。

四步答法:

怎么用:拆任务(类型 → 实现 → 测试 → 重构),一次只让它产出一屏能看完的 diff
怎么验:tsc + 单测 + 指标前后对比,三道闸都过才合
踩过什么坑:它给列表 key 用了下标,插入后输入框内容错位;后来把 key 规则写进团队约定
换来什么:同类任务从 78 分钟降到 45 分钟,返工率从"每两次一次"降到"每五次一次"

三个不要:不要说"提升 50%"却没有口径;不要说"全交给 AI"(等于自曝没有审查);不要贬低工具(显得没用过)。

反问环节可以问:"团队对 AI 生成的代码有 review 约定吗?"——这个问题会立刻把你和"只会喊 AI 提效"的候选人区分开。

九、动手任务:自写 → AI 写 → 对比 → 取舍

每题四步,不许跳步:① 自己写(限时,关补全)→ ② 让 AI 写同一题 → ③ 逐条比对 → ④ 说出保留谁、为什么。

① 自己写

  1. 写一个订单列表的搜索框:输入停顿 300ms 才发请求,清空恢复全量,请求失败显示重试。(限时 20 分钟)
  2. 写一个 useOrders Hook,支持分页与取消上一次请求,卸载时不再 setState。

② 让 AI 写同一题

把上面的题干原样交给 AI,补一句约束:React 19 + TS 严格模式,不新增依赖,不改现有组件 props。要求它标注"哪里不确定"。

③ 对比

检查面我的版本AI 版本差异说明
正确性(竞态/清理/依赖)
依赖与真实性(幻觉 API)
一致性(命名/目录/错误处理)
可测性(能否不 mock 内部)

④ 说取舍

  1. AI 版本里有没有你没一眼看出来的 bug?是竞态、清理,还是依赖?
  2. 它"多写"的部分里,哪些是该删的样板?
  3. 这段代码进 review,你会提哪三条意见?
  4. 下次同类任务,哪一步你会让它做、哪一步坚持自己写?

记录模板(一题一行,攒够 10 行就是你面试的案例库):

题目 | 我错在哪 | AI 错在哪 | 最终保留谁的写法 | 用时(我/AI/合计)

十、自测

  1. AI 让哪一步的成本发生了数量级变化?哪一步没变?
  2. 上下文四件套是哪四件?缺哪一件最容易返工?
  3. 判断任务拆分粒度的标准是什么?
  4. 读 diff 的四个检查面按什么顺序做?为什么先看正确性?
  5. AI 写的 React 代码,错误集中在哪几类?
  6. 净收益怎么算?为什么"生成越快越赚"这个直觉是错的?

参考答案要点:生成;审查、验证与责任。目标/现状/约束/验收,缺"约束"。一次 review 能在 10 分钟内逐行看完。正确性 → 依赖真实性 → 一致性 → 可测性,因为后面的检查在不正确的代码上没意义。key 用下标、依赖漏项、请求竞态、卸载后 setState、派生值进 state、受控组件缺 onChange。净收益 = 生成节省 −(审查 + 返工 + 事故),生成越快审查量越大。

配套题目

★ 越多越该先背;点题目可看完整答案与深入版。

提示:读完可以顺手做几道对应题目,检验是否真的理解