# Step 10: 源码对照
一句话导读:Phase 1 收官不写新功能,而是用一段脚本真的去读
claude-code-src/的源文件、数它们的行数,把我们 9 步的骨架摆在真实血肉旁边——用「规模差」验证一个假设:核心交互模式我们已经写对了,剩下的都是在骨架上填肌肉。
# 一、这一步做了什么(What)
step10 是一个纯「对照复盘」步骤。它做了三件事:
- 真读源码数行数:脚本用
readFileSync打开真实的QueryEngine.ts/Tool.ts/cli.tsx等文件,split('\n').length数出真实行数——不是凭记忆写的数字; - 抽真实骨架片段:用
range()/excerpt()截取真实源码的类定义、类型定义片段打印出来,和我们的实现并列; - 画功能对照表:对话循环、工具系统、UI、权限、System Prompt、MCP、多代理各维度,列「我们的实现」vs「真实源码」。
它是 Phase 1 的「阶段验收」,也为 Phase 2 起了个头(下一步开始实现 Tool 系统)。
# 二、面试官视角:为什么要做?(Why)
面试题:都能自己写一个 mini 版了,为什么还要专门花一步去对照真实源码?直接继续往下写不行吗?
因为「自己能跑起来」和「按真实架构在演进」是两回事。定期做源码对照有三个不可替代的价值:
| 目的 | 不做对照 | 做源码对照 |
|---|---|---|
| 校准方向 | 容易越写越像自己的玩具,偏离真实设计 | 每个 Phase 确认「抽象是否和真实同构」 |
| 量化差距 | 只有模糊的「还差很多」 | 精确到行数/文件数,知道差在规模还是设计 |
| 区分「省略」与「缺陷」 | 分不清哪些是刻意简化、哪些是设计错了 | 明确标注「未实现(Phase X)」vs「实现错了」 |
| 建立信心与路线图 | 不知道自己覆盖了多少 | 确认核心已覆盖,后续 40 步是填充 |
一句话:源码对照是「阶段性对表」。学习一个大型系统最怕闭门造车,越写越像自己臆想的版本。每隔几步和真实源码对一次表,能及时发现「我的抽象和真实是不是同构」——如果同构,只是规模小,那方向就对了。
# 关键结论:核心是同构的,差的是规模
对照下来最重要的发现是——真实 QueryEngine 的核心逻辑和我们一样:都是配 API 客户端、发消息、处理流式响应、维护历史队列。差别不在「思路」,而在「规模和生产级特性」:它有完整的 Token 预算、对话压缩、错误恢复、并发限制。这个结论直接决定了后续路线:我们已经把最核心的交互模式写对了,剩下 40 步是在这个正确骨架上填肌肉。
# 三、原理:它是怎么工作的(How,这里指「对照脚本怎么工作」)
# 不背数字,而是让脚本真去读文件
这一步最讲究的地方是:所有行数都是运行时数出来的,不是手写的。脚本定义了几个读源码的小工具:
const SRC = 'D:/牛马/源码学习/claude-code-src/src';
function read(path: string): string {
return readFileSync(SRC + '/' + path, 'utf-8');
}
function lines(path: string): number {
return read(path).split('\n').length; // ← 真实行数,现场数
}
function range(path, start, end): string {
return read(path).split('\n').slice(start - 1, end).join('\n');
}
2
3
4
5
6
7
8
9
10
11
然后用它数真实文件规模:
const files = [
['QueryEngine.ts', '对话引擎', lines('QueryEngine.ts')],
['Tool.ts', '工具接口', lines('Tool.ts')],
['tools.ts', '工具注册', lines('tools.ts')],
['main.tsx', '主入口', lines('main.tsx')],
['entrypoints/cli.tsx', 'CLI 入口', lines('entrypoints/cli.tsx')],
];
2
3
4
5
6
7
这样做的意义:对照是可复现、可信的。源码更新了,重跑一次数字自动变,不会像手写数字那样过期骗人。
# 抽真实骨架,眼见为实
脚本还用 range() 把真实 QueryEngine.ts 第 130-200 行的类定义骨架捞出来打印,让你亲眼看到「真实的 QueryEngine 也就是个 class + constructor + async run」,祛魅它的神秘感。
# 数据流
step10 脚本启动
↓
readFileSync 真实 claude-code-src/src/*.ts
↓
lines() 数行数 → 打印规模对比表
range() 抽片段 → 打印真实类/类型骨架
↓
手写功能对照表(对话循环/工具/UI/权限/...)
↓
输出「Phase 1 覆盖了什么 + Phase 2 预告」
2
3
4
5
6
7
8
9
10
# 四、深入追问(面试常见 follow-up)
Q:规模对比里我们 ~200 行 vs 真实 QueryEngine ~1234 行,这 1000 行差距主要是什么? A:不是「核心逻辑」的差距,而是生产级特性:Token 预算精确控制、上下文自动压缩、错误恢复与重试、并发/速率限制、流式事件的精细处理、边界情况兜底。核心的「发消息—收流—维护历史」我们和它一样,那 1000 行是把这个核心包裹成生产可用的健壮系统。
Q:为什么行数要现场数,不直接写死在文章里?
A:写死的数字会过期且不可信。源码是活的,版本一升行数就变。用脚本 readFileSync + split 现场数,保证任何时候重跑得到的都是当前真实值——这也是「忠于源码、不编造」的工程化保证。
Q:对照表里很多格子写「无 / 未实现」,这是承认失败吗? A:不是。这些是刻意的、有序的省略,并标注了将在哪个 Phase 补上(如工具系统 Phase 2、权限、UI、MCP、多代理各有其步)。区分「暂未实现的路线图项」和「实现错了的缺陷」正是对照的价值——前者是计划,后者才是问题,而这一步没发现后者。
Q:既然核心同构,后面 40 步是不是就没技术含量了? A:恰恰相反。骨架好写,把骨架变成生产级系统的那些「肌肉」——工具系统的权限沙箱、UI 的 React+Ink 渲染、MCP 的插件协议、多代理的上下文隔离——才是真正的工程难点。step10 的结论是「地基对了」,不是「盖楼很简单」。
# 五、踩坑 / 设计权衡
- 对照脚本硬编码了源码绝对路径
D:/牛马/源码学习/claude-code-src/src:这是学习项目的权宜,换机器就得改。生产工具会用相对路径或配置项,但对一次性对照脚本,简单直接优先。 - 功能对照表是手写的:规模数字能自动数,但「我们的实现 vs 真实设计」的语义对照需要人的理解,无法脚本化。这也提醒:自动化能测「量」,测不了「架构是否同构」这种需要判断的「质」。
# 六、与真实源码的对照
| 我们的实现(Phase 1,10 步) | Claude Code 源码 |
|---|---|
对话循环 while + streamAI ~20 行 | QueryEngine.ts ~1234 行 |
| 工具系统 Tool 接口(仅预留) | Tool.ts ~754 行 + tools/ 53 个工具 |
API 调用 streamAI() ~30 行 | services/api/ 20 个文件 |
终端 UI console.log | React + Ink 406 个组件文件 |
| 权限系统:无 | utils/permissions/ 24 个文件 |
| System Prompt:4 行字符串 | constants/system.ts ~500 行 |
| 插件/MCP:无 | services/mcp/ 23 个文件 |
| 多代理:无 | coordinator/ + AgentTool/ |
# 七、一句话总结
step10 = 阶段性对表:用脚本真读真数得出「核心同构、规模有差」的结论——我们 10 步已把 REPL 循环、流式输出、消息历史、System Prompt、持久化这套最核心的交互模式写对了,后续 40 步不是推倒重来,而是在这个正确骨架上填工具、权限、UI、插件、多代理的肌肉。
# 下一节预告
Phase 2 开始。step11 动手实现 Tool 系统——定义 ToolDef / ToolResult / ToolRegistry 三大抽象,这是把「对话」升级成「能动手干活」的分水岭。