我最早用 AI 写代码的时候,其实并不怎么信它。
不是因为它完全不能用,而是它总给我一种很奇怪的感觉:它可以很快写出一段“看起来像样”的代码,但你如果继续往下追问两层,通常就会发现这段东西只是把表面需求拼出来了,结构、边界、历史包袱、性能和回滚成本,它并不真的在乎。
那时候我把它当高级补全。写个函数、补个类型、翻个正则,够用了。再复杂一点,我宁愿自己来。
后来我开始在真实项目里持续使用 Claude Code 和 Codex,想法才慢慢变了。不是因为它们忽然变成了“会独立交付的工程师”,而是因为我发现了一件更重要的事:如果你让 AI 做对的事情,它能显著缩短从“我大概知道要改哪儿”到“我确认能安全下手”之间的那段时间。
这段时间,恰恰是日常开发里最耗人的部分。
真正的开发工作,很多时候不是写代码本身,而是下面这些动作反复叠在一起:
- 把模糊需求翻译成技术问题。
- 在项目里定位相关模块。
- 弄清楚数据从哪儿来、状态往哪儿走。
- 判断改动面会不会失控。
- 猜测历史实现里藏了什么约束。
- 决定先做最小修复,还是顺手整理结构。
- 想清楚这次改动最容易把什么地方弄坏。
以前这些动作只能自己做。现在我会让 AI 先陪我走第一遍。
但这里有个前提:我不会让它在没有上下文的时候直接写。
它最适合做的,不是“替我开发”,而是替我把问题翻开
如果非要给 Claude Code 和 Codex 在我工作里的角色下个定义,我更愿意把它们叫做“可交互的工程阅读器”。
很多人谈 AI 编程,喜欢把焦点放在“它一分钟写了多少代码”。我现在反而越来越少关注这个。
因为代码生成本身,从来都不是最难的部分。真正难的是:你到底知不知道自己应该改哪儿,为什么改这儿,改完会影响谁。
比如有时候我接到一个需求,描述是这样的:
这个页面的筛选体验不太对,用户找不到自己想看的内容。
这句话对产品来说已经够了,但对工程来说几乎没有信息量。它可能是:
- 当前筛选条件没有被持久化,用户一返回页面就丢失状态。
- 列表页和详情页之间状态没有共享,导致体验断裂。
- URL 没有表达筛选条件,刷新后恢复不了。
- 数据请求层在筛选变化时重复触发,造成抖动。
- 文案写得不清楚,用户根本不知道自己筛了什么。
如果我直接让 AI “优化筛选体验”,它通常会很积极地改一堆 UI。按钮更圆了,标签更明显了,空状态也更漂亮了。问题是,这些改动大概率没有打到真正的根上。
所以我现在更常见的开场方式是:
先不要改代码。
请阅读列表页、筛选组件、路由参数处理和数据请求相关文件。
告诉我当前筛选状态是如何产生、存储、同步和触发请求的。
最后列出最可能导致体验不稳定的三个点。
这一步很重要。它不是在拖慢开发,恰恰是在避免后面返工。
我需要先确认 AI 有没有读到真正相关的文件,有没有理解现在的状态链路,有没有把局部现象误判成系统问题。只有这一步说得通,后面让它动手才有意义。
我为什么不让它上来就写
有些人第一次看我用 AI,会觉得我有点“浪费工具能力”。明明一句“帮我实现”就能开始写,为什么还要先让它读、让它解释、让它出方案?
原因很简单:AI 在陌生代码库里最危险的地方,不是写错,而是写得太顺。
它会非常自信地沿着你给的目标往前冲,把路径中遇到的每个阻碍都当成局部技术问题来处理。它不太会天然意识到:
- 这个项目是不是故意没用某个库。
- 这段代码虽然丑,但背后是不是兼容了某个历史输入。
- 这个 hook 写法很怪,是不是为了 SSR / hydration / 小程序环境做过妥协。
- 这个 API 看着可以合并,实际上是不是被三个旧页面依赖了不同边界。
人类工程师看到这些东西,会本能地产生怀疑。AI 不会,除非你明确逼它停下来审视。
所以我的流程里有一个几乎固定的要求:先读,再说链路,再给方案,最后才允许改。
通常我会让它先做两件事。
第一件事是复述现状。不是泛泛而谈,而是把调用链讲清楚。
比如:
请按“入口组件 -> 状态来源 -> 数据请求 -> 子组件消费”的顺序解释当前实现。
如果有你不确定的地方,请明确标出来,不要猜。
第二件事是出一个小范围计划。
请给我一个最小改动方案。
要求:
1. 保持现有技术栈和目录结构
2. 不引入新依赖
3. 不做顺手重构
4. 标出可能影响的文件
这里我最关心的,其实不是它计划本身有多漂亮,而是它有没有表现出对“边界”的理解。
一个成熟开发者面对真实项目,第一反应不是“我还能顺便优化哪些地方”,而是“这次只应该碰哪些地方”。AI 恰恰容易在这里失控。
它在代码阅读这件事上,确实帮我省了很多时间
我现在最依赖 AI 的场景,反而不是生成,而是读代码。
尤其是下面几类问题,它真的很有帮助:
1. 老模块接手
有些模块你不是没能力改,而是不想花半小时先建立脑图。
这时候我会让它做信息压缩:
请读完这个模块,回答我三个问题:
1. 这个模块的单一职责是什么?
2. 它依赖哪些外部状态或服务?
3. 如果我要改这里,最可能波及到哪几个调用方?
注意这里不是让它“总结代码”。总结代码最容易变成废话。我要的是可操作信息。
2. 定位影响范围
改一个接口签名、一个字段名、一个公共 hook,最怕不是改不动,而是漏改。
这种时候 AI 很适合先帮我扫一遍:
请找出这个 hook 的所有调用点,并按以下类别分类:
1. 只读消费
2. 写入状态
3. 依赖返回值结构
4. 可能受这次修改影响的边界调用
它未必一次就全对,但通常能先帮我把搜索面缩小一大截。
3. 解释历史实现
有时候你看到一段奇怪代码,会直觉觉得“这应该能简化”。我现在一般不会立刻动手,会先让 AI 用怀疑的方式读一遍:
这段实现为什么会写成这样?
请从兼容性、性能、异步时序、SSR/CSR 边界四个角度推测可能原因。
如果无法确认,请明确说是推测。
很多时候它给不出正确答案,但它会帮我列出几个值得检查的方向。这比我自己对着一坨旧代码发呆要有效率得多。
真正开始改代码时,我只让它做小步动作
我对 AI 写代码最核心的控制方法,其实很土:一次只让它完成一个局部目标。
不是因为它做不了大改,而是因为大改的审查成本太高。
举个很常见的例子。一个列表页要补完整状态,按理说它会涉及 loading、empty、error、retry、权限不足、筛选无结果等多个分支。AI 完全可以一口气都做了,甚至顺手帮你把组件拆掉、样式也整理一下。
问题是,当它同时改 8 个文件时,你的注意力会被迫分散。你一旦没逐行读清楚,就很容易把“逻辑是否正确”和“改动看起来很多”混在一起。
所以我更倾向这样下指令:
这次只补 empty 和 error 两个状态。
不要改数据请求层,不要拆组件,不要调整筛选逻辑。
完成后列出所有修改文件,并说明每个修改解决了什么问题。
你会发现,AI 在这种任务里通常表现很好。因为目标明确,边界清楚,判断标准也清楚。
相反,如果你说“顺便优化一下这个页面结构”,那它就会开始自由发挥。自由发挥本身不可怕,可怕的是它发挥时不承担后果。
review 阶段,我会让它换一个脑子再看一遍
AI 写完代码之后,我不会立即把它当结果。我会把它当“第一版补丁”。
接下来有一个动作我现在基本都会做:让它切到 review 视角重新看自己的改动。
提示词一般很简单:
请把刚才的修改当作别人提交给你的 patch 来 review。
不要继续改代码,只列问题。
优先关注:
1. 行为回归
2. 类型问题
3. 异步边界
4. 空值和异常处理
5. 是否有不必要的重构
这个动作 surprisingly 有用。因为它在生成模式下追求的是“完成目标”,在 review 模式下会更容易暴露自己刚才跳过的东西。
我看过它在这一步指出过很多典型问题:
- 某个
map默认假设数组存在,但接口实际允许返回null - 服务端 action 返回错误时,前端少了一个状态兜底
- 一个客户端组件偷偷依赖了服务端生成的数据格式
- 为了省事用
any压掉了一个本来应该修的类型冲突 - 样式在桌面端正常,但移动端宽度溢出
当然,它也会说一些很“教科书”的无效意见,所以最终还是要我自己判断。但把它当成第二双眼睛,值。
测试这件事,AI 真能帮上忙,但前提是你别让它自嗨
我对 AI 参与测试的态度,经历过一个变化。
一开始我会让它“补测试”,结果通常是它很热情地给我写出一套看上去完整、实际上价值不高的用例。断言很多,覆盖率看起来也不错,但真正关键的行为没有被保护,或者测试过于依赖实现细节,稍微一重构就全碎。
后来我意识到,问题不在“AI 不会写测试”,而在于我给它的任务太宽泛。
现在我会先让它回答一个更小的问题:
如果这次改动出 bug,最可能坏在哪里?
请按必须保护 / 可以暂缓 两类列测试点。
这一步比直接写测试重要得多。因为测试的本质不是凑数量,而是保护最脆弱的行为。
比如一个联系表单,如果只从用户视角看,真正需要保护的通常是这几件事:
- 必填项校验是否生效
- 提交中按钮是否禁用,防止重复提交
- 服务端返回错误时用户能否得到明确反馈
- 成功后是否进入稳定且可感知的完成状态
如果它先把这几个测试点列对了,我才会让它往下生成代码。
前端单元测试我一般会这样给约束:
请为这个表单组件补测试。
只测用户可观察行为,不要断言内部 state。
不要依赖 className 或动画细节。
覆盖初始渲染、校验失败、提交中禁用、错误提示和成功状态。
后端单元测试则更偏输入和异常分支:
请为 submitContact 这个 Server Action 设计测试。
先列 case,再写代码。
重点覆盖字段缺失、邮箱格式错误、Turnstile 失败、邮件发送异常和成功提交。
至于 E2E,我现在只让它用 Cypress 做一条最关键的路径,而不是追求铺满全站。
因为 E2E 最贵,不管是执行时间还是维护成本都高。它应该保护真正关键的业务闭环,而不是充当“所有细节都测一下”的安慰剂。
请基于当前页面结构生成一条 Cypress E2E 测试。
用户进入首页,滚动到联系区域,填写表单,并验证失败提示是否正确显示。
只覆盖核心路径,不要测试视觉动画。
我会特别提醒它两件事:
- 不要为了测试好写去修改业务代码。
- 不要写盯着实现细节的脆弱断言。
AI 在测试上的价值,不是帮我神奇地生成一套完美测试,而是把“我到底该保护什么”更快说清楚。
Claude Code 和 Codex 在我手里的分工,其实更像两个工作阶段
我并不会非常教条地区分“这个工具只能干什么,那个工具只能干什么”。但在实际使用里,它们在我工作流中的位置确实不太一样。
当问题还处在模糊阶段,我通常更愿意跟 Claude Code 来回讨论。
比如:
- 一个模块到底该不该拆
- 这个需求应该先做最小版本还是顺手治结构病
- 当前架构哪里最可能成为下一轮迭代的阻力
- 一组用户反馈背后到底是交互问题还是状态建模问题
Claude Code 在这种长上下文、需要持续推演的对话里更像一个能陪你把问题掰开的同伴。
而当问题已经比较明确,需要真正贴着代码库执行时,我会更依赖 Codex。
例如:
- 沿着项目结构去定位调用链
- 做小范围代码修改
- 补一段测试
- 跑一轮静态检查
- 整理当前改动摘要
它给我的感觉更接近“在工作目录里和你一起干活的工程搭子”。
当然这不是绝对分工。更准确地说,它们对应的是我开发里的两个阶段:
- 想清楚问题
- 把问题落实到代码
一旦你把这两个阶段混在一起,AI 就会开始乱跑。
我现在常用的一些提示方式,其实都很朴素
用了这么久,我反而越来越少写那种很花哨的提示词。
真正管用的,通常就是几句简单但边界明确的话:
- “先分析,不要改代码。”
- “先说调用链和影响范围。”
- “如果不确定,请明确说不确定。”
- “给我最小改动方案,不要顺手重构。”
- “先列测试点,再写测试代码。”
- “把刚才的改动当作 code review 重新检查。”
- “说明你验证了什么,没有验证什么。”
这些话的作用,是把 AI 从“表演完成任务”拉回“参与工程判断”。
我现在越来越觉得,提示词最重要的不是语法技巧,而是你是否真的知道自己要它帮你做哪一层工作。
如果你自己都没分清是要它帮你读代码、拆需求、给方案、写 patch 还是补测试,那它最后大概率会给你一份看起来勤奋、实际上不够可靠的输出。
哪些事情我还是不会交给它
尽管我现在已经很习惯把 Claude Code 和 Codex 放进日常开发里,但有几类判断我依旧只相信人来做最终拍板。
第一类是产品判断。
一个功能该不该做、做到什么程度、当前阶段值不值得做,并不是代码问题。这里面有用户、业务、节奏、资源和取舍。AI 可以帮我整理讨论材料,但它不能代替对目标负责的人做决定。
第二类是架构边界。
AI 很容易给出“看起来更优雅”的重构建议,但一个系统该不该拆模块、要不要引入新层、某个状态是否应该上升为全局能力,这些都不是局部代码能回答的。它牵涉的是未来成本,而不是当前 patch 是否漂亮。
第三类是安全与权限。
涉及认证、权限、密钥、支付、用户数据的时候,我会把 AI 的输出当草稿,而不是答案。因为这些地方一旦出错,后果不是“代码不优雅”,而是真事故。
最后也是最重要的一类,是责任。
不管代码最初是谁写出来的,只要进了仓库,它就是工程的一部分。只要我还没把它读懂,我就不会让它合进去。
最后说一句真实感受
如果你问我,Claude Code 和 Codex 到底改变了什么,我不会说“开发速度翻倍”这种话。
更准确的说法是:它们降低了很多开发动作的启动成本。
以前你对一个陌生模块有疑问,可能要先花二十分钟建立上下文,才能开始真正思考。现在你可以先让 AI 帮你把第一遍链路理出来,再由你决定哪些地方值得信、哪些地方还要自己继续查。
以前你碰到一个模糊需求,可能会在“先改哪里”这件事上卡很久。现在你可以先让它把问题拆成几个备选路径,再用工程判断挑一条最稳的走。
以前你写完一个改动,review 可能主要靠你自己脑补边界。现在你至少多了一双会不停帮你找空值、异步、回归和测试缺口的眼睛。
它没有替代工程能力,但它确实把工程能力放大了。
前提是,你得先把它当协作者,而不是许愿机。