面对 AI 编程的 5 个关键问题:Matt Pocock 是怎么解的?

在日常使用 AI 写代码时,有几个问题几乎避无可避:

  1. AI 写着写着跑偏了,怎么办?
  2. 遇到自己不懂的生疏领域,AI 给出方案,是先花时间恶补知识还是闭眼听它的?
  3. 调 Bug 时 AI 在同一个报错上反复碰壁"鬼打墙",怎么解决?
  4. 几个小时的长任务,怎么保证 AI 不把最初的目标忘得一干二净?
  5. AI 深度参与写出来的项目,其他人怎么才能快速看懂?

近段时间,我高强度地在使用 TypeScript 社区知名开发者 Matt Pocock 开源的 mattpocock/skills。这个工具库的核心理念,就是把随性、碰运气的"氛围编程(Vibe Coding)",改造成有严格工程门禁的确定性工作流。

结合这段时间的使用体验,我们来看看 Matt 如何用这些skills 来回答这 5 个问题的。

1. 如何保证 AI 没有跑偏?

把一个稍微复杂的任务交给 AI 时,几轮对话下来,代码常常会脱离原有的架构规划,甚至把原本写好的逻辑改乱。

AI 跑偏的主要原因有三个:上下文太长导致注意力被稀释、一开始没有把设计收敛清楚,以及缺乏客观的机器反馈。

Matt 给出的解法不是寄希望于模型"自觉清醒",而是在流程上立下几道硬性关卡:

动手前先做设计质询

在让 AI 动手写代码之前,强制启动一道质询流程(见 skills/grilling)。

让 AI 先把需求整理成一棵决策树,按层级逐一盘问边界条件、异常分支和前置假设。在所有关键分支确认之前,严禁直接跳到写代码阶段。先把需求在纸面上推演清楚,往往就能过滤掉大部分偏航的风险。

用 TDD 提供客观的机器反馈

进入编码阶段后,强制走测试驱动开发流程(见 skills/tdd)。

AI 必须先写出一个能稳定复现预期行为的失败测试(Red),再去编写最简实现使其通过(Green)。测试能不能跑通,由机器直接判决,这是最客观的红线,能有效防止 AI 靠猜想去凑代码。

垂直切片与单任务清空上下文

很多人习惯按架构横向拆分任务(比如"先写完所有的数据库表,再写完所有的接口"),这种方式在长会话里极易漂移。

更好的做法是使用垂直切片(Tracer Bullets)机制(见 skills/to-tickets):把任务拆成穿透数据层、接口层、前端与测试的单条细粒度 Ticket。每个 Ticket 严格在一个全新的会话窗口内完成,一旦测试通过并提交 Git Commit,立即清空会话,切断上下文膨胀带来的干扰。

拦截不可逆的 Git 破坏性指令

在终端环境层面配置硬规则(见 skills/git-guardrails-claude-code),直接拦截 git push --force、git reset --hard、git clean -f 等危险命令,守住底层的物理安全。


2. 面对不熟悉的领域,AI 提出的问题自己也不懂,是先补全知识还是按 AI 的推荐走?

在做技术选型或方案推导时,常常会遇到这种情况:

业务需要接入一个自己从没碰过的协议或技术栈,AI 给出了几个复杂的方案选项,里面的专有名词和原理自己根本看不懂。停下来从头学可能要花好几天,但直接照搬推荐又怕踩坑。

Matt Pocock 提出的原则很清楚:以 AI 的推荐作为假设基准,但必须通过客观约束检验其权衡,不可盲从;事实归工具,决策归人类。

在具体操作上,可以把握这几点:

区分事实与决策

客观事实(比如某个 Node.js 版本是否支持特定 API、某个第三方库的打包体积与运行表现)是 AI 和自动化工具的强项,完全可以放手让它们写测试脚本、查阅官方文档去调研,不需要人类花大量时间死记硬背。

但涉及方案的取舍与代价,决策权必须留在人类手里,不能让 AI 代替自己做主。

检验推荐的三个具体问题

当 AI 给出明确推荐时,即便自己对细节不熟,也可以追问它三个问题:

  1. 采纳这个方案牺牲了什么?前置假设是什么?
  2. 如果半年后发现不合适需要替换,重构的代价有多大?它的对外接口是否足够独立?
  3. 为什么不采用社区里更普遍的传统做法?

借助极简原型(Prototype)建立直观认知

如果在口头上讨论不清,可以让 AI 在沙盒里写一个几十行代码的极简原型(见 skills/prototype)。跑一跑真实代码,看看实际输入输出,比在脑子里凭空猜测要实在得多。

记录决策理由(ADR)

即便决定采纳 AI 的建议,也要通过架构决策记录(见 skills/grill-with-docs)在 docs/adr/ 里记下"为什么选 A、放弃 B 的原因"。日后对这个领域的理解加深了,也能随时根据记录推翻旧方案。


3. AI 如果在一个问题上"鬼打墙"应该怎么解决?

很多人用 AI 调 Bug 时都有过这种痛苦经历:报错出现后,AI 给出修改建议;改完之后报了另一个错,AI 又把代码改回去;来回折腾十几轮,代码改得乱七八糟,Bug 依然在那里。

AI 在原地打转,根本原因在于缺少精准的负反馈,导致它退化成了"看着代码瞎猜"。而随着报错日志越积越多,上下文被严重污染,模型就更容易被带偏。

我们来看一个 API 客户端里的常见例子,直观对比一下两种排查方式。

实战对比:API 客户端偶发解析异常

假设我们写了一个数据请求模块,偶尔会报 SyntaxError: Unexpected end of JSON input。

❌ 容易陷入死循环的闲聊式调试

普通会话里经常出现这样的拉扯:

开发者 :"调用 fetchData() 时偶尔报 JSON 解析错误,帮我修一下。"

AI(瞎猜) :"可能是接口返回的内容不完整,我给解析部分加了 try...catch,并在出错时默认返回 {}。"

开发者 :"改完之后下游拿不到字段,全报 TypeError: Cannot read properties of undefined 了!"

AI(继续瞎猜) :"不好意思,那我改回抛出异常,在外面加个重试逻辑,如果失败就 setTimeout 重试 3 次......"

开发者:"现在的报错变成了超时,请求直接卡死了!"

AI 从头到尾没有确认过真正的原因,只是在生产代码上反复试错,对话窗口堆积了大量无效信息,最后整个会话只能作废。

✅ diagnosing-bugs 的排查规则

如果按照 skills/diagnosing-bugs 的规则,流程完全不同:

第一步:无秒级复现,严禁动业务代码。

AI 必须先写出一个能在 1 秒内稳定触发、精准断言该报错的最小测试用例:

javascript 复制代码
// tests/repro-fetch-stream.test.js
import { test } from 'node:test';
import assert from 'node:assert';
import { createMockServer, fetchData } from '../src/client.js';

test('reproduce truncated stream response', async () => {
  const server = await createMockServer({ chunkDelay: 50, truncate: true });
  // 必须断言预期的业务表现,若出现断流应精准抛出 HttpTruncatedError 而非吞掉异常
  await assert.rejects(
    () => fetchData(server.url),
    { name: 'HttpTruncatedError' }
  );
  server.close();
});

运行该命令时,必须看到红色的失败结果:

bash 复制代码
$ node --test tests/repro-fetch-stream.test.js
✖ reproduce truncated stream response (12ms)
  AssertionError [ERR_ASSERTION]: Expected error to be thrown, but got SyntaxError: Unexpected end of JSON input

只要没写出能稳定亮红的测试,AI 就必须停手,禁止修改任何一行生产代码。

第二步:提出可证伪的假设。

AI 提出的每个猜测,都必须符合固定格式:

"假设:底层 Stream 在传输未完成时提前触发了关闭事件。

预测 :如果该假设成立,在数据流完全接收前监听 on('end') 并校验 Content-Length,上述测试应该稳定转绿。"

第三步:反思架构接缝。

如果发现代码紧密耦合了原生的 fetch,导致根本写不出上面那个只耗时 12ms 的 Mock 测试,这本身就说明当前模块缺乏接缝(Seam)。

此时应参考 skills/improve-codebase-architecture,先把网络层与解析层解耦,留出可测试的接缝,再去验证修复。

一旦会话已经充斥了无效尝试,不要继续在该会话里挣扎。把写好的 repro-fetch-stream.test.js 单独提出来,扔进一个干净的新会话,AI 往往能一次改对。


4. 长任务怎么保证 AI 不忘记原本的目标?

在长任务中,如果只是在同一个窗口里不断聊天,AI 几乎一定会遗忘最初的顶层约束。

依靠大模型的上下文记忆来维持长任务是靠不住的。正确的做法是把状态放在外面,把会话本身当作可抛弃的耗材。

以写一个"CSV 转嵌套 JSON 命令行工具"为例:

实战对比:CSV 转换工具开发

❌ 依赖记忆的长对话

开发者 :"帮我写一个 CLI 工具,把 CSV 转成嵌套 JSON,必须支持 Node.js 流式读取、类型推断、点分隔路径(如 user.address.city),以及 gzip 输出。"

(讨论 15 轮参数解析和类型推断后......)

开发者:"点分隔的深层嵌套对象怎么处理?"

AI :"我改写了解析逻辑,先把整个 CSV 文件通过 fs.readFileSync 读进内存数组,然后循环组装嵌套对象......"

此时 AI 早已把第一轮强调的"流式读取"和内存限制忘得一干二净,为了做新特性,直接破坏了前面的设计。

✅ wayfinder + to-tickets 的外置状态流

在规范工程里,长任务的状态记录在文件和 Issue 里:

1. 全局决策地图(见 skills/wayfinder)

在 Issue Tracker 或项目根目录维护一份固定格式的地图:

markdown 复制代码
# wayfinder:map

## Destination(终点)
纯流式驱动(Stream-first)的 CSV 转嵌套 JSON CLI 工具,内存占用峰值严格低于 50MB。

## Decisions so far(已定决策)
- #1 使用 Node 原生 stream/transform,不引入高开销外部解析库
- #2 类型推断采用严格模式(整型、浮点型、布尔型)

## Not yet specified(战争迷雾)
- 遇到非法 CSV 格式行时的容错策略(跳过并报警 vs 立即中断)

## Out of scope(范围外)
- 暂不支持 XML 或 YAML 格式输出
- 暂不接入外部网络数据源

任何代码改动如果超出了 Destination 或踏入了 Out of scope,都会被直接否决。

2. 垂直切片与会话销毁(见 skills/to-tickets)

把长任务拆成独立切片:

  • Ticket-1:点分隔键名解析核心函数与 TDD 单测;
  • Ticket-2:流式 Transform 转换流接入;
  • Ticket-3:CLI 命令入口参数绑定。

此时,会话变成了一次性工具:

text 复制代码
开启全新 Agent 会话 
   └─ 读取 Ticket-1
   └─ 编写失败单测(Red)
   └─ 编写最简实现通过测试(Green)
   └─ 提交 Git Commit
   └─ 彻底关闭会话,清空上下文!

下一个 Ticket 由新会话启动,读取 Git 提交与 Issue 继续推进。状态保存在 Git 和地图里,AI 每次都在最干净的上下文中工作。


5. 如何让其他人也能很快地理解项目?

很多人会有顾虑:通过大量 Ticket 切片、由 AI 深度参与写出来的项目,其他人甚至以后的自己接手时,会不会变成一堆拼凑出来、无法理解的碎片代码?

Matt Pocock 的实践揭示了一个很有意思的事实:真正适合 AI 高效协作的工程约束,恰恰也是人类团队顺畅协作的最佳实践。

项目难懂,原因无非是概念混乱、模块过度细碎,或者设计意图丢失。要让新成员快速上手,有几个基础建设值得做:

维护统一的术语表(GLOSSARY.md)

在根目录下维护词汇表(见 skills/domain-modeling),把业务概念与代码中的类名、函数名严格对应起来,避免同义词混用。新成员看一眼词汇表,就能建立一致的心智模型。

记录架构决策(docs/adr/)

关键的技术选型和权衡取舍,都通过轻量的 ADR 文档留存(见 skills/grill-with-docs)。新人接手时,不用到处打听当初为什么这么写,翻翻 ADR 就一清二楚。

尽量写深模块(Deep Modules)

参考 John Ousterhout 在《软件设计哲学》中的原则(见 skills/improve-codebase-architecture):对外提供简单、稳定的接口,对内隐藏复杂的实现细节。

不要为了迎合测试把代码拆成几十个互相调用的浅函数,导致看一个流程得在 5 个文件之间来回跳。让模块变深,代码的自解释性自然就上去了。

渐进式披露(Progressive Disclosure)

在项目的指引文件(如 AGENTS.md)里,顶层只保留最重要的目录索引与核心约定,具体细节按需查阅。无论是人还是工具,都不用一上来就背诵几千行规则。

相关推荐
一缕82年的清风12 小时前
当 AI 承包了 90% 的代码,架构师那致命的 10% 到底在控什么?
人工智能·vibecoding
编码生活禅意录2 天前
一部手机开发安卓 App:让写代码和看效果都舒服起来
vibecoding
Hashan7 天前
Vibe Coding 下前后端怎么对接接口?后端不给力的兜底方案
前端·后端·vibecoding
哈基咪怎么可能是AI9 天前
dsh插件开发踩坑实录(写给正在build dsh插件的ai agent)
deepseek·vibecoding
vibecoding日记9 天前
dsh插件开发踩坑实录(写给正在build dsh插件的ai agent)
编程工具·ai助手·vibecoding
勤劳打代码9 天前
从 Demo 搭建的Flutter 演示项目 —— Forge
flutter·客户端·vibecoding
潘锦11 天前
使用 Vibe Coding 的这6 种后遗症,你有吗?
cto·vibecoding
kisshyshy11 天前
《从屎山到秩序:Vibe Coding 95 驾驭术全公开》
人工智能·代码规范·vibecoding
无责任此方_修行中11 天前
插件+1:MiaoMint —— 类 RayCast 的标签管理工具
前端·javascript·vibecoding