乐于分享
好东西不私藏

AI 时代的工程实践指南:别让 AI 把你的代码库变成屎山

AI 时代的工程实践指南:别让 AI 把你的代码库变成屎山

写在前面:你可能正在经历的痛苦

如果你正在用 Claude、Cursor、GitHub Copilot 这些 AI 工具写代码,下面这些场景你一定不陌生:

场景 1:需求对不上

你:“帮我做个用户管理功能”AI:(噼里啪啦写了 500 行代码)你:(一看)“我要的是批量导入啊,你这只能一个个添加!”AI:“好的,我来改”(又写了 300 行)你:“不对不对,我要的是……”

一个下午过去了,代码越改越乱。

场景 2:代码能跑但看不懂

AI 生成的代码能跑,测试也过了。但是…… 10 个文件互相调用,改一个地方要动 5 个文件。新来的同事看了一眼:“这什么玩意儿?”你:“我也不知道,AI 写的……”

场景 3:Bug 越修越多

修了一个 Bug,冒出来三个新 Bug。AI 说:“我帮你修”,然后又引入了两个 Bug。你开始怀疑人生:用 AI 到底是加速了还是减速了?

场景 4:三个月后的噩梦

项目刚开始:AI 真香,代码飞快!三个月后:改不动了,每次改动都心惊胆战。六个月后:重构吧,这坨代码没救了。

如果你中了 2 个以上,这篇文章就是为你写的。


核心问题:AI 让你写得快,但不会让代码变好

Matt Pocock(TypeScript 圈的知名专家)在用了一年多 AI 编程工具后,总结出了一个残酷的真相:

AI 能让你以 10 倍速度制造混乱,也能以 10 倍速度创造价值。关键在于:你有没有一套适配 AI 的工程实践。

他开源了自己每天在用的 Skills 工具集,专门解决 AI 编程中的四大陷阱。这不是又一个“AI 框架”,而是一套让 AI 成为你工程伙伴的方法论。


四大陷阱与破解之道

陷阱 1:需求错位 —— “我以为你懂我”

真实场景

产品经理:“做个订单取消功能。”

你让 AI 写了代码,产品经理一看:

“不对啊,我要的是:

  • 已支付的订单取消要退款

  • 已发货的不能取消

  • 部分商品可以单独取消

  • 取消后库存要回滚

  • 要给用户发通知“

你:“你早说啊!”产品经理:“我以为你知道啊!”

这就是需求错位。 《程序员修炼之道》里说得好:“没有人确切知道自己想要什么。” 人和人之间都会错位,何况人和 AI?

破解之道:让 AI 先“拷问”你

/grill-with-docs 技能就是干这个的。在写代码前,让 AI 对你的计划进行“灵魂拷问”:

你:做个订单取消功能

AI:好的,我先问几个问题:

1. 哪些状态的订单可以取消?

   - 待支付?已支付?已发货?已完成?

2. 取消后钱怎么办?

   - 原路退回?退到余额?多久到账?

3. 库存怎么处理?

   - 立即回滚?延迟回滚?

4. 部分取消支持吗?

   - 一个订单 3 件商品,能只取消 1 件吗?

5. 取消失败怎么办?

   - 退款失败?库存回滚失败?

6. 用户怎么知道?

   - 站内信?短信?邮件?

问完这些,你会发现: 你以为的“简单功能”,其实有 20 个分支要处理。

更重要的是,AI 会把这些决策记录到 CONTEXT.md 文档里,下次再做相关功能,它就知道你们项目的“行话”了。


陷阱 2:语言不通 —— “你说的订单和我说的订单不是一个东西”

真实场景

你的项目里有这些概念:

  • Order(订单)

  • Cart(购物车)

  • Payment(支付)

  • Transaction(交易)

AI 不知道你们的定义,结果:

  • 有时候把 Order 和 Transaction 混着用

  • 有时候 Payment 里又包含了 Order 的逻辑

  • 代码里同一个东西有 5 种叫法

三个月后,新来的同事:

“这个 OrderTransaction 和 TransactionOrder 有啥区别?”你:“呃…… 我也不太清楚了……”

破解之道:建立“黑话词典”

CONTEXT.md 就是你项目的“黑话词典”。

没有 CONTEXT.md 的对话:

你:实现订单取消功能,取消后要处理支付和库存

AI:好的(写了 200 行代码,创建了 OrderCancellation、

     PaymentRefund、InventoryRollback、

     CancellationTransaction 四个类)

有 CONTEXT.md 的对话:

CONTEXT.md 里写着:

- Order:订单,包含 items(商品列表)和 payment(支付信息)

- Order.cancel():取消订单,自动触发退款和库存回滚

- Refund:退款记录,由 Payment.refund() 创建

你:实现订单取消功能

AI:好的,我在 Order 类里加个 cancel() 方法,

    它会调用 this.payment.refund() 和 

    this.items.forEach(item => item.restoreStock())

对比一下:

  • 没词典:4 个新类,200 行代码,概念混乱

  • 有词典:1 个方法,20 行代码,清晰明了


陷阱 3:盲飞 —— “代码能跑就行了吧?”

真实场景

你:帮我写个用户注册功能

AI:(写了代码)

你:看起来不错,提交!

// 上线后...

用户 A:邮箱里有加号注册不了

用户 B:密码太短也能注册

用户 C:注册成功了但是登录不了

你:AI,帮我修这些 Bug

AI:(改了代码)

// 结果...

用户 D:现在邮箱里有点号也注册不了了

用户 E:密码限制太严格了,我的密码不让用

问题在哪? AI 在“盲飞”—— 它不知道代码跑起来是什么样的。

《程序员修炼之道》说:“反馈速率就是你的速度限制。” 没有反馈,AI 就是在瞎猜。

破解之道:先写测试,再写代码

/tdd 技能教 AI 用“测试驱动开发”:

// 第 1 步:写一个会失败的测试

test('用户可以用有效邮箱注册'async () => {

const user = awaitregister({

email'test@example.com',

password'SecurePass123!'

  });

expect(user.id).toBeDefined();

expect(user.email).toBe('test@example.com');

});

// 运行:❌ 失败(因为还没写代码)

// 第 2 步:写最少的代码让测试通过

asyncfunctionregister({ email, password }) {

const user = await db.users.create({ email, password });

return user;

}

// 运行:✅ 通过

// 第 3 步:再写一个测试

test('邮箱格式不对应该报错'async () => {

awaitexpect(

register({ email'not-an-email'password'SecurePass123!' })

  ).rejects.toThrow('邮箱格式不正确');

});

// 运行:❌ 失败

// 第 4 步:加上邮箱验证

asyncfunctionregister({ email, password }) {

if (!isValidEmail(email)) {

thrownewError('邮箱格式不正确');

  }

const user = await db.users.create({ email, password });

return user;

}

// 运行:✅ 通过

这样做的好处:

  1. AI 有反馈 —— 每写一点代码就知道对不对

  2. 你有信心 —— 改代码时测试会告诉你有没有搞坏

  3. 新人友好 —— 测试就是活文档,看测试就知道功能怎么用

关键反模式:别一口气写完所有测试

❌ 错误做法:

写 10 个测试 → 写实现 → 发现测试写错了 → 全部重写

✅ 正确做法:

写 1 个测试 → 写实现 → 通过 → 写下一个测试 → ...

就像爬楼梯,一步一步来,而不是先画 10 层楼梯图纸再一口气爬。


陷阱 4:代码腐化 —— “三个月后看不懂自己写的代码”

真实场景

项目刚开始:

// 清晰简单

functioncheckout(cart) {

const order = createOrder(cart);

processPayment(order);

return order;

}

三个月后,AI 帮你“优化”了 20 次:

// 变成了这样

functioncheckout(cart) {

const validator = newCartValidator(cart);

if (!validator.validate()) {

thrownewValidationError(validator.getErrors());

  }

const orderBuilder = newOrderBuilder();

  orderBuilder.setCart(cart);

  orderBuilder.setUser(cart.user);

  orderBuilder.setShippingAddress(cart.shippingAddress);

const order = orderBuilder.build();

const paymentProcessor = PaymentProcessorFactory.create(order.paymentMethod);

const paymentResult = await paymentProcessor.process(order);

if (paymentResult.status === 'success') {

    order.setPaymentId(paymentResult.id);

await order.save();

awaitnewOrderNotifier(order).notify();

  }

return order;

}

问题:

  • 原来 3 行代码能做的事,现在 20 行

  • 引入了 5 个新类,每个类都很“浅”(接口复杂,功能简单)

  • 改一个地方要动 5 个文件

《软件设计哲学》里说:“最好的模块是深的 —— 简单的接口,强大的功能。”

破解之道:定期“体检”代码

/improve-codebase-architecture 技能帮你找出这些问题:

AI 分析后:

发现 3 个"浅层模块":

1. CartValidator

   - 问题:只是简单调用 cart.items.forEach(validate)

   - 建议:删掉这个类,把验证逻辑放到 Cart 类里

   - 好处:少一个类,逻辑更集中

2. OrderBuilder

   - 问题:只是把参数传来传去

   - 建议:Order 构造函数直接接收 cart

   - 好处:代码从 5 行变成 1 行

3. PaymentProcessorFactory

   - 问题:只有一个 create 方法,而且只支持一种支付方式

   - 建议:等有第二种支付方式再抽象

   - 好处:不要过度设计

“深层模块”vs“浅层模块”的比喻:

浅层模块 = 万能遥控器

  • 表面上 100 个按钮,看起来很强大

  • 实际上就是把 5 个遥控器的按钮都搬过来

  • 你还是要记住每个按钮是干啥的

深层模块 = Siri

  • 接口简单:说句话就行

  • 功能强大:背后调用了一堆 API

  • 你不用管它怎么实现的

好代码应该像 Siri,不是万能遥控器。


四个核心技能深度解析

技能 1:/grill-with-docs —— 开工前的“灵魂拷问”

为什么需要它?

你有没有遇到过这种情况:

你:做个用户搜索功能

AI:好的(写了 200 行代码)

你:不对,我要的是模糊搜索

AI:好的(改了 100 行)

你:还要支持按标签筛选

AI:好的(又改了 150 行)

你:还要支持排序

AI:...(开始重构)

问题在哪? 你以为自己想清楚了,其实没有。AI 也不知道该问什么。

它是怎么工作的?

第 1 步:AI 开始“拷问”

你:做个用户搜索功能

AI:好的,我先确认几个问题:

1. 搜索范围

   - 只搜用户名?还是也搜邮箱、手机号?

   - 要搜用户的其他信息吗(比如公司、职位)?

2. 搜索方式

   - 精确匹配还是模糊匹配?

   - 支持拼音搜索吗?

   - 支持缩写吗(比如"张三"搜"zs")?

3. 结果展示

   - 一次显示多少条?

   - 需要分页吗?

   - 需要高亮匹配的部分吗?

4. 性能要求

   - 用户量级是多少?

   - 搜索速度要求多快?

   - 需要缓存吗?

5. 权限控制

   - 所有人都能搜所有用户吗?

   - 需要隐藏某些用户吗?

第 2 步:你回答问题的过程中,发现了很多之前没想到的

你:

1. 搜用户名和邮箱就行

2. 模糊匹配,拼音和缩写暂时不需要

3. 一次 20 条,要分页,要高亮

4. 现在 1000 个用户,预计一年内到 1 万

5. 对,有些内部用户不能被搜到

AI:明白了。我还发现一个问题:

你们现有的 User 模型里有 "isInternal" 字段吗?

如果没有,我们需要加一个。

你:哦对,没有,那加一个吧。

AI:好的,那我们还需要:

- 在用户注册时设置这个字段

- 在搜索时过滤掉 isInternal=true 的用户

- 在用户管理界面加个开关

这些都要做吗?

你:对,都要。

AI:好的,我整理一下需求...

第 3 步:AI 把这些决策记录到 CONTEXT.md

# CONTEXT.md

## 核心概念

### User(用户)

- 系统中的用户账号

- 包含:用户名、邮箱、手机号、是否内部用户

- 内部用户(isInternal=true)不会出现在搜索结果中

### 用户搜索

- 支持按用户名、邮箱模糊搜索

- 每页 20 条结果

- 自动过滤内部用户

- 匹配部分高亮显示

第 4 步:开始写代码,AI 会遵循这些定义

// AI 生成的代码会很清晰

asyncfunctionsearchUsers(query, page = 1) {

constPAGE_SIZE = 20// 遵循 CONTEXT.md 的定义

returnawait db.users.findMany({

where: {

isInternalfalse// 遵循 CONTEXT.md 的规则

OR: [

        { username: { contains: query } },

        { email: { contains: query } }

      ]

    },

skip: (page - 1) * PAGE_SIZE,

takePAGE_SIZE

  });

}

真实效果对比

不用 /grill-with-docs

  • 来回改了 5 次

  • 花了 3 小时

  • 代码改得乱七八糟

  • 最后还是有遗漏

用了 /grill-with-docs

  • 前 30 分钟在“拷问”

  • 写代码只花了 30 分钟

  • 一次写对,没有返工

  • 还留下了清晰的文档

结论:磨刀不误砍柴工。


技能 2:/tdd —— 让 AI 学会“先想后做”

为什么需要它?

场景:你让 AI 写个登录功能

// AI 写的代码

asyncfunctionlogin(email, password) {

const user = await db.users.findOne({ email });

if (user && user.password === password) {

return { tokengenerateToken(user) };

  }

thrownewError('登录失败');

}

看起来没问题?实际上有 5 个 Bug:

  1. 密码是明文比对(应该用哈希)

  2. 邮箱没有转小写(Test@example.com 和 test@example.com 会被当成两个用户)

  3. 错误信息太笼统(用户不知道是邮箱错了还是密码错了)

  4. 没有防暴力破解(可以无限次尝试)

  5. 没有记录登录日志

为什么会这样? 因为 AI 没有反馈,它不知道这些边界情况。

TDD 的核心思想:先写测试,让测试告诉你代码对不对

第 1 轮:最基本的功能

// 先写测试

test('用户可以用正确的邮箱和密码登录'async () => {

// 准备数据

awaitcreateUser({

email'test@example.com',

password'hashed_password_123'

  });

// 执行登录

const result = awaitlogin('test@example.com''password123');

// 验证结果

expect(result.token).toBeDefined();

});

// 运行:❌ 失败(因为还没写 login 函数)

// 写最少的代码让测试通过

asyncfunctionlogin(email, password) {

const user = await db.users.findOne({ email });

const isPasswordCorrect = await bcrypt.compare(password, user.passwordHash);

if (isPasswordCorrect) {

return { tokengenerateToken(user) };

  }

thrownewError('登录失败');

}

// 运行:✅ 通过

第 2 轮:处理邮箱大小写

// 写测试

test('邮箱不区分大小写'async () => {

awaitcreateUser({

email'test@example.com',

password'hashed_password_123'

  });

// 用大写邮箱登录

const result = awaitlogin('TEST@EXAMPLE.COM''password123');

expect(result.token).toBeDefined();

});

// 运行:❌ 失败

// 改代码

asyncfunctionlogin(email, password) {

const normalizedEmail = email.toLowerCase(); // 加这一行

const user = await db.users.findOne({ email: normalizedEmail });

// ... 其他代码不变

}

// 运行:✅ 通过

第 3 轮:更清晰的错误信息

// 写测试

test('邮箱不存在时应该明确提示'async () => {

awaitexpect(

login('notexist@example.com''password123')

  ).rejects.toThrow('该邮箱未注册');

});

test('密码错误时应该明确提示'async () => {

awaitcreateUser({

email'test@example.com',

password'hashed_password_123'

  });

awaitexpect(

login('test@example.com''wrongpassword')

  ).rejects.toThrow('密码错误');

});

// 运行:❌ 失败

// 改代码

asyncfunctionlogin(email, password) {

const normalizedEmail = email.toLowerCase();

const user = await db.users.findOne({ email: normalizedEmail });

if (!user) {

thrownewError('该邮箱未注册'); // 明确的错误

  }

const isPasswordCorrect = await bcrypt.compare(password, user.passwordHash);

if (!isPasswordCorrect) {

thrownewError('密码错误'); // 明确的错误

  }

return { tokengenerateToken(user) };

}

// 运行:✅ 通过

好测试 vs 坏测试

❌ 坏测试:测试实现细节

// 这个测试很脆弱

test('login 函数应该调用 bcrypt.compare'async () => {

const spy = jest.spyOn(bcrypt, 'compare');

awaitlogin('test@example.com''password123');

expect(spy).toHaveBeenCalled(); // 测试实现方式

});

// 问题:如果你把 bcrypt 换成 argon2,测试就挂了

// 但实际上功能没变

✅ 好测试:测试行为

// 这个测试很稳定

test('用户可以用正确的密码登录'async () => {

awaitcreateUser({

email'test@example.com',

password'password123'

  });

const result = awaitlogin('test@example.com''password123');

expect(result.token).toBeDefined(); // 测试最终结果

});

// 你可以随便换加密算法,只要登录功能正常,测试就过

比喻:

  • 坏测试像监工盯着你怎么干活:“你必须先拿锤子,再拿钉子,再敲三下”

  • 好测试像验收成果:“我不管你怎么做,钉子钉进去就行”

最常见的错误:水平切片

❌ 错误做法:

// 一口气写 10 个测试

test('测试 1', ...);

test('测试 2', ...);

test('测试 3', ...);

// ... 写了 10 个

// 然后开始写实现

functionlogin() {

// 写了 100 行代码

}

// 结果:发现前 5 个测试都写错了,要全部重写

✅ 正确做法:垂直切片

// 写 1 个测试

test('基本登录', ...);

// 写实现

functionlogin() {

// 10 行代码

}

// 测试通过,继续

// 写第 2 个测试

test('邮箱大小写', ...);

// 改实现

functionlogin() {

// 加 1 行代码

}

// 测试通过,继续...

比喻:

  • 水平切片:先画 10 层楼的图纸,再一口气盖楼 → 盖到一半发现图纸画错了

  • 垂直切片:盖一层,检查一层,再盖下一层 → 每一层都是对的


技能 3:/diagnose —— 系统化调试,不再“瞎试”

为什么需要它?

典型的调试过程:

你:这个功能报错了

AI:我看看... 可能是这里的问题,我改一下

你:还是报错

AI:那可能是那里的问题,我再改一下

你:还是报错

AI:要不试试这样?

你:(崩溃)

问题在哪? 没有方法,纯靠猜。就像看病不做检查,直接开药。

系统化调试的三大步骤

步骤 1:建立反馈循环(最重要!)

核心思想: 在修 Bug 之前,先建立一个能稳定重现 Bug 的方法。

场景:用户反馈“有时候登录会失败”

❌ 错误做法:

你:AI,用户说登录有时候会失败,你看看哪里有问题

AI:(看了代码)可能是密码验证的问题,我改一下

你:好像还是会失败...

AI:那可能是数据库连接的问题...

✅ 正确做法:先建立反馈循环

// 第 1 步:写一个能重现 Bug 的测试

test('重现登录失败的 Bug'async () => {

// 模拟用户的操作

awaitcreateUser({ email'test@example.com'password'pass123' });

// 尝试登录 100 次,看看会不会失败

for (let i = 0; i < 100; i++) {

const result = awaitlogin('test@example.com''pass123');

expect(result.token).toBeDefined();

  }

});

// 运行:❌ 失败了 3 次

// 现在你有了一个能稳定重现 Bug 的方法!

为什么这一步最重要?

  • 没有反馈循环:改代码 → 手动测试 → 不知道对不对 → 再改 → 再测……(可能花 2 小时)

  • 有反馈循环:改代码 → 运行测试 → 2 秒知道结果 → 再改 → 2 秒知道结果……(可能花 20 分钟)

比喻:

  • 没有反馈循环:蒙着眼睛扔飞镖,扔完了走过去看中没中

  • 有反馈循环:每扔一次飞镖,立刻知道中没中

建立反馈循环的 10 种方法:

  1. 写个失败的测试(最常用)

  2. 写个 curl 脚本(适合 API)

    # test-login.sh

    curl -X POST http://localhost:3000/login \

      -d '{"email":"test@example.com","password":"pass123"}'

  3. 写个 CLI 命令(适合命令行工具)

    # 运行 100 次,看看哪次会失败

    for i in {1..100}; do

      node cli.js login test@example.com pass123

    done

  4. 用 Playwright 写个浏览器脚本(适合前端)

    test('登录测试'async ({ page }) => {

    await page.goto('http://localhost:3000/login');

    await page.fill('[name=email]''test@example.com');

    await page.fill('[name=password]''pass123');

    await page.click('button[type=submit]');

    awaitexpect(page).toHaveURL('/dashboard');

    });

步骤 2:提出假设并验证

现在你有了反馈循环,可以快速验证假设了。

你:登录有时候会失败

AI:我生成 3 个假设:

假设 1:并发问题

- 预测:如果是并发问题,单线程测试应该不会失败

- 验证:写个单线程测试

- 结果:单线程也会失败 ❌ 假设错误

假设 2:数据库连接池耗尽

- 预测:如果是连接池问题,增加连接数应该能解决

- 验证:把连接池从 10 改成 100

- 结果:还是会失败 ❌ 假设错误

假设 3:bcrypt 在某些情况下比对失败

- 预测:如果是 bcrypt 的问题,加日志应该能看到

- 验证:在 bcrypt.compare 前后加日志

- 结果:发现有时候 user.passwordHash 是 undefined!✅ 找到了!

原因:创建用户时,如果密码为空,passwordHash 会是 undefined

修复:在创建用户时加上密码非空验证

关键点:一次只验证一个假设

❌ 错误做法:

同时改了 3 个地方,测试通过了,但不知道是哪个改对了

✅ 正确做法:

改一个地方 → 测试 → 没通过 → 撤销

改另一个地方 → 测试 → 通过了 → 找到原因了!

步骤 3:修复并防御

// 第 1 步:写个回归测试(防止以后再犯)

test('创建用户时密码不能为空'async () => {

awaitexpect(

createUser({ email'test@example.com'password'' })

  ).rejects.toThrow('密码不能为空');

});

// 第 2 步:修复代码

asyncfunctioncreateUser({ email, password }) {

if (!password) {

thrownewError('密码不能为空'); // 加上验证

  }

const passwordHash = await bcrypt.hash(password, 10);

returnawait db.users.create({ email, passwordHash });

}

// 第 3 步:验证原来的 Bug 是否修复

test('登录不再随机失败'async () => {

awaitcreateUser({ email'test@example.com'password'pass123' });

// 登录 100 次,应该全部成功

for (let i = 0; i < 100; i++) {

const result = awaitlogin('test@example.com''pass123');

expect(result.token).toBeDefined();

  }

});

// 运行:✅ 全部通过!

真实效果对比

不用 /diagnose

  • 花了 3 小时

  • 改了 10 个地方

  • 不确定是哪个改对的

  • 可能引入了新 Bug

用了 /diagnose

  • 花了 30 分钟

  • 改了 1 个地方

  • 确定是这个问题

  • 加了测试,以后不会再犯


技能 4:/improve-codebase-architecture —— 代码“体检”

为什么需要它?

项目的生命周期:

第 1 个月:代码很清晰,改起来很快

第 3 个月:开始有点乱,但还能改

第 6 个月:改一个地方要动 5 个文件

第 9 个月:不敢改了,怕改坏

第 12 个月:重构吧,这坨代码没救了

问题: AI 能让你快速写代码,但也能快速制造混乱。

解决方案: 定期“体检”,及早发现问题。

什么是“浅层模块”?用生活例子来理解

先说个生活中的例子:

你要做一顿饭,有两种方式:

方式 A:简单直接

1. 打开冰箱拿食材

2. 切菜

3. 炒菜

4. 装盘

方式 B:过度复杂

1. 找到"冰箱管理员",告诉他你要什么食材

2. "冰箱管理员"再去找"食材定位专家"

3. "食材定位专家"找到食材后,交给"食材搬运工"

4. "食材搬运工"把食材交给你

5. 你再开始切菜...

方式 B 就是“浅层模块”的典型表现——看起来很专业,实际上把简单的事情搞复杂了。


代码中的真实案例

场景:你让 AI 做了个订单结账功能

第 1 版:简单清晰(3 个月前)

classOrder {

constructor(items, user) {

this.items = items;      // 订单商品

this.user = user;        // 下单用户

// 直接计算总价

this.total = items.reduce((sum, item) => sum + item.price0);

  }

asynccheckout() {

awaitthis.processPayment();      // 处理支付

awaitthis.sendConfirmation();    // 发送确认邮件

returnthis;

  }

}

// 使用:3 行代码搞定

const order = newOrder(cartItems, currentUser);

await order.checkout();

console.log('订单完成!');

为什么这个版本好?

  • 一眼就能看懂在做什么

  • 要改逻辑,直接在 Order 类里改

  • 没有多余的东西


第 20 版:复杂混乱(AI “优化”了 20 次后)

classOrder {

constructor(items, user) {

this.items = items;

this.user = user;

// 注意:总价不在这里算了

  }

asynccheckout() {

// 步骤 1:验证订单

const validator = newOrderValidator(this);

const errors = validator.validate();

if (errors.length > 0) {

thrownewValidationError(errors);

    }

// 步骤 2:计算总价(为什么要单独一个类?)

const calculator = newPriceCalculator(this.items);

this.total = calculator.calculate();

// 步骤 3:处理支付(工厂模式,但只有一种支付方式)

const paymentProcessor = PaymentProcessorFactory.create(this.paymentMethod);

const result = await paymentProcessor.process(this);

// 步骤 4:发送通知(又一个工厂)

const notifier = NotifierFactory.create(this.user.preferredChannel);

await notifier.send(this);

returnthis;

  }

}

// 使用:看起来一样,但背后多了 5 个类

const order = newOrder(cartItems, currentUser);

await order.checkout();

console.log('订单完成!');

问题在哪?让我们逐个分析:


问题 1:OrderValidator —— 假装在验证

// OrderValidator 的实现

classOrderValidator {

constructor(order) {

this.order = order;

  }

validate() {

const errors = [];

// 就做了这么点事:

if (this.order.items.length === 0) {

      errors.push('订单不能为空');

    }

this.order.items.forEach(item => {

if (item.price <= 0) {

        errors.push('商品价格必须大于 0');

      }

    });

return errors;

  }

}

// 问题:这 10 行代码为什么要单独一个类?

// 直接写在 checkout() 里不是更清楚吗?

这就像: 你要检查冰箱里有没有鸡蛋,结果专门雇了个“鸡蛋检查员”,他的工作就是打开冰箱看一眼。


问题 2:PriceCalculator —— 一行代码的类

// PriceCalculator 的实现

classPriceCalculator {

constructor(items) {

this.items = items;

  }

calculate() {

// 就这一行!

returnthis.items.reduce((sum, item) => sum + item.price0);

  }

}

// 使用:

const calculator = newPriceCalculator(this.items);

this.total = calculator.calculate();

// 问题:为什么不直接写?

this.total = this.items.reduce((sum, item) => sum + item.price0);

这就像: 你要算 1+1,结果专门买了个计算器,还要先看说明书。


问题 3:PaymentProcessorFactory —— 只有一个选项的“工厂”

// PaymentProcessorFactory 的实现

classPaymentProcessorFactory {

staticcreate(method) {

// 现在只支持 Stripe

if (method === 'stripe') {

returnnewStripeProcessor();

    }

// 以后可能支持 PayPal、支付宝...

// 但现在还没有!

  }

}

// 使用:

const processor = PaymentProcessorFactory.create('stripe');

await processor.process(order);

// 问题:现在只有一种支付方式,为什么要工厂?

// 直接用不就行了?

const processor = newStripeProcessor();

await processor.process(order);

这就像: 你家只有一辆车,结果建了个“车辆调度中心”,每次开车都要先去调度中心申请。


总结:什么是“浅层模块”?

特征
浅层模块
深层模块
接口
复杂(要创建对象、调用多个方法)
简单(一个方法搞定)
功能
简单(就做一点点事)
强大(做很多事)
价值
没有(删掉反而更清晰)
有(封装了复杂逻辑)
比喻
万能遥控器(100 个按钮,每个都要记)
Siri(说句话就行)

判断方法:删除测试

  • 删掉后,代码变简单了 → 浅层模块,应该删

  • 删掉后,复杂度分散到 10 个地方 → 深层模块,应该留

“删除测试”:判断模块有没有价值

方法:想象删掉这个模块,会发生什么?

测试 OrderValidator:

// 删掉 OrderValidator

classOrder {

asynccheckout() {

// 把验证逻辑直接写在这里

if (this.items.length === 0) {

thrownewError('订单不能为空');

    }

for (const item ofthis.items) {

if (item.price <= 0) {

thrownewError('商品价格必须大于 0');

      }

    }

// ... 其他逻辑

  }

}

// 结果:代码更清晰了!

// 结论:OrderValidator 是个"浅层模块",应该删掉

测试 PriceCalculator:

// 删掉 PriceCalculator

classOrder {

asynccheckout() {

// 直接计算总价

this.total = this.items.reduce((sum, item) => sum + item.price0);

// ... 其他逻辑

  }

}

// 结果:从 3 行变成 1 行,更清晰了!

// 结论:PriceCalculator 也应该删掉

但是,有些模块不能删:

// 假设你有个 EmailService

classEmailService {

asyncsend(to, subject, body) {

// 连接 SMTP 服务器

// 处理认证

// 发送邮件

// 处理错误重试

// 记录日志

// ... 100 行代码

  }

}

// 如果删掉 EmailService,这 100 行代码会散落到 10 个地方

// 结论:EmailService 是个"深层模块",应该保留

判断标准:

  • 浅层模块:删掉后,复杂度消失了 → 应该删

  • 深层模块:删掉后,复杂度分散到 N 个地方 → 应该留

真实案例:重构一个“浅层模块”

Before:

// 有个 UserProfileFormatter 类

classUserProfileFormatter {

constructor(user) {

this.user = user;

  }

format() {

return {

namethis.user.name,

emailthis.user.email,

avatarthis.user.avatar || '/default-avatar.png'

    };

  }

}

// 用起来很麻烦

const formatter = newUserProfileFormatter(user);

const profile = formatter.format();

After:

// 直接在 User 类里加个方法

classUser {

toProfile() {

return {

namethis.name,

emailthis.email,

avatarthis.avatar || '/default-avatar.png'

    };

  }

}

// 用起来很简单

const profile = user.toProfile();

好处:

  • 少了一个类

  • 代码更清晰

  • 更符合直觉(用户的资料,当然应该是用户自己提供)

什么时候该抽象?

原则:有两个以上的使用场景,再抽象

❌ 过度抽象:

// 现在只有一种支付方式,就搞了个工厂

classPaymentProcessorFactory {

staticcreate(method) {

if (method === 'stripe') {

returnnewStripeProcessor();

    }

// 以后可能支持 PayPal、支付宝...

  }

}

// 问题:现在只有 Stripe,这个工厂没有意义

✅ 合理抽象:

// 现在只有一种支付方式,直接用

classStripeProcessor {

asyncprocess(order) {

// 处理支付

  }

}

// 等有第二种支付方式时,再抽象

// 到时候你会更清楚应该怎么抽象

比喻:

  • 过度抽象:你家只有一辆车,就建了个车库管理系统

  • 合理抽象:等你有了第二辆车,再考虑怎么管理


如何开始使用

安装

  1. 访问 github.com/mattpocock/skills

  2. 运行 skills.sh 安装程序

  3. 选择你需要的技能和目标 AI 工具

  4. 在项目中运行 /setup-matt-pocock-skills 初始化

实践路线图

新项目(推荐路线)

第 1 天:建立基础

1. 运行 /setup-matt-pocock-skills

2. 创建 CONTEXT.md,写下核心概念

3. 用 /grill-with-docs 理清第一个功能

第 1 周:养成习惯

1. 每次做新功能前,先 /grill-with-docs

2. 关键功能用 /tdd 写测试

3. 遇到 Bug 用 /diagnose

第 1 个月:定期体检

1. 每周五运行 /improve-codebase-architecture

2. 发现问题及时重构

3. 更新 CONTEXT.md

现有项目(救火路线)

第 1 步:止血(1 天)

1. 找出最痛的 3 个问题

2. 用 /diagnose 修复最严重的 Bug

3. 用 /improve-codebase-architecture 找出最乱的模块

第 2 步:建立文档(1 周)

1. 创建 CONTEXT.md

2. 写下核心概念和术语

3. 记录重要的架构决策(ADR)

第 3 步:逐步改善(持续)

1. 新功能用 /grill-with-docs + /tdd

2. 改老代码时顺手重构

3. 每周运行 /improve-codebase-architecture

团队推广建议

第 1 阶段:个人试用(1 周)

  • 你自己先用起来

  • 记录效果(节省了多少时间、避免了多少 Bug)

  • 准备案例

第 2 阶段:小范围推广(1 个月)

  • 找 2-3 个愿意尝试的同事

  • 一起用,互相反馈

  • 完善团队的 CONTEXT.md

第 3 阶段:全团队推广(持续)

  • 在团队会议上分享效果

  • 建立团队规范(什么时候用哪个技能)

  • 定期回顾和改进


常见问题

Q1:这些技能会不会让开发变慢?

短期: 是的,前期会慢一点(多花 20-30% 时间)

长期: 不会,反而会更快

真实数据(来自使用者反馈):

  • 第 1 周:慢 30%(在学习和适应)

  • 第 2 周:慢 10%(开始习惯)

  • 第 1 个月:持平(速度恢复)

  • 第 3 个月:快 50%(因为代码质量好,改起来快)

  • 第 6 个月:快 2 倍(因为没有技术债务)

比喻:

  • 不用这些技能:像开快车不系安全带,一开始很快,但迟早出事

  • 用这些技能:像开车系安全带,一开始慢 2 秒,但能安全到达

Q2:我的项目很小,需要用这些吗?

需要! 尤其是小项目。

原因:

  1. 小项目更容易变成大项目

  2. 小项目的坏习惯会带到大项目

  3. 在小项目上练习,成本更低

建议:

  • 小项目:至少用 /grill-with-docs 和 CONTEXT.md

  • 中项目:加上 /tdd

  • 大项目:全套都用

Q3:AI 会自动遵循这些技能吗?

不会。 你需要主动调用。

但是:

  • 用几次后,AI 会学习你的模式

  • CONTEXT.md 会让 AI 自动遵循你的术语

  • 测试会自动约束 AI 的行为

建议:

  • 把常用技能做成快捷键或命令

  • 在项目 README 里写清楚什么时候用哪个技能

  • 团队成员互相提醒

Q4:这些技能适合所有编程语言吗?

是的。 这些是方法论,不是具体的工具。

已验证的语言:

  • JavaScript / TypeScript

  • Python

  • Go

  • Rust

  • Java

  • C#

核心思想是通用的:

  • 需求对齐(任何语言都需要)

  • 共享语言(任何项目都需要)

  • 测试驱动(任何语言都能做)

  • 代码设计(任何语言都重要)


核心价值观

这套技能体系的设计哲学:

1. 小而美,不是大而全

不是: 一个包罗万象的框架而是: 一组可以灵活组合的小工具

比喻:

  • 不是瑞士军刀(100 个功能,每个都不好用)

  • 而是专业工具箱(每个工具专注做好一件事)

2. 授人以渔,不是授人以鱼

不是: 让 AI 替你做决策而是: 教你如何与 AI 协作

比喻:

  • 不是自动驾驶(你坐着就行)

  • 而是驾驶辅助(你还是司机,但有了助手)

3. 基于经验,不是赶时髦

不是: 最新最炫的技术而是: 经过几十年验证的原则

来源:

  • 《程序员修炼之道》(1999 年)

  • 《领域驱动设计》(2003 年)

  • 《软件设计哲学》(2018 年)

  • 《极限编程解析》(1999 年)

这些书都是经典,原则不会过时。

4. 适配 AI,不是对抗 AI

不是: “AI 不可靠,少用为妙”而是: “AI 很强大,但需要正确使用”

比喻:

  • 不是拒绝用电动工具(“还是手工锯靠谱”)

  • 而是学会安全使用电动工具(“戴好护目镜,抓稳了”)

5. 持续改进,不是一劳永逸

不是: “设置一次,永久有效”而是: “定期体检,及时调整”

建议:

  • 每周回顾:哪些技能用得好?哪些还需要改进?

  • 每月更新:CONTEXT.md 是否需要补充?ADR 是否需要添加?

  • 每季度优化:运行 /improve-codebase-architecture,大扫除


结语:AI 是工具,你才是工程师

三年前,我们担心 AI 会取代程序员。

现在我们知道了:AI 不会取代程序员,但会用 AI 的程序员会取代不会用的。

但这里有个更深的问题:会用 AI ≠ 让 AI 替你写代码

真正会用 AI 的程序员,是那些:

  • 知道什么时候该让 AI 写代码

  • 知道什么时候该让 AI 闭嘴

  • 知道如何引导 AI 写出好代码

  • 知道如何验证 AI 写的代码

这套技能体系,就是教你这些的。


最后,送你一句话:

“工具会变,原则不变。AI 会进化,工程思维永恒。

不要做 AI 的打字员,要做 AI 的架构师。“


相关资源:

  • GitHub 仓库:github.com/mattpocock/skills

  • Matt Pocock 的 Newsletter:aihero.dev(6 万+ 开发者订阅)

开始行动:

  1. Star 这个仓库

  2. 选一个小项目试试

  3. 一周后,回来告诉我效果如何

你的经验也很重要:

  • 你在用 AI 编程时遇到过哪些坑?

  • 你有什么实践心得?

  • 欢迎在评论区分享!


本文基于 Matt Pocock 的开源项目深度解读,致敬所有为工程实践贡献智慧的开发者。