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 已经覆盖了所有核心概念。差距在于每个概念从"能跑"到"能用"之间的那段路——那段路叫工程化。

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——自动化构建、测试、发布流程

这些规范增加了代码量(类型定义、测试、文档都是"不直接产生功能"的代码),但它们让大型团队能在同一个代码库上协作而不互相踩脚。

如果要把 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 理解工程细节。两者互补,不替代。

在"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 官方源码项目进行架构分析,聚焦设计思想而非代码实现。

相关内容