引言
2025年2月,Andrej Karpathy 在 X 上发了一条后来引爆技术圈的推文:
There's a new kind of coding I call "vibe coding", where you fully give in to the vibes, embrace exponentials, and forget that the code even exists. It's possible because the LLMs (e.g. Cursor Composer w Sonnet) are getting too good. Also I just talk to Composer with SuperWhisper so I barely even touch the keyboard. I ask for the dumbest things like "decrease the padding on the sidebar by half" because I'm too lazy to find it. I "Accept All" always, I don't read the diffs anymore. When I get error messages I just copy paste them in with no comment, usually that fixes it. The code grows beyond my usual comprehension, I'd have to really read through it for a while. Sometimes the LLMs can't fix a bug so I just work around it or ask for random changes until it goes away. It's not too bad for throwaway weekend projects, but still quite amusing. I'm building a project or webapp, but it's not really coding - I just see stuff, say stuff, run stuff, and copy paste stuff, and it mostly works.
这条推文迅速走红,也创造了一个新的概念:Vibe Coding。它很快被自媒体解读成一种“只要会说人话,不需要真正理解代码,也能借助AI构建产品”的软件开发新范式。
然而,这种解读大多是选择性地截取了推文的前半部分,如果仔细读完这条推文,会发现 Andrej Karpathy 在推文的后半部分已经明确限定了边界:
It's not too bad for throwaway weekend projects, but still quite amusing. I'm building a project or webapp, but it's not really coding - I just see stuff, say stuff, run stuff, and copy paste stuff, and it mostly works.
throwaway weekend projects——这指的是周末随手搭建、用完即弃的实验性项目。也就是说,Karpathy 在这里所描述的 Vibe Coding,本质上是一种偏实验性、快速试错导向的开发方式,并不是一种可以直接迁移到商业软件交付中的严肃开发方法论。
但它确实揭示了一件更重要的事:我们与代码交互的方式,正在发生改变。
那么,Vibe Coding 究竟是什么呢?
简单来说,它是一种用自然语言“对话”来驱动编程的方式。
你可以把向 AI 描述需求的过程,想象成向一位技术能力极强、执行速度极快的工程师布置任务。你说出想要的功能、界面、交互或修复目标,AI 生成代码;你运行、观察效果、粘贴报错或继续补充需求,AI 再修改代码。整个过程形成了一个高频迭代的闭环。
在最纯粹的Vibe Coding 形式里,用户甚至完全不关心AI 生成了什么代码,只在意应用“看起来对不对”“能不能跑起来”。这确实大幅降低了将一个想法快速变为可运行原型的门槛。对于个人工具、内部系统、早期创业项目和产品原型来说,一个人借助 AI 就能在几个小时内搭出 landing page、后台系统、数据处理脚本,甚至一个完整的 MVP。
但在真实的软件开发场景中,Vibe Coding 更多时候并不是“一句话造应用”,而是一种 AI 增强型开发方式:开发者通过自然语言、运行反馈、错误信息、测试结果和代码审查来引导 AI 生成与修改代码。Vibe Coding 的本质不是“不写代码”,而是开发者的角色发生了变化:
- 从
coding转向directing - 从
implementation转向orchestration - 从手写代码转向管理意图、上下文和验证
Vibe Coding 最令人兴奋的地方在于其惊人的效率,但问题也恰恰出在这里。
如果你用 AI 写过成体系的代码,你很快会发现:AI 的确很强,但如果你只是不断用简短 prompt 让 Coding Agent 往前写,结果通常不太理想。第一版代码往往看起来还不错,页面能打开,功能能跑通,demo 也足够惊艳。可随着你不断让 AI 再加一个功能、顺手修一下 bug、把这里改成那样,代码质量会开始一点点下滑。
更糟糕的是,这种下滑通常不是突然发生的,而是缓慢积累的。这其实并不是 AI 时代才出现的问题。《程序员修炼之道》里有一个概念:软件熵。熵增是宇宙的基本规律,万事万物都会倾向于变得混乱,软件系统也一样。每一次缺乏约束的改动,都会让系统朝着更混乱的方向滑一点。AI 生成代码的速度比人快十倍、百倍,所以它带来的熵增速度也比以前快得多。
可以想象两种截然不同的代码库:
- 结构清晰的代码库:结构清晰、命名一致、模块边界明确、测试完整。AI 能快速理解上下文,精准修改,成为开发者的“杠杆”,将产出放大十倍。
- 混乱的代码库:像一锅乱炖,同一个概念有三种叫法,核心逻辑散落各处,测试几乎为零。AI 每次修改都要先猜半天,改完还经常牵一发而动全身。在这里,AI 不是杠杆,而是“混乱放大器”,将问题放大十倍。
这也印证了 Matt Pocock 在 AI Engineer 大会上的观点:Software Fundamentals Matter More Than Ever(软件工程基本功没有过时,反而比以前更值钱了)。AI 不是只会增强代码生产力,它也会放大代码的混乱。
Vibe Coding 真正的问题并不是“能不能用 AI 写代码”。答案显然是能,而且会越来越强。真正的问题是:我们用什么来约束 AI?用什么来表达需求?用什么来验证正确性?用什么来控制软件熵?
这也是为什么,在最初的 Vibe Coding 热潮之后,开发者开始重新关注一系列更工程化的 AI 开发模式。这些模式是将“凭感觉和 AI 聊天写代码”升级为一套可靠、可控、适合长期项目的 AI 编程工作流的关键护栏。
- SDD (Spec-Driven Development):用规格说明来约束 AI,解决“需求到底是什么”的问题。
- TDD (Test-Driven Development):用测试用例来约束 AI,解决“代码是否正确”的问题。
- BDD (Behavior-Driven Development):用用户行为和验收场景来约束 AI,解决“业务是否符合预期”的问题。
Vibe Coding 是 AI 时代软件开发的新入口,而 SDD、TDD、BDD 等工程化实践,才是让它从“随手做个 demo”走向“可维护系统”的关键护栏。
接下来,我们将从这些模式开始,探讨如何构建一个更可靠的 AI 编程工作流。
软件开发方法
TDD
测试驱动开发(Test-Driven Development,TDD)是一种以自动化的单元测试来驱动软件设计与实现的敏捷实践。它颠覆了“先写代码,后补测试”的传统方式,要求开发者严格遵循“测试先行”的纪律,通过极短的开发循环来构建高质量、可测试且设计简洁的系统。
核心概念
TDD 三定律(Robert C. Martin 提出)
- 第一定律:在编写一个失败的单元测试之前,不允许编写任何生产代码。
- 第二定律:只允许编写恰好导致测试失败的单元测试(编译不通过也算失败)。
- 第三定律:只允许编写恰好能让当前失败测试通过的生产代码。
循环:红-绿-重构(Red-Green-Refactor)
这是 TDD 最核心的,每一次功能增量都在这三步间循环:
- 红(Red):编写一个无法通过的小测试。这一步验证测试本身的有效性,并明确当前要实现的具体行为。
- 绿(Green):以最快的速度让测试通过,哪怕代码实现得“丑陋”、生硬。此阶段只关注让测试条变绿,不关注设计。
- 重构(Refactor):在测试全部通过的保护下,消除代码中的重复、提炼抽象、改善结构,以提升设计质量。重构后测试必须保持绿色。
实际案例:字符串计算器
需求1:空字符串返回 0
- 红:编写第一个测试。因
StringCalculator类不存在,编译即失败。
// 测试代码:StringCalculatorTest.java
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class StringCalculatorTest {
@Test
void shouldReturnZeroForEmptyString() {
StringCalculator calc = new StringCalculator();
assertEquals(0, calc.add(""));
}
}
- 绿:创建类和方法,用最简实现让编译通过且测试变绿
// 生产代码:StringCalculator.java
public class StringCalculator {
public int add(String numbers) {
return 0; // 刚好让测试通过
}
}
- 重构:当前无重复,代码极简,跳过。
需求2:单个数字返回自身
- 红:新增测试,
"1"应返回1
@Test
void shouldReturnNumberForSingleNumber() {
assertEquals(1, new StringCalculator().add("1"));
}
- 绿:修改生产代码让两个测试都通过。最次最快实现。
public int add(String numbers) {
if (numbers.isEmpty()) return 0;
return Integer.parseInt(numbers);
}
- 重构:虽然引入了一个分支,但目前足够清晰,暂不重构。
需求3:两个数字(逗号分隔)返回和
- 红:
"1,2"应返回3。
@Test
void shouldReturnSumOfTwoNumbers() {
assertEquals(3, new StringCalculator().add("1,2"));
}
- 绿:用逗号分割并求和
public int add(String numbers) {
if (numbers.isEmpty()) return 0;
String[] parts = numbers.split(",");
int sum = 0;
for (String part : parts) {
sum += Integer.parseInt(part);
}
return sum;
}
- 重构:这里的空判断可融入循环逻辑(空数组长度 0,求和为 0),让代码更一致。重构后测试必须绿。
public int add(String numbers) {
String[] parts = numbers.split(",");
int sum = 0;
for (String part : parts) {
if (!part.isEmpty()) {
sum += Integer.parseInt(part);
}
}
return sum;
}
此时发现空字符串 split(",") 得到数组 [""],上面逻辑能让 sum 保持 0。测试仍然通过。
需求 4:支持换行符作为分隔符
- 红:
"1\n2,3"返回6。
@Test
void shouldHandleNewlinesAsDelimiters() {
assertEquals(6, new StringCalculator().add("1\n2,3"));
}
- 绿:将分隔符正则改为
[,\n]。
String[] parts = numbers.split("[,\n]");
- 重构:提取分隔符正则常量,消除魔数。
需求 5:支持自定义分隔符(格式:b)
- 红:
"//;\n1;2"应返回3。
@Test
void shouldSupportCustomDelimiter() {
assertEquals(3, new StringCalculator().add("//;\n1;2"));
}
- 绿:检测自定义分隔符前缀,动态提取分隔符和数字部分。
public int add(String numbers) {
String delimiter = "[,\n]";
if (numbers.startsWith("//")) {
int delimiterEnd = numbers.indexOf("\n");
delimiter = numbers.substring(2, delimiterEnd); // 获取分隔符
numbers = numbers.substring(delimiterEnd + 1);
}
String[] parts = numbers.split(delimiter);
int sum = 0;
for (String part : parts) {
if (!part.isEmpty()) {
sum += Integer.parseInt(part);
}
}
return sum;
}
- 重构:
add方法承担了解析和计算两项职责,违反单一职责原则。提取解析方法parseInput,返回数字字符串数组。
public int add(String numbers) {
String[] parts = parseNumbers(numbers);
int sum = 0;
for (String part : parts) {
if (!part.isEmpty()) {
sum += Integer.parseInt(part);
}
}
return sum;
}
private String[] parseNumbers(String numbers) {
String delimiter = "[,\n]";
if (numbers.startsWith("//")) {
int delimiterEnd = numbers.indexOf("\n");
delimiter = numbers.substring(2, delimiterEnd);
numbers = numbers.substring(delimiterEnd + 1);
}
return numbers.split(delimiter);
}
重构后所有测试依然绿灯。如需支持负数抛异常,则再写一个红测试 shouldThrowExceptionForNegatives,然后绿实现检查并抛出,最后重构提取校验方法。
BDD(Behavior-Driven Development)
TDD确保代码是可测试的,并且编写的测试满足需求。TDD有助于在开发周期的早期识别缺陷,减少修复缺陷的成本并提高代码质量。TDD还鼓励开发人员编写简单、模块化和可维护的代码。
行为驱动开发(BDD)是TDD的扩展,它关注系统的行为,而不是实现细节。BDD基于这样一种思想,即系统的行为应该以技术和非技术利益相关者都容易理解的方式进行描述。
在BDD中,测试是用描述系统行为的更自然的语言编写的。这些测试被称为“场景”,通常以“Given When Then”语句的形式编写。BDD鼓励开发人员、测试人员和业务利益相关者之间的协作,以确保每个人都对系统的行为有共同的理解。
BDD有以下三个阶段:
- 发现:识别描述系统行为的场景。
- 公式化:用每个人都容易理解的自然语言写出场景。
- 自动化:使用测试框架自动化场景。
BDD经常用于敏捷软件开发方法论,其中强调团队成员之间的协作和沟通。BDD鼓励开发人员和测试人员从用户的角度考虑系统的行为,而不仅仅是技术细节。
行为驱动开发(BDD)是TDD的一个扩展,专注于软件的行为。BDD使用领域特定语言(DSL)以人类可读的格式描述所需的行为。BDD强调开发人员、测试人员和利益相关者之间的协作,以确保软件满足业务需求。
在BDD中,行为是根据场景来描述的,场景描述了用户和系统之间的交互。每个场景都由一组Given When Then步骤组成。Given步骤指定初始条件,When步骤指定操作,然后步骤指定预期结果。
BDD确保软件满足业务需求,并以预期的行为交付。BDD改善了利益相关者之间的沟通,减少了误解,并确保软件具有所需的功能。
DDD
DDD(Domain-Driven Design)