3000 行 vs 50 万行:从玩具到产品的架构深渊
claude-code-from-scratch 项目用大约 3000 行 TypeScript 实现了一个"迷你版 Claude Code"。它有 Think-Act-Observe 循环、工具系统、System Prompt、权限控制、记忆、Skill、多代理、MCP——我们在前面 10 篇文章里拆解的核心概念,它全都有。
Claude Code 官方源码呢?大约 50 万行 TypeScript/TSX。
3000 行和 50 万行之间差了一百多倍。 差了什么?
这不是"功能多寡"的差距。from-scratch 已经覆盖了所有核心概念。差距在于每个概念从"能跑"到"能用"之间的那段路——那段路叫工程化。
概念验证 vs. 生产就绪
from-scratch 是一个概念验证(Proof of Concept)。它回答了"Claude Code 的核心原理是什么"这个问题。你读完它的代码,能理解 Think-Act-Observe 循环怎么转、工具怎么调用、记忆怎么存储。
Claude Code 是一个产品。它回答的是"怎么让 100 万开发者每天可靠地使用这个工具"。
两者解决的问题完全不同:
- 概念验证解决"能不能做"
- 产品解决"做得好不好、稳不稳、久不久"
从"能"到"好"的距离,比大多数人想象的要远。
错误处理:从"希望不报错"到"假设一定会报错"
from-scratch 的错误处理是概念级的:工具调用失败了,把错误信息塞回对话历史,让模型自己处理。逻辑正确,覆盖主流场景。
Claude Code 的错误处理是工业级的。它处理的不只是"工具调用失败",还包括:
- API 限流——Anthropic/OpenAI 的速率限制到了,怎么降级?切备用模型?排队重试?告知用户?
- 网络中断——请求发到一半断了,怎么恢复?从断点续传还是从头重来?
- 上下文溢出——模型返回的内容比预期长,超出缓冲区,怎么截断?
- 并发冲突——多个工具调用同时修改同一个文件,怎么协调?
- 进程崩溃——Agent 跑到一半崩了,怎么恢复?会话状态怎么持久化和重建?
- 编码问题——文件不是 UTF-8 怎么办?二进制文件被误读怎么办?
- 权限边界——用户撤销了某个权限,正在执行的工具调用怎么处理?
from-scratch 假设"大多数操作会成功"。Claude Code 假设"一切都会出错,只是时间问题"。
这不是悲观,是工程纪律。概念验证在理想条件下运行,产品在恶劣条件下生存。
用户体验:从"能输出"到"好用"
from-scratch 的 UI 是基本的终端输出——模型回复直接打印到控制台。能用,能看懂。
Claude Code 的 UI 是一个基于 Ink(React for CLI)的完整终端应用。它有:
- 流式输出——模型生成的每个 token 实时显示,不需要等完整回复
- 结构化展示——工具调用、文件变更、命令输出用不同的样式和颜色区分
- 交互界面——用户可以在 Agent 运行时输入命令、切换权限、查看状态
- 进度指示——长时间操作有 spinner 和状态提示
- 历史记录——可以回看之前的对话和工具调用
这些"表面功夫"占了大量代码,但它们决定了用户是"能用这个工具"还是"愿意每天用这个工具"。
用户体验不是产品的锦上添花,是产品的核心竞争力。 两个功能相同的工具,用户体验好的那个赢得用户。在 AI 工具领域尤其如此——用户与 AI 的交互是高频、长期的,体验的微小差异在日复一日的使用中被放大。
性能优化:从"能跑通"到"跑得快"
from-scratch 的性能策略是"先能跑再说"。每次循环调用模型,等返回,执行工具,再调用。串行、阻塞、简单。
Claude Code 的性能优化是多层次的:
- 请求优化——并行工具调用、流式响应、连接复用
- 缓存策略——文件内容缓存、记忆预加载、工具结果缓存
- 上下文管理——智能裁剪、动态压缩、token 预估
- 渲染优化——终端输出的增量更新,避免全量重绘
这些优化不是在"能跑通"之后加上去的装饰,它们是在产品过程中逐步演化出来的。每一个优化都对应一个真实用户的真实痛点:“为什么读三个文件要等 5 秒"“为什么长对话越来越慢”。
可维护性:从"一个人能看懂"到"一百个人能协作”
3000 行的代码库,一个人能从头读到尾。模块边界模糊一点没关系,命名不规范一点能忍受,注释少一点靠记忆。
50 万行的代码库需要严格的工程规范:
- 模块化——清晰的模块边界,明确的接口定义
- 类型安全——严格的 TypeScript 类型约束,减少运行时错误
- 测试覆盖——单元测试、集成测试、端到端测试
- 文档——API 文档、架构决策记录、贡献指南
- CI/CD——自动化构建、测试、发布流程
这些规范增加了代码量(类型定义、测试、文档都是"不直接产生功能"的代码),但它们让大型团队能在同一个代码库上协作而不互相踩脚。
那 50 万行里到底是什么
如果要把 Claude Code 的 50 万行分类,大致是这样的分布:
- 核心 Agent 逻辑(循环、工具、prompt):~5%
- UI/终端渲染(Ink 组件、流式输出、交互):~20%
- 工具实现(30+ 工具的详细实现和错误处理):~15%
- 命令系统(50+ CLI 命令):~10%
- 基础设施(配置管理、日志、错误处理、会话持久化):~15%
- 测试(单元测试、集成测试、E2E):~15%
- 构建与工具链(TypeScript 配置、CI/CD、打包):~5%
- 其他(MCP 客户端、迁移脚本、类型定义):~15%
核心逻辑只占 ~5%。剩下的 95% 是让核心逻辑在真实世界中可靠运行的基础设施。
这 95% 不性感。它不会出现在技术博客里,不会成为面试问题,不会在技术会议上被吹捧。但它是产品和玩具的分水岭。
从玩具到产品的架构深渊
“架构深渊"这个词不是夸张。它描述的是一个真实的现象:
一个系统的概念架构和它的生产架构之间,存在一个巨大的、非线性的复杂度跃迁。
跨越这个深渊没有捷径。你不能"一开始就设计好”——因为你不知道哪些细节重要,直到真实用户开始用。你只能一步步走:先做出概念验证,找到用户,收集反馈,修补问题,迭代优化。每一步都暴露新的细节,每一个细节都需要工程决策。
from-scratch 的价值在于它让你站在深渊边上,看到对面是什么。Claude Code 的源码价值在于它展示了跨越深渊后的样子。
两者之间的那段路,没有代码可以展示。它由成千上万个工程决策组成,每一个都是"这个问题怎么处理更好"的判断。有些决策是对的,有些是错的,有些是妥协。它们叠加起来,就是产品。
启示
这个对比不是"from-scratch 太简单"或"Claude Code 太复杂"的价值判断。两者在不同的维度上有价值:
- from-scratch 的价值是教育——它让你理解核心概念,建立心智模型
- Claude Code 的价值是实践——它展示了概念在真实世界中的样子
理解 AI Agent 架构,需要先读 from-scratch 建立概念框架,再读 Claude Code 理解工程细节。两者互补,不替代。
延伸阅读:BYF 的位置 —— 中间的工程化实践
在"3000 行 vs 50 万行"的光谱中间,BYF 处在一个有趣的位置。
BYF 不是概念验证(它在生产中使用),也不是像 Claude Code 那样的 50 万行巨兽(它的核心大约几万行)。它处于"概念已验证、工程待完善"的阶段。这个阶段有一些独特的工程挑战:
1. 分层架构的自觉。 BYF 通过 ADR 0006 明确定义了四层架构(应用层 → SDK 层 → 引擎层 → LLM/环境层)。每一层的依赖方向是严格的——apps/cli 只能通过 @byfriends/sdk 消费核心能力,不能直接导入 agent-core。这种自觉的分层让 BYF 的每个子系统可测试、可替换。
2. 设计债的主动清理。 BYF 有个 improve-architecture 扫描流程,定期扫描源码找出设计债(类型逃生口、抽象泄漏、职责混淆)。TaskEntry 判别联合(ADR 0014)就是扫描结果之一:一处 as unknown as KaosProcess 的类型强制转换被重写为干净的类型联合。
3. 可视化调试工具。 BYF 的 vis 工具(byf vis 命令)读取会话的 wire records,渲染为可浏览的时间线。你能看到每一次 tool call 的时序、每一步的 token 消耗、每一轮压缩的触发点。这是 Claude Code 没有的功能——BYF 选择"在调试体验上多花钱,在核心功能上不铺摊子"。
BYF 的存在本身证明了从概念到产品的架构深渊是可以跨越的,只是需要一步一步走——每一次设计债清理、每一次 ADR 决策、每一次分层优化,都是在深渊里铺下一块砖。
下一篇
这是系列的最后一篇。我们将从 Think-Act-Observe 循环一路回顾到 50 万行的工程深渊,然后看向更远的地方:AI 编程工具的未来——从 Copilot 到 Autonomous Agent 的演进路径,以及 CLAUDE.md 这种"项目宪法"可能带来的开发范式变革。
本系列基于 Claude Code 官方源码项目进行架构分析,聚焦设计思想而非代码实现。