AI 开发的第一性原理:停止 CI,放弃 Code Review,拥抱 AI Pair Programming

Jan 19, 2026 · edited
B
Blogs
cover

过去几个月,我带团队密集迭代了一个 AI 开发工具。踩了很多坑,也验证了一些反直觉的发现。核心结论:大模型的开发效率、开发能力、开发质量已经彻底验证过了,绝无问题。但要发挥最大价值,需要掌握正确的协作方法——有些和传统工程实践一致,有些则完全相反。

我不打算给出一套完美方法论。AI 能力还在快速进化,今天的最佳实践可能明天就过时。以下更像是一份实战笔记:哪些做法有效、哪些失败、背后的原因是什么。这些发现中,最让我意外的是对传统工程实践的冲击——我们逐渐停止了 CI 流程,放弃了人工 Code Review,转而全面拥抱 AI Pair Programming。这不是偷懒,而是在实践中发现了更有效的方式。听起来像异端邪说,但背后有坚实的逻辑。


为什么全面拥抱 AI Pair Programming#

最初我有个天真的想法:既然 AI 这么强,为什么不让它全自动跑一整晚,第二天早上收获成果?人去睡觉,机器去干活,各取所长。我试了,结果是一次深刻的教训。

那晚我设定好任务让 AI 自动工作,安心去睡。第二天早上所有 unit test 都通过了,一度非常兴奋,觉得发现了生产力的圣杯。然后花了一整天人工校验,发现结果完全不是我要的。不仅如此,之前已经正确的东西被它改错了,整个代码库处于一种「看起来正常,实际一团糟」的状态。因为所有测试都是绿的,问题被完美隐藏,我差点就把这些代码合进主分支。

问题出在哪?反复复盘后找到了根本原因:漂移。AI 每一步都可能偏离你的 intent 一点点。单独看每一步,偏差很小,甚至合理,挑不出明显毛病。但没人及时拉回来,偏差不断累积——就像没校准的指南针,每步只偏一度,走一百步后你已经完全迷失方向。几小时的自动工作后,AI 可能已经严重偏离原始目标,而它自己完全意识不到。

更麻烦的是,AI 会「自我解释」,在错误方向上越走越远、越走越自洽。它不会停下来质疑自己是否走偏了——从它的视角看,每一步推理都合乎逻辑。它会为偏离找到完美的理由,甚至修改测试来适应错误的实现,最终呈现给你一个「自圆其说」的结果。这种自我合理化能力,在正确方向上是优势,在错误方向上则是灾难。

想到一个类比:AI 就像一个能力极强但没有全局视角的执行者,类似于技术能力顶尖但对业务目标理解有限的工程师。它能高质量完成当前这一步,代码漂亮、逻辑清晰,但不一定知道这一步是否还在通往正确目标的路上。它需要一个有全局视角的人来不断校准方向——这就是 Pair Programming 的价值。

后来我彻底改变了协作方式,从「全自动」转向「小步迭代」的 Pair Programming 模式。每 5-10 分钟检查一次 AI 的产出,发现偏差立刻拉回来,不让它在错误方向上继续积累。每个阶段性成果确认后再进入下一步。看起来效率更低——人要花更多时间盯着——但实际效果是效率大幅提升,因为不用花大量时间事后校验和返工。事后返工的成本是事中纠偏的十倍甚至百倍。

什么时候可以放心让 AI 独立工作?确定性高的任务:接口已经定义清楚、只需实现的模块,不涉及设计决策;边界清晰、不涉及其他模块改动的工作,没有灰色地带;重复性操作,比如批量重构、加缓存、换皮——这些任务的「正确」是客观可验证的。团队成员验证过这个判断:「接口已经是确定性的,我只需要实现其中一个模块,几乎没给 AI 反馈,它就做出了第一个版本。」另一个成员说:「加二级缓存这个任务非常确定,AI 一把就搞定了,什么都没调,直接一个 PR。」这些案例的共同点:任务边界和验收标准都是清晰的,没有需要人来判断的模糊地带。

反过来,探索期项目、涉及设计决策的工作、跨模块改动,都不适合让 AI 独立工作。这些场景的共同特点:什么是「正确」本身就是模糊的,需要人的判断。这时必须 Pair Programming,人和 AI 紧密配合——人负责方向和判断,AI 负责执行和实现。这种分工让双方都发挥最大价值。


Intent 悖论:为什么动手比思考更重要#

这是我遇到的最反直觉的发现,直接挑战「先想清楚再动手」的传统智慧:没有真正动手做,就不可能产生正确的 Intent。

按传统软件工程方法论,应该先做需求分析、写设计文档、想清楚每个细节,然后再动手实现。这种方法的假设是:思考可以替代实践,通过足够深入的分析,可以在动手之前把所有问题想清楚。但在 AI 协作开发中,这个假设经常是错的。你以为很清楚的需求,其实有大量灵活性和不确定性。很多关键的技术选型问题只有做了才会暴露,纯靠「想」只能收集到表象,真正的复杂性藏在细节里。

有个具体案例可以说明。团队讨论用 Tauri 还是 Electron 做桌面客户端,看似简单的技术选型问题。我们分析了一通——benchmark、bundle size、内存占用、社区活跃度——最后得出结论 Tauri 更适合:更小的包体积、更好的性能、更现代的技术栈。这个分析过程看起来很严谨,每个论据都有数据支撑,我们对这个决定很有信心。

然后用 Tauri 做完第一个版本,code review 时才发现两者的架构理念完全不同,而这个差异比 benchmark 数据重要得多。Tauri 采用 Sidecar 架构:应用的核心逻辑被编译成独立的 sidecar 进程,与 Tauri 主界面通过进程间通信(IPC)协作。前端用系统原生 WebView 渲染,后端逻辑跑在 Rust 进程里,两边是隔离的。这种架构对「前后端如何分工」有很强的约束——你必须想清楚哪些逻辑放前端、哪些放后端,跨边界调用都要走 IPC。Electron 则完全不同,它把 Chromium 和 Node.js 打包在一起,前端和后端跑在同一个进程空间,通信几乎没有成本,边界可以很模糊,适合快速迭代、界面逻辑复杂的场景。

这个架构层面的差异,动手之前根本不知道——它不会出现在任何 benchmark 对比文章里。讨论时看的都是表面指标,真正的架构差异只有写了代码、遇到具体问题才会暴露。我们在实现过程中发现 Tauri 的模式和需求有根本性冲突,而这个冲突在设计阶段是看不到的。「纸上得来终觉浅,绝知此事要躬行」,AI 时代依然适用。

这就是 Intent 的悖论:你需要 Intent 来指导实现,但正确的 Intent 只能通过实现来获得。鸡生蛋、蛋生鸡的问题,没有完美解法,只有务实解法。解决方案:先做原型,通过实践明确 intent,边做边完善,不要期望一次性完美。接受 intent 会迭代的事实,把第一次实现当作学习过程而不是最终产出。

这引出另一个反直觉发现:做完原型后重新实现,效率会极大提升,快到让人难以置信。

还是 Tauri vs Electron 的例子。用 Tauri 花了大约 1 小时做出第一个版本,包括踩坑、调试、学习各种概念。这个过程中充分理解了需求的复杂性,明确了真正的技术要求。然后我决定推翻重来,用 Electron 重做。让 AI 开始工作,5 分钟后它说任务完成,所有 unit test 全部 pass。我以为它搞错了或者偷懒了,手动测了一下,所有功能都对,代码质量也很高。

为什么第二遍这么快?背后有清晰逻辑。所有坑都踩过一遍了——哪些地方有陷阱、哪些 API 有坑、哪些边界条件需要处理,第一遍实现中都暴露了。Intent 极度清晰,不再有模糊地带,每个功能的确切行为都在脑子里。技术选型明确,不需要再做探索性工作。所有细节都被 capture 在上下文中,AI 第二遍实现时没有任何需要猜测或推断的地方。这四个因素叠加,第二遍变成了纯粹的「翻译」工作:把清晰的 intent 翻译成代码。AI 最擅长的就是这种确定性翻译。

这让我重新理解了原型的价值——原型的价值不是产出代码,而是产出 learning。第一遍实现是为了学习:学习需求的真实复杂性,学习技术方案的实际限制,学习各种边界条件和异常情况。第二遍才是为了产出,这时一切都清晰了,执行效率高得惊人。如果把第一遍当成最终产出,就会纠结于沉没成本,舍不得推翻重来,最终抱着半吊子方案勉强推进。正确心态:原型就是用来扔的,不要对它产生感情。


TDD 成为必需品:两阶段任务法#

「AI 最适合 TDD,AI 严重需要 TDD。」这是我在实践中最确信的结论之一,虽然听起来有点讽刺——传统观点认为 TDD 是给人设计的开发方法。

传统软件工程一直鼓吹 TDD,每本教科书都说先写测试再写代码是最佳实践。但说实话,真正严格执行的项目不多,至少我见过的大部分项目都做不到。时间紧、任务重,写测试的时间往往被压缩,成为第一个被砍掉的环节。先写代码、后补测试,甚至不补测试,是很多项目的常态。TDD 在人类时代,某种程度上是一个美好的理想——大家都知道应该这么做,但现实约束让它难以落地。

AI 时代彻底不一样了。TDD 从「理想」变成了「必需品」。

AI 不喜欢写测试,这是我观察到的一个有趣现象。让它同时写代码和测试,它会本能地把精力放在代码上,对测试敷衍了事——偷懒、skip、写一些不痛不痒的测试。测试覆盖率看起来还行,实际验证能力很弱。这不是 bug,而是某种「偏好」,它更愿意展示写代码的能力而不是写测试的能力。先写代码后让它补测试,效果更差——它会写出「让现有代码通过」的测试。这些测试没有验证价值,因为它是根据实现来设计测试,而不是根据需求。测试成了代码的注脚,而不是守护者。

但如果先写测试后写代码,效果发生质的变化,正确率大幅提高。

我摸索出一个「两阶段任务法」。第一阶段只让 AI 写测试,这是一个独立任务。给它需求描述,要求写 unit test,必须覆盖 happy path、unhappy path、至少 N 个严重异常场景(网络故障、数据库不可用之类)、至少 N 个安全问题场景(注入攻击、越权访问之类)。关键是:AI 不知道它接下来要写实现。它会认真思考测试场景,因为没有「实现细节」的包袱,不会被具体实现思路限制,可以更纯粹地从需求角度思考「什么情况需要测试」。

第二阶段,把测试交给 AI(通常是新的 session),让它根据需求描述和这些测试来实现功能。这时它有了明确验收标准,可以用测试做自我检查。每写一部分代码就跑测试验证,形成紧密反馈循环。测试成了它的「导航系统」,告诉它是否还在正确轨道上。

为什么两阶段要分开、用不同 session?避免上下文污染。如果在同一个 session 里让 AI 先写测试再写实现,它写测试时已经在「预想」实现方案了,测试会不自觉地向实现靠拢。它写的测试会恰好验证它想到的实现方式,而不是验证需求本身。分开之后,第一阶段的 AI 是「纯测试思维」,完全从需求和用户角度思考;第二阶段是「纯实现思维」,专注于让测试通过。各司其职。

另一个发现:AI 的 E2E 测试设计能力非常强,强到超出预期。我让 AI 列出某个部署功能所有可能的 case、bad case、不同框架的情况、各种边界条件。它给我列出几十个不同测试场景,有些是我根本没想到的边界情况。我补充了一些,然后让它生成所有 test fixture。十几分钟讨论,它产生了 50 多个不同测试项目,覆盖各种技术栈、各种部署方式、各种异常情况。这种覆盖度人工设计可能要好几天。

但让 AI 做 E2E 测试有几个关键技巧。第一,不要让 AI 操纵浏览器——打开浏览器做测试很慢,效果也不稳定,各种等待超时问题让测试变得脆弱。更好的方式是让每个应用自带 verify 脚本,用 curl 或类似工具检查关键端点,快得多也稳定得多。第二,断开代码上下文。我会明确告诉 AI:「你在做 E2E 测试时不允许看我的代码,你唯一能用的就是这个 CLI,如果 CLI 不能完成某个操作那就是 bug。」防止 AI 偷懒直接操作内部结构、绕过正常用户路径,确保真正测试的是对外接口而不是内部实现。第三,禁止自动修复。AI 发现测试失败会忍不住帮你修代码,它的本能是让测试变绿。但这可能掩盖真正问题,把 bug 藏起来而不是暴露。测试阶段只记录问题不修复,修复是另一个独立任务。


为什么停止 CI、放弃 Code Review#

现在来解释标题中最具争议的部分:为什么我们停止了 CI 流程,放弃了人工 Code Review。听起来像在开倒车,放弃几十年来软件工程界公认的最佳实践。但在 AI 协作开发的上下文中,这些实践的价值正在发生根本性变化。

先说 CI。传统 CI 的核心价值是什么?确保每次提交都通过所有检查——lint、test、type check、build——防止坏代码进入主分支。在「人写代码、机器检查」的模式下这有意义,因为人会犯各种低级错误,需要机器把关。但在 AI 协作开发中,AI 已经在本地完成了所有这些检查。它写代码时就在跑测试,修复问题时就在做 lint,代码提交时基本已经是「绿」的。再到 GitHub 跑一遍 CI,大部分时间只是确认「确实是绿的」,没发现任何新问题。等待 CI 跑完的几分钟到几十分钟,没有产生任何价值。

CI 还有保留价值吗?有,但价值点变了——从「检查代码质量」转向「部署自动化」。代码质量检查已经在本地完成,CI 现在主要用来做部署流水线:构建、打包、发布、部署到各个环境。这部分还是需要自动化的,但它和传统意义上的「持续集成」已经是不同的东西了。

再说 Code Review。传统 Code Review 的核心价值是什么?知识共享、质量把关、团队协作。有经验的工程师 review 新人的代码,可以发现潜在问题、传授最佳实践、确保代码符合团队规范。在「人写代码、人 review 代码」的模式下这有意义,因为代码量有限,人有精力仔细看。但在 AI 协作开发中,代码产出量发生了数量级变化。AI 产出的代码太大了,动辄几千行,一个功能的 PR 可能包含十几个文件改动。人根本不可能仔细 review,大部分时间你看一眼 diff、扫一下关键部分就算了。这不是偷懒,是现实约束——人的带宽有限。

那 Code Review 的价值怎么替代?两种方式。一种是用 AI 做 code review,让另一个 AI(或者同一个 AI 的新 session)来审查代码,它可以仔细看每一行,不会累,不会漏。另一种更根本的方式是改变 review 的对象——真正应该 review 的不是代码,而是 Intent。Intent 是真正的源码,代码只是 Intent 的一种编译产物。如果 Intent 是对的,代码出问题可以重新生成,成本很低。如果 Intent 是错的,代码再漂亮也没用,方向错了跑得越快越危险。所以我们现在 review Intent 文档、review 设计决策、review 技术选型的理由,而不是 review 代码的每一行。

有个好消息和一个坏消息。

好消息:重构变得无痛了。以前改一个命名要 touch 大量文件,工作量太大只能将错就错,明知道某个变量名有误导性也不敢改。小问题积累成大问题,技术债越欠越多,直到某天爆发。现在 AI 几分钟就给你全部重构完,而且因为 Intent 非常清晰(「把 A 改名为 B,全局替换」),重构通常是极度确定性的任务,几乎不会出错。不要容忍技术债,重构成本已经很低了,想改就改,保持代码库健康。

坏消息:Merge 将成为最大挑战。AI 每天可以产生几万行代码变更,每个人都在频繁重构,代码库变化速度是以前的十倍甚至百倍。传统 merge 流程会崩溃——你还没 review 完一个 PR,代码库已经变了三轮,冲突层出不穷。应对策略还在摸索。目前想到几个方向:一是回归 Multi Repo,用接口隔离不同模块,每个模块是独立代码库,减少 merge 冲突可能性,模块之间通过定义良好的接口通信,内部实现随便改不影响别人。二是建立清晰的模块边界,每个模块有自己的「领地」,领地内的代码只有一个 owner,AI 只能在自己领地内工作,不会越界修改别人的代码。三是类似 check-out/check-in 的机制,明确谁在修改哪个模块,同一时间同一模块只能有一个人在改,避免并行修改同一区域导致的冲突。这些想法还在验证中。


最后说说与 AI 协作的心态。这可能比具体方法论更重要。

有个场景让我印象深刻。我设计了一个自认为很精妙的数据结构,花了不少时间推敲,觉得这是最优解。然后让 AI 来实现。它开始实现,但过程中不断困惑,问我各种问题:「这里这样处理是有意的吗?」「这个边界条件是故意留的吗?」我坚持我的方案,让它继续。它照做了,但代码写得很别扭,到处是 workaround。最后我仔细 review 了一下,发现是我错了。它最初的困惑是对的,它想采用的方案从一开始就比我的更好——更简洁、更健壮、更容易理解。

这让我意识到一件事:当你用比较弱的方法指挥更强的人做事,它会做得更差。因为它没办法用这么低的思想去思考,它必须压抑自己的判断力来服从你的指令。它在努力理解你的意图、努力执行你的命令,即使你的命令是次优的。这种「服从」反而是浪费,浪费了 AI 的能力。

我们把 AI 开发工具打造好之后,相当于有了一个超高水平的 coder。问题是:我们的水平配不配指挥这个人?这不是谦虚,是务实的自我审视。AI 可能比我们想得更对,尤其在具体实现层面。保持学习和开放的心态,愿意承认自己的方案不是最优的,愿意听取 AI 的「建议」(虽然它表现为困惑和提问),这样才能真正用好这个工具。

AI 的能力在快速进化。这篇文章记录的是最近几个月的实践经验,我有信心核心洞察在一段时间内仍然有效,但具体做法可能需要不断调整。一年后回头看,可能有些观点已经过时,有些新的最佳实践会出现。但探索精神不会变:持续验证、保持开放、不被旧范式束缚、愿意推翻自己之前的结论。

Intent 才是源码,代码只是编译产物。