目录

Vibe Coding 工作流

如何利用cursor快速理解复杂代码工程

本文转载自知乎Up主笙囧同学的如何利用cursor快速理解复杂代码工程? - 知乎

去年我试着读 vLLM 的源码。

起因是我在用 vLLM 做推理服务的时候遇到了一个性能问题,想看看它的调度器到底是怎么工作的。我clone了仓库,打开文件树,看到了大概几百个Python 文件、一堆 C++ 扩展、还有 CUDA kernel。

我盯着屏幕看了十分钟,关掉了编辑器,去泡了杯咖啡。

那种感觉你一定体会过——就像你站在一座陌生城市的市中心,四周全是高楼,每栋楼里都有几十层,你知道你要找的东西就在某一栋楼的某一层的某个房间里,但你连东南西北都分不清。

后来我开始用 Cursor 来辅助读源码。摸索了一段时间之后,确实找到了一些有效的方法。

不是那种”让AI帮你读完所有代码然后给你一份总结”的方法——那种方法我试过,没用,AI给你的总结跟你自己看README得到的信息差不多。

我要说的方法更像是把Cursor当成一个对这个代码库了如指掌的向导,你问它路,它带你走,但路是你自己走的,风景是你自己看的。

为什么直接让AI”总结整个项目”没用

这是很多人的第一反应。打开Cursor,把整个项目索引了,然后问:”帮我介绍一下这个项目的整体架构。”

AI会给你一段话。听起来头头是道。什么”这个项目采用了分层架构,核心模块包括 XXX、YYY、ZZZ,它们之间通过 XXX 方式通信”。

你读完觉得”嗯嗯好有道理”。

然后呢?

然后你发现自己并没有真正理解任何东西。你知道了几个模块的名字,但你不知道数据是怎么从入口流到出口的。你不知道某个关键的设计决策背后的原 因。你不知道当一个请求进来的时候,代码到底在做什么。

因为理解不是信息的传递,是认知结构的构建。 AI把架构信息告诉你,相当于给了你一张地图。但地图不是领土。你没有亲自走过那些路,地图上的每一个 标记对你来说都只是一个符号,而不是一段体验。

这就是为什么”让AI帮你读代码”作为一个整体策略是行不通的。

但”在你自己读代码的过程中,让AI帮你解决具体的障碍”——这个策略是非常有效的。

两者的区别是什么?是主动权在谁手里。前者你是被动的信息接收者,后者你是主动的探索者。AI的角色从”替你读”变成了”帮你读”。

我的方法:从一个问题开始

读任何一个复杂项目,我现在都不会从”了解整体架构”开始。

我会从一个具体的问题开始。

读 vLLM 的时候,我的问题是:”一个推理请求从进入系统到返回结果,经历了哪些步骤?”

读 FastAPI 的时候,我的问题是:”当我写了一个 @app.get('/users') ,框架是怎么把这个装饰器变成一个能处理HTTP请求的东西的?”

读 Redis 的时候,我的问题是:”当我执行 SET key value 的时候,这个命令从被接收到数据被写入内存,中间经过了什么?”

一个好的问题就是你的故事线。 你不需要理解整个项目——你需要沿着一个问题,把它涉及到的代码路径从头到尾走一遍。

走完一条路径之后,你对项目的理解就不再是零了。你有了一个”锚点”。然后你可以从这个锚点出发,问第二个问题,走第二条路径。两条路径之间一定 会有交叉点——那些交叉点就是项目的核心模块。

几条路径走完,项目的骨架自然就在你脑子里了

这个方法不需要AI也能做。但有Cursor的帮助,效率会高非常多。

第一步:找到入口

每一条故事线都需要一个入口。对于大部分项目来说,入口就是用户最先接触到的那个界面

如果是一个 Web 框架,入口就是你创建 app、注册路由的地方。

如果是一个推理引擎,入口就是你调用 model.generate() 的地方。

如果是一个数据库,入口就是它接收客户端连接和命令的地方。

怎么用 Cursor 找入口? 直接问它:

1
2
3
这个项目的用户入口在哪?当一个用户发起一个推理请求时,
代码的执行从哪个文件的哪个函数开始?请给我具体的文件路径
和函数名,不需要解释细节。

注意最后那句”不需要解释细节”。这很重要。

你在这个阶段只需要一个起点,不需要AI给你长篇大论。如果你让AI一次性解释太多,你会淹没在信息里,反而找不到方向

Cursor 会告诉你类似这样的信息:”入口在 vllm/entrypoints/llm.pyLLM.generate() 方法”。

好。打开那个文件。开始读。

第二步:沿着调用链往下走

打开入口文件之后,你开始读代码。很快你会遇到第一个障碍——你看到了一个函数调用,但你不知道那个函数做了什么、在哪个文件里。

这是Cursor最有价值的使用场景之一

选中那个函数调用,问Cursor:

1
2
这个函数做了什么?用一两句话概括它的核心职责,
然后告诉我它在哪个文件里定义的。

注意:一两句话。 你不需要AI给你逐行解释那个函数的实现。你现在只需要知道”它大概做了什么”以及”它在哪”。

这就像你在陌生城市里走路,遇到一个路口,你不需要知道每条岔路通向哪里的完整地图。你只需要知道”这条路大概通往火车站”和”那条路大概通往商 业区”,然后选择跟你目标相关的那条走下去。

你会沿着调用链一路走下去。入口函数调用了 A,A 调用了 B,B 调用了 C……

你不需要走完所有的分支。你只需要走跟你的问题相关的那条主线

中间遇到不影响主线理解的分支——比如日志记录、参数校验、缓存检查——直接跳过。怎么判断一个分支是不是主线?问 Cursor:

1
2
在这个函数里,哪一行是实际执行核心逻辑的?
其他部分可以先忽略吗?

Cursor会告诉你:”第47行的 self._run_engine() 是核心调用,上面的都是参数处理和校验,可以先跳过。”

然后你跳到第47行,继续往下走。

第三步:遇到不懂的技术栈时

这是你提到的第二个痛点——项目里用了你不熟悉的语言或技术。

比如你在读一个 Python 项目,突然发现核心的计算部分是用 C++ 写的 CUDA 扩展。你不懂 CUDA。

传统的做法是:先去学 CUDA,学个几天几周,然后回来接着读。

有了 Cursor 你可以换一种做法:不学 CUDA 本身,只理解那段 CUDA 代码在做什么

选中那段 C++/CUDA 代码,问 Cursor:

1
2
3
4
我不熟悉CUDA编程。请用我能理解的方式解释这段代码
在做什么——不需要解释CUDA的语法细节,只告诉我
它的输入是什么、输出是什么、中间做了什么计算。
用Python的思维方式类比。

最后那句”用 Python 的思维方式类比”很关键。它告诉 Cursor 用你已有的知识框架来解释新东西。

Cursor 可能会说:”这段 CUDA kernel 本质上在做的事情相当于 Python 里的:对一个大矩阵的每一行做 softmax,然后跟另一个矩阵做矩阵乘法。只不过它把这个计算拆分到了GPU的几千个线程上并行执行。”

你不需要理解它是怎么拆分的、线程是怎么同步的——那些是CUDA的实现细节。你需要理解的是它在计算层面做了什么。 有了这个理解,你就能把这个 CUDA函数当成一个”黑盒”,知道它的输入输出,然后继续沿着主线往下走。

同样的方法适用于你遇到的任何不熟悉的技术栈:

  • 不懂 Rust?让 Cursor 用你懂的语言类比解释

  • 不懂某个框架的特定 API?让 Cursor 解释它的作用而不是用法

  • 不懂某个设计模式?让 Cursor 解释它在这个项目里解决了什么问题

关键原则是:你的目标不是学会那个技术栈,而是理解代码的逻辑流。两者需要的知识量差了一个数量级

第四步:画地图

走完一条主线之后,你需要把你走过的路记下来。

我的做法是在一个单独的markdown文件里,边读边记。格式很简单:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
## 一个推理请求的完整路径

1. 入口:LLM.generate()(vllm/entrypoints/llm.py)

 - 接收用户的prompt,封装成SequenceGroup

2. 调度:Scheduler.schedule()(vllm/core/scheduler.py)

 - 决定哪些请求可以在这一步被处理
 - 核心逻辑:根据当前GPU显存和KV Cache的空间来排队

3. 执行:ModelRunner.execute_model()(vllm/worker/model_runner.py)

 - 把一批请求打包,送进模型做一次forward
 - 这里会调用CUDA kernel做实际计算

4. 采样:Sampler.forward()(vllm/model_executor/layers/sampler.py)

 - 根据模型输出的logits,采样出下一个token

5. 结果返回:把生成的token拼回去,返回给用户

## 我的理解

vLLM的核心创新在第2步——调度器的PagedAttention机制

让KV Cache可以像操作系统的虚拟内存一样按需分配,

不需要预留连续的大块显存……

这份笔记不是给别人看的,是给你自己看的。 所以不需要写得很正式,用你自己能理解的语言就好。

你可能觉得”我直接记在脑子里不就行了”。不行。复杂项目的调用链太长了,你走到第五层的时候大概率已经忘了第二层的细节。写下来才能在脑子里维 持一张完整的地图。

而且这份笔记还有一个用处——当你后续探索第二条故事线的时候,你可以把新的路径叠加到同一份地图上,看到不同路径之间的交叉点。那些交叉点往往 就是项目最核心的模块。

第五步:在关键节点上深入

走完主线之后,你已经有了一个粗粒度的理解。接下来你可以选择在某些你特别感兴趣的节点上深入。

比如我走完vLLM的主线之后,对调度器特别感兴趣——因为它是vLLM性能优势的核心。

这时候我会打开调度器的代码,让Cursor帮我做更细粒度的解读:

1
2
3
请帮我梳理Scheduler.schedule()这个方法的内部逻辑。
按执行顺序列出它做了哪几件事,每件事用一句话概括。
对于涉及PagedAttention的部分请详细解释。

注意这次我要求”详细解释”了——因为这是我选择深入的节点,值得花时间理解细节。

在主线探索阶段,你的提问策略应该是”概括+定位”——告诉我大概做了什么、在哪里。

在深入阶段,你的提问策略变成”细节+原因”——具体是怎么实现的、为什么这么设计

这个节奏很重要。如果你从一开始就事事追问细节,你会迷失在细节里走不动。如果你全程只停留在概括层面,你最终得到的理解是浅薄的。

先粗后细,先主线后分支。 像画画一样——先画骨架,再填细节。

一些容易踩的坑

坑一:一次问太多。

不要发这种消息:

1
2
3
帮我解释一下这个项目的整体架构、核心模块的职责、

主要的设计模式、数据流向、以及关键算法的实现原理。

这种问法得到的回答一定是又长又泛的废话。不是 Cursor 不行,是你的问题太大了,任何人面对这种问题都只能给你一个泛泛的回答。

好的提问是窄的、具体的、有明确边界的

  • “这个函数的第三个参数是干什么用的?”

  • “这个类为什么要继承那个基类而不是直接实现?”

  • “数据从这个函数出来之后,下一步去了哪里?”

每个问题只解决一个困惑。解决完了再问下一个。你的理解就是这样一个困惑一个困惑地拼起来的

坑二:完全信任Cursor的回答。

Cursor 有时候会瞎说。尤其是当你问的问题涉及到跨文件的复杂逻辑时,它可能会给你一个听起来很合理但实际上是错的解释。

怎么办?交叉验证

Cursor 告诉你”这个函数会调用XXX”——你自己跳过去看一眼,确认它确实调用了。

Cursor 告诉你”这个变量在YYY文件里被初始化”——你自己搜一下那个变量名,确认它说的位置是对的。

不需要每一句话都验证,但关键节点一定要自己确认。 尤其是那些会影响你对整体架构理解的判断——比如”模块A和模块B是通过消息队列通信的”—— 这种结论你最好自己看到代码里的证据。

养成这个习惯之后,你会发现大部分时候 Cursor 是对的,但偶尔它确实会犯错。那些”偶尔”的错误如果你没有验证就接受了,可能会导致你对整个项目的理解建立在一个错误的基础上。

坑三:试图一次读完。

复杂的开源项目不是一个下午能读完的。vLLM 的代码我前前后后读了大概两周,中间穿插着其他工作。

不要给自己压力说”今天一定要把这个项目读完”。你今天读明白了一条主线,就是实实在在的进展。明天再读一条。

每次读完记得写笔记。 你下次再回来的时候,看一眼笔记就能快速恢复上下文,不用从头开始。

坑四:只读不跑。

这可能是最大的坑。

光看代码你能理解的东西是有限的。把项目跑起来,加几个断点或者打几行日志,亲眼看到数据怎么流动的——这个过程给你的理解深度是光看代码的好几 倍。

你可以让 Cursor 帮你做这件事:

1
2
我想在本地把这个项目跑起来,走通一个最简单的例子。
请告诉我最少需要哪些步骤。如果有复杂的外部依赖可以mock掉的,告诉我怎么mock。

不需要搭建完整的开发环境。只要能跑通一个最小的例子,能让你在关键位置加断点看到数据,就够了。

跑起来之后,在你走过的主线上的关键函数入口加个断点或者日志。然后触发一个请求,看实际的执行路径是不是跟你读代码时理解的一致。

经常会有惊喜。 你以为数据走的是路径A,实际上走的是路径B——因为有一个条件分支你读代码时忽略了。这种”预期和现实的偏差”本身就是最好的学习 机会。

针对不同类型项目的策略差异

不同类型的项目,”故事线”的选择方式不一样。简单说几个常见的:

Web框架类(FastAPI、Express、Gin等):

最好的故事线是”一个HTTP请求从进入到返回的完整生命周期”。从监听端口→接收请求→路由匹配→中间件执行→handler调用→响应返回。这条线走 完,你就理解了框架的骨架。

数据库/存储引擎类(Redis、LevelDB、SQLite等):

最好的故事线是”一个写操作从接收到持久化的完整路径”。从命令解析→数据结构操作→内存写入→持久化到磁盘。然后再走一条”读操作”的路径。两 条路径交叉的地方就是核心的数据结构和存储引擎。

AI推理/训练框架类(vLLM、DeepSpeed、PyTorch等):

最好的故事线是”一个前向传播从输入到输出的完整路径”。从数据预处理→模型加载→计算执行→结果后处理。特别注意计算是在哪里从Python进入 C++/CUDA的——那个边界通常是理解性能优化的关键。

编译器/解释器类(CPython、V8、GCC等):

最好的故事线是”一段源代码从文本到被执行的完整路径”。从词法分析→语法分析→AST→中间表示→优化→代码生成/执行。每一步的输入和输出是什 么,格式是什么。

不管哪种类型的项目,核心思路都是一样的:找到一条从入口到出口的主线,沿着它走,遇到障碍时让 Cursor 帮你清除障碍。

一个完整的实战流程示例

最后把整个流程用一个具体的例子串一遍。假设你想理解 FastAPI 是怎么工作的。

起手。 把 FastAPI 的仓库 clone 下来,用 Cursor 打开。

1
2
3
4
5
6
7
8
找入口。 问 Cursor:
当用户写了这样的代码:
app = FastAPI()
@app.get("/hello")
def hello():
 return {"msg": "hello"}
请告诉我 @app.get 这个装饰器的代码在哪个文件的
哪个函数里定义的。只给我位置,不用解释。

Cursor 告诉你在 fastapi/applications.py 里。打开它。

沿主线走。 你看到 app.get 实际上调用了 self.router.add_api_route() 。你不知道这个函数做了什么。选中它,问 Cursor:

1
这个函数做了什么?一句话概括。它在哪定义的?

Cursor告诉你它在 fastapi/routing.py 里,作用是”把你的函数包装成一个 APIRoute 对象,注册到路由表里”。

你跳到 routing.py ,看到了 APIRoute 这个类。你好奇路由匹配是怎么做的——当一个请求进来时,框架怎么知道该调用哪个 handler?

1
2
当一个HTTP请求到达时,FastAPI是怎么匹配到
对应的路由handler的?从哪个函数开始?

Cursor 告诉你 FastAPI 底层用的是 Starlette 的路由系统。实际的匹配逻辑在 Starlette 的 Router.__call__ 里。

遇到技术栈边界。 你可能不熟悉 Starlette。没关系:

1
2
3
我不熟悉Starlette。FastAPI和Starlette是什么关系?
我只需要知道它们之间的边界在哪——FastAPI自己做了什么,
哪些事情是委托给Starlette做的?

Cursor 会解释清楚边界。你决定要不要跨过这个边界深入到 Starlette 里去。也许现在不需要——你只需要知道”路由匹配这件事 Starlette 替你做了”就行, 你更感兴趣的是 FastAPI 自己加了什么东西。

记笔记。 走完这条主线之后你写下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
## 一个请求的处理流程

1. 启动时:@app.get 装饰器 → router.add_api_route() 
   → 创建 APIRoute 对象 → 注册到路由表

2. 请求到达时:Starlette的路由系统做URL匹配
   → 找到对应的 APIRoute

3. APIRoute 调用你的handler函数之前:

 - 依赖注入(Depends )被解析和执行
 - 请求参数被校验(用Pydantic)

4. 执行handler,返回结果

5. 结果被序列化成JSON返回

## 发现

FastAPI的核心价值不在路由(那是Starlette做的),
而在第3步——依赖注入和参数校验。
这才是它跟其他框架的本质区别。

选择深入点。 你对依赖注入特别好奇。开始第二轮探索,专门走Depends的解析流程。

循环。 每一轮探索都让你的地图更完整、更细致。三四轮之后,你对FastAPI的理解就已经远超大部分使用者了。


回到最开始的问题。

读复杂代码工程最大的难点从来不是”代码太多看不完”。代码多不可怕,可怕的是你不知道该看哪里。

Cursor 最大的价值不是替你读代码,而是在你迷路的时候告诉你该往哪走

但走路这件事,得你自己来

每一行你亲自读过的代码、每一个你亲自想明白的逻辑、每一次你的预期和实际运行结果对不上然后你搞清楚了为什么——这些时刻构成的理解,是任何AI 总结都给不了你的。

Cursor 是一个极好的向导。但向导的价值是带你去你自己想去的地方,不是替你走完全程然后给你讲一遍沿途的风景。

风景得自己看。看过的才是你的

我如何使用 Claude Code

原文地址:How I Use Claude Code

我使用 Claude Code 作为主要开发工具大约 9 个月了,逐渐形成了一套与大多数人截然不同的工作流。

大多数开发者的做法是:输入提示词,有时用 plan 模式,修复错误,然后循环重复。更极客的用户则会拼凑各种 ralph 循环、MCP、gas town(还记得吗?)等工具。但这两种方式的结果都是一团乱麻,遇到稍微复杂的任务就会彻底崩溃。

我接下来要介绍的工作流有一个核心原则:在你审阅并批准书面计划之前,绝不让 Claude 写代码。

这种规划与执行的分离,是我所做的最重要的事情。它能避免无效劳动,让我掌控架构决策,并且比直接跳到写代码相比,用更少的 token 产出明显更好的结果。

我通常会循环下面这 6 个阶段 1~6 次:

  • Research(研究)

  • Plan(计划)

  • Annotate(批注)

  • Todo List(任务清单)

  • Implement(实现)

  • Feedback & Iterate(反馈与迭代)

flowchart LR Research --> Plan --> Annotate Annotate --> TodoList["Todo List"] --> Implement --> Feedback["Feedback & Iterate"] Annotate -->|"repeat 1-6x"| Annotate

阶段1:Research(研究)

每项有意义的任务都从一个深度阅读指令开始。我要求 Claude 在做任何事之前,先彻底理解代码库的相关部分。

而且我始终要求将调研结果写入一个持久化的 Markdown 文件,而不是仅仅在对话中做口头总结。

1
深入阅读这个文件夹,彻底理解它的工作原理、功能和所有细节。完成后,将你的学习成果和发现写成详细报告,保存在 research.md 中。
1
详细研究通知系统,理解其中的复杂性,并写一份详细的 research.md 文档,记录通知系统工作原理的方方面面。
1
2
深入梳理任务调度流程,理解其细节,并查找潜在的 bug。系统中肯定存在 bug,因为它有时会运行本应被取消的任务。持续调研,直到找出所有 bug 为止。完成后,将你的发现写成详细报
告,保存在 research.md 中。

注意这些措辞:“深入”、“详细”、“复杂性”、“梳理一切”。这不是废话,而是必要的。没有这些词,Claude 只会浮于表面——读一个文件,在函数签名层面理解函数的作用,然后就继续往下走了。你需要明确告知它:浅尝辄止是不可接受的。

书面产出(research.md)至关重要。 这不是让 Claude 做作业,而是给我提供一个审阅界面。我可以阅读它,验证 Claude 是否真正理解了系统,并在任何规划开始之前纠正误解。如果调研有误,计划就会有误,实现也会有误——垃圾进,垃圾出。

这是 AI 辅助编码中代价最高的失败模式,它不是语法错误或逻辑错误,而是那些单独运行没问题、但会破坏周边系统的实现:

  • 忽略了已有缓存层的函数
  • 没有考虑 ORM 约定的数据库迁移
  • 重复了已有逻辑的 API 端点。

research 阶段就是为了防止所有这些问题。


阶段2:Planning(规划)

调研审阅完毕后,我会要求 Claude 在一个单独的 Markdown 文件中生成详细的实现计划。

1
我想构建一个新功能 \<名称和描述\>,用于扩展系统以实现 \<业务目标\>。请写一份详细的 plan.md 文档,说明如何实现,并附上代码片段。
1
list 接口应支持基于游标的分页,而不是偏移量分页。写一份详细的 plan.md,说明如何实现。阅读源文件后再提出建议,计划要基于实际代码库。

生成的计划始终包含:

  • 方案的详细说明
  • 展示实际修改的代码片段
  • 将被修改的文件路径
  • 相关考量和权衡

我使用自己的 .md 计划文件,而不是 Claude Code 内置的计划模式。内置计划模式很糟糕。 我的 Markdown 文件给了我完全的控制权——我可以在编辑器中编辑它,添加内联注释,并且它作为真实的项目产出物持久保存。

我一直在用的一个技巧:对于功能范围明确、且我在某个开源项目中看到过优秀实现的需求,我会在请求计划时附上那段代码作为参考。如果我想添加可排序 ID,我会粘贴一个做得好的项目中的 ID 生成代码,然后说:

1
"他们就是这样实现可排序 ID 的,请写一份 plan.md,说明我们如何采用类似的方案。" 相比从零设计,当 Claude 有具体的参考实现可以参照时,效果会好得多。

但计划文档本身并不是最有趣的部分,最有趣的是接下来发生的事。


Annotation Cycle(标注循环)

这是我工作流中最与众不同的部分,也是我贡献最大价值的地方。

Claude 写完计划后,我会在编辑器中打开它,直接在文档中添加内联注释。这些注释用于纠正假设、拒绝某些方案、添加约束条件,或者提供 Claude 所没有的领域知识。

注释的长度差异很大。有时只有两个词,比如在 Claude 标记为可选的参数旁边写"不可省略";有时则是一段话,用于解释业务约束或粘贴我期望的数据结构代码片段。

一些真实的注释示例:

  • “用 drizzle:generate 生成迁移,不要用原生 SQL” —— Claude 没有的领域知识
  • “不对——这里应该用 PATCH,不是 PUT” —— 纠正错误假设
  • “删掉这一节,这里不需要缓存” —— 拒绝某个提议方案
  • “队列消费者已经处理了重试,所以这里的重试逻辑是多余的。删掉它,让它直接失败就好” —— 解释为什么要修改
  • “这里错了,visibility 字段需要在列表本身上,而不是在单个条目上。当一个列表是公开的,所有条目都是公开的。请相应地重构 schema 部分” —— 重定向计划中的整个章节

comment 的方式:

  • 通过使用 > 📝 review comment:,这样我们人类看上去也是引用块形式,也明显

然后我让 Claude 回到文档:

1
 我在文档里加了一些批注(> 📝 review comment:),请逐条处理并相应更新文档。先别开始实际开发实现。

这个循环会重复 1 到 6 次。“暂不实现"这个明确的约束至关重要。 没有它,Claude 一旦认为计划足够好,就会立刻跳去写代码。在我说"可以了"之前,它还不够好。

为什么这个方法效果这么好

Markdown 文件充当了我和 Claude 之间的共享可变状态。我可以按自己的节奏思考,精确地标注出哪里有问题,并重新介入而不丢失上下文。我不需要在聊天消息中解释所有事情,而是直接指向文档中的确切位置,并把我的修正写在那里。

这与试图通过聊天消息来引导实现有着本质区别。计划是一份结构化的完整规范,我可以整体审阅;而聊天对话需要我不断翻滚才能重建决策过程。计划每次都胜出。

三轮"我添加了注释,请更新计划”,就能将一份通用实现计划转变为完美契合现有系统的方案。Claude 擅长理解代码、提出解决方案和编写实现,但它不了解我的产品优先级、我的用户痛点,或者我愿意接受的工程权衡。标注循环就是我注入这些判断的方式。


待办事项清单

在实现开始之前,我总会要求生成一个细粒度的任务拆分:

1
在计划中添加一个详细的待办事项清单,包含完成计划所需的所有阶段和独立任务——暂不实现。

这创建了一个在实现过程中充当进度追踪器的清单。Claude 在推进过程中会将完成的条目标记为已完成,这样我随时瞥一眼计划就能知道进展到哪里了。在持续数小时的会话中尤其有价值。


阶段3: 实现

计划准备就绪后,我发出实现指令。我将其精炼成了一个标准提示词,在各次会话中复用:

1
全部实现。完成一项任务或阶段后,在计划文档中将其标记为已完成。不要在所有任务和阶段完成之前停下来。不要添加不必要的注释或 JSDoc,不要使用 any 或 unknown 类型。持续运行类型检查,确保不引入新问题。

这一个提示词涵盖了所有关键点:

  • “全部实现”:执行计划中的所有内容,不要有所取舍
  • “在计划文档中将其标记为已完成”:计划是进度的唯一真实来源
  • “不要在所有任务和阶段完成之前停下来”:不要在中途停下来确认
  • “不要添加不必要的注释或 JSDoc”:保持代码整洁
  • “不要使用 any 或 unknown 类型”:保持严格类型
  • “持续运行类型检查”:尽早发现问题,而不是留到最后

我在几乎每次实现会话中都使用这个精确措辞(略有变化)。当我说"全部实现"时,所有决策都已经做出并经过验证。实现变成了机械性的,而不是创造性的。这是刻意为之的。 我希望实现是无聊的。创造性工作发生在标注循环中。一旦计划是对的,执行就应该是直接的。

没有规划阶段,通常会发生的情况是:Claude 在早期做出一个合理但错误的假设,在此基础上构建 15 分钟,然后我不得不撤销一连串修改。“暂不实现"的约束从根本上消除了这个问题。


实现过程中的反馈

一旦 Claude 开始执行计划,我的角色就从架构师转变为监督者。我的提示词变得极其简短。

规划注释可能是一段话,而实现纠正往往只是一句话:

  • “你没有实现 deduplicateByTitle 函数。”

  • “你把设置页面建在主应用里了,它应该在管理应用里,移过去。”

Claude 拥有计划和当前会话的完整上下文,所以简短的纠正就足够了。

前端工作是迭代最多的部分。我在浏览器中测试,快速发出修正:

  • “再宽一点”

  • “还是被裁剪了”

  • “有 2px 的间距”

对于视觉问题,我有时会附上截图。一张表格错位的截图,比描述问题要快得多。

我也会不断引用现有代码:

  • “这个表格应该看起来和用户表格完全一样,相同的表头、相同的分页、相同的行间距。”

这比从头描述设计精确得多。成熟代码库中的大多数功能都是现有模式的变体。新的设置页面应该看起来像现有的设置页面。指向参考实现,无需逐一说明所有隐含要求,Claude 通常会在修改之前先读取参考文件。

当某些东西偏离方向时,我不会试图修补它,而是还原并重新限定范围,丢弃 git 修改:

“我已经还原了所有修改。现在我只想让列表视图更简洁——其他什么都不做。”

还原后缩小范围,几乎总是比试图逐步修复一个糟糕方案产生更好的结果。


始终掌握主导权

即使我将执行委托给了 Claude,我也从不给它构建什么的完全自主权。我在 plan.md 文档中完成绝大部分主动引导。

这很重要,因为 Claude 有时会提出技术上正确、但对项目来说是错误的解决方案——也许方案过度设计,或者改变了系统其他部分依赖的公共 API 签名,或者在有更简单选项时选择了更复杂的方案。我拥有 Claude 所没有的关于更广泛系统、产品方向和工程文化的上下文。

  • 从提案中挑选:当 Claude 识别出多个问题时,我逐一处理:“对于第一个问题,直接用 Promise.all,不要搞得过于复杂;对于第三个问题,提取成一个单独的函数以提高可读性;忽略第四和第五个,它们不值得增加这种复杂性。” 我根据自己对当前什么重要的了解,做出逐条决策。

  • 削减范围:当计划包含锦上添花的内容时,我主动删减。“从计划中删除下载功能,我现在不想实现它。” 这防止了范围蔓延。

  • 保护现有接口:当我知道某些东西不应该改变时,我设置硬性约束:“这三个函数的签名不应该改变,应该是调用方来适配,而不是库。”

  • 覆盖技术选择:有时我有 Claude 不会知道的特定偏好:“用这个模型而不是那个模型"或"用这个库的内置方法而不是自己写一个”。快速、直接的覆盖。

Claude 处理机械性执行,而我做判断。计划预先捕获了重大决策,而有选择性的引导处理实现过程中出现的较小决策。


单次长会话

我在****单次长会话**中完成调研、规划和实现,而不是将它们分散到多个独立会话中。一次会话可能从深度阅读一个文件夹开始,经过三轮计划标注,然后运行完整的实现——全部在一次连续对话中完成。

我没有看到大家所说的在超过 50% 上下文窗口后出现的性能下降。实际上,当我说"全部实现"时,Claude 已经在整个会话中积累了理解:在调研期间阅读文件、在标注循环中精炼其思维模型、吸收我的领域知识纠正。

当上下文窗口满了,Claude 的自动压缩会保持足够的上下文继续工作。而计划文档——这个持久化的产出物——以完整的保真度在压缩中保存下来。我可以在任何时候指向它。


用一句话总结这个工作流

深度阅读 → 写一份计划 → 不断标注计划直到它正确 → 然后让 Claude 一次性执行完所有内容,同时持续检查类型。

就这些。没有神奇的提示词,没有精心设计的系统指令,没有聪明的黑科技。只是一个严格的流水线,将思考与打字分离开来。调研防止 Claude 做出无知的修改。计划防止它做出错误的修改。标注循环注入我的判断。实现命令让它在所有决策都已做出后不间断地运行。

试试我的工作流,你会好奇自己以前没有一份标注过的计划文档,是怎么用编程助手交付任何东西的。