作者:Moka 张豪
开场:一个真实的故事
去年十月到年底,我用 Vibe Coding 的方式做了一个内部项目——「首席程序员」。
从零到一的阶段,Vibe Coding 是真的爽。需求说完,AI 哗哗地写,代码跑起来了,功能上线了,一切顺利得让人飘飘然。
直到有一天,来了一个很简单的需求——接口需要支持工作区隔离。需求很清楚,设计很简单。AI 三下五除二写完了,一如既往地告诉我:“Perfect。”
然后这个”简单”的功能,断断续续调了两周。
有一个异步相关的 bug,偶尔复现。我让 AI 去查,AI 每次都 100% 自信地说修好了。但代码一发布,我们一测试——还是偶尔有问题。AI 再改,再说修好了。再测——还是 TMD 有问题。
反复几轮之后,我决定自己去看代码。
不看不知道,一看吓一跳。这个项目才一个多月,已经是屎山了。一个函数上千行,异步嵌套到处都是,光看根本搞不清楚执行的先后顺序,看得我眼睛疼,头疼,血压飙升。
那一刻我意识到:
AI 生成的代码,不一定 100% 正确,但它一定 100% 自信。
而我,在 Claude Code 一声声 “Perfect” 和 “完美” 中,逐渐迷失了自我。我感觉自己像一个昏君,不问朝政,所有事情全部交给权臣去做,出问题之后,难辨忠奸,难断是非。
那天晚上我开始认真思考一个问题:我到底应该怎么和 AI 协作?
这篇文章就是我的答案。我把它分成三层——从看清角色变化,到理解本质矛盾,再到掌握新的工作范式。
第一层:角色进化——从”写代码的人”到”定义问题的人”
古法编程 vs AI Coding
回顾一下编程方式的演变:
- Type Coding——每一行代码都是键盘一个字一个字敲出来的。写一个函数,从函数签名到实现到边界处理,全靠手速。
- Tab Coding——前几年 Copilot 出现,你写几个字,Tab 一下,代码补全了半行、一行、甚至一整段。效率提升了,但本质没变——你还是在”写”,只是写得更快了。
- AI Coding——Cursor、Claude Code、Codex。你描述需求,AI 直接生成整个模块。生成代码这件事,已经不需要人亲力亲为了。
从 Type 到 Tab 到 AI,变化的不只是速度,是角色。Type 和 Tab 时代,人是生产者,机器是辅助。但到了 AI Coding 时代,这个关系反转了——AI 变成了生产者,人变成了定义者和裁判。 这不只是效率提升,而是角色质变。
人的核心产出:约束
AI 接管了代码实现,那在 AI Coding 的模式下,人的核心产出是什么?
我认为答案是约束。
类型定义、性质约束、架构规范、测试规格——这些东西告诉 AI”什么是对的”,限定它的输出边界。没有约束的 AI,就像开场故事里的那样——100% 自信,但并不 100% 可靠。
在 AI 协作的语境下,约束即编程,规格说明即源代码。
一个例子——“实现一个除法函数”。
如果你对 AI 说”写一个除法函数”,它可能给你 10 种不同的实现——有的忘了处理除零,有的返回 NaN,有的抛异常。你需要逐一检查。
但如果你给出精确约束:
function divide(a: number, b: NonZeroNumber): Result<number, DivisionError>
// 性质 1:divide(a, 1) === a (除以 1 等于自身)
// 性质 2:divide(a, b) * b ≈ a (乘除可逆)
NonZeroNumber 在编译期就消除了除零的可能性。Result 类型明确了错误必须被显式处理。两条性质约束了函数的数学行为。到这一层,AI 几乎只能给出正确的实现。
你写了多少行实现代码?零。但你的类型、性质、正确性边界——压缩了 AI 的搜索空间,让它从”在巨大解空间中猜测”变成了”在精确约束内推导”。
当然,不是所有编程工作都能被约束覆盖——交互设计、性能调优、系统集成中的胶水代码,这些仍然需要人的判断和手艺。但 AI 能自动化的部分,约束的质量决定了产出的质量。
第二层:本质矛盾——Easy to Generate, Hard to Verify
AI Coding 时代最根本的矛盾,可以用两条曲线的剪刀差来描述。
- 代码产能曲线:指数级增长。AI 让个人产能提升 2-5 倍,明天可能是 10 倍、100 倍。
- 验证能力曲线:线性增长,甚至原地踏步。
两条曲线之间有一个越来越大的剪刀差。
为什么验证这么难?因为 LLM 本质上是一个概率模型——给定上下文,预测最可能的下一个 token。它不”理解”正确性,只计算概率。幻觉不是 bug,是概率生成模型的内生属性。
回想开头的故事。没有任何约束的 Vibe Coding,一个多月就生成了一座屎山。AI 生成这些代码只要几分钟,但我们验证一个偶发的异步 bug,断断续续花了两周。
这里有一个核心关系:
增加约束 → 压缩 AI 的输出空间 → 压缩验证范围 → 降低验证难度
反过来说,减少约束 → 输出空间爆炸 → 验证范围爆炸 → 验证永远追不上产出。
借用 Claude Shannon(没错,Claude 的名字就来源于信息论之父)的一个核心直觉:信道容量有限时,必须最大化有效信息的密度。LLM 也有一个”信道容量”——它的上下文窗口。无论你给它多少材料,它能处理的永远是有限的 token 数。
所以关键不是”到处加约束”,而是知道在哪里加。有些地方必须严格,有些地方可以放手——但凭什么判断?
第三层:新范式——概率与确定性的共舞
悬崖与草原
这是我的核心比喻。
- 悬崖:错误代价不可承受的地方——数据模型、状态机、安全规则、核心业务逻辑。出了错后果不可逆。必须悬崖勒马。
- 草原:试错成本低的地方——UI 样式、工具函数、原型探索。AI 的创造力和灵活性是优势。可以让 AI 纵马奔腾。
判断标准只有一个问题:“这里出错的代价是什么?“
两种计算范式
AI 时代的软件开发,本质上是两种计算范式的协作:
- 概率计算(LLM):在巨大的解空间中搜索生成,有创造力,但代价是不保证正确。
- 确定性计算(编译器 / 类型系统 / 测试框架):给出确定的对或错,但代价是不能创造。
有了这两个概念,悬崖与草原就有了本质定义:
- 悬崖 = 必须由确定性计算守护的区域
- 草原 = 可以交给概率计算驰骋的区域
ChromeMCP → Playwright:最漂亮的协作模式
让我用一个具体的例子来说明这两种计算范式如何协作。
ChromeMCP 让 LLM 直接操作浏览器。它本质上是概率性的——LLM 根据自己的”理解”决定点击哪里、输入什么。同一个页面交互做五次,可能得到五次不同的操作路径。这种不确定性是 LLM 的固有特性,不是 bug。
Playwright 则相反。它是确定性的自动化脚本。一个 page.click('#submit'),每次执行都点同一个按钮,不会某次突然决定去点页面左上角的 logo。
有意思的地方在哪里?你可以用 ChromeMCP 去探索一条操作路径,然后把探索的结果固化为 Playwright 脚本。
流程是这样的:
- 路径发现(概率计算):LLM 通过 ChromeMCP 操控浏览器,在页面上摸索操作步骤。这个过程是创造性的、探索性的。LLM 可能会走错路,然后纠正,最终找到正确的操作序列。
- 路径固化(确定性计算):把探索出来的正确操作序列,自动生成为一个 Playwright 脚本。从此以后,这条路径是可重复、可验证的。
- 异常处理(循环):当确定性脚本失败时,回到概率路径排查原因,再把修正固化为新脚本。
写代码也是同一种模式。类型签名定义了输入和输出(起点和终点),AI 在中间自由探索实现方式(概率路径),最终产出的代码经过编译和测试验证(确定性固化)。
这就是概率与确定性的共舞——路径发现是概率性的,路径固化是确定性的。
三个角色:创造者、守护者、定义者
把上面的协作模式浓缩一下,就是三个角色构成的三角。
创造者(AI / 概率计算):在草原上搜索和生成候选方案。善于探索,不善于保证。
守护者(确定性系统):驻守在悬崖边守住底线——编译器、类型检查器、测试。它们不创造,只判定。
定义者(人):决定哪里是悬崖、哪里是草原,用契约连接创造者和守护者。定义者的水平,决定了这个三角协作的上限。
契约的三个维度
定义者通过契约(即约束)来沟通:
- 结构维度——数据结构、模型定义。告诉 AI”数据长什么样”。
- 行为维度——性质与不变量。告诉 AI”什么是对的”。
- 边界维度——架构与模块化。局部性好的架构,AI 只需要看 2-3 个文件就能正确生成修改。
真实案例:首席程序员项目翻车后的重建
回到开头那个故事。翻车之后,我重新审视了整个项目,发现根源就是三角协作的缺位——创造者在狂奔,但既没有定义者划定边界,也没有守护者站岗。
第一步:定义者——用契约划定悬崖与草原
让 AI 写了一份 500 多行的 CLAUDE.md,覆盖三个维度:
- 结构:禁止裸 dict/list 返回,必须定义数据模型
- 行为:术语消歧表(4 种 Session、3 种 API Key、2 种 Project)+ 11 条 Bad Case 反模式规则
- 边界:强制分层 API → Service → DAO → Models,禁止跨层调用
第二步:守护者——用确定性系统守住底线
设置了一个 Git Hook:pre-commit 检测环境变量 CLAUDE_COMMIT=1,没有此标记直接拦截。只有通过专门的 Commit Skill 才会设置此变量。代码在 commit 时就被守住了。
第三步:让概率与确定性共舞
E2E 测试分三层策略:优先跑 Playwright 确定性脚本,覆盖不到的让 AI 操控浏览器探索,探索成功的路径再固化为 Playwright 脚本。
效果立竿见影。之前 80% 的时间花在验证 AI 输出上,之后彻底改变。
FAQ 补充
“我用的是 xxx 语言,没有强类型怎么办?” 类型约束不等于静态类型语言。我们强调的是数据结构层面的概念——用任何语言都可以定义数据模型和业务规则。
“需求本身就是模糊的,怎么精确定义?” 定义约束的过程就是迫使你把模糊变清晰的过程——这本身就是价值所在。模糊的需求交给 AI,得到的是模糊的代码。
“这不就是 Design by Contract 吗?” 是的。新的是语境——当 AI 成为主要代码生产者时,契约从”锦上添花”变成了**“不可或缺的协作协议”**。
结语
回顾全文,三层进化总结:
第一层:意识到角色变了——从写代码的人,到定义约束的人。
第二层:理解本质矛盾——易生成难验证。约束压缩 AI 的输出空间,降低验证难度。不加约束的 AI 产能越高,你被埋得越深。
第三层:掌握新范式——概率与确定性的共舞,创造者、守护者、定义者的三角协作。
最后送大家一句话:
AI 不会替代程序员,但会替代没有判断力的程序员。
Moka 张豪,2026 年 3 月 11 日
关于作者:Moka 张豪,资深软件工程师。在 AI Coding 的浪潮中从一个昏君变成了一个还算清醒的皇帝。本文基于他在技术分享会上的演讲整理而成。