代码库设计
它的作用¶
codebase-design 固定你设计模块时使用的词:模块、接口、深度、接缝、适配器、杠杆、局部性。它精确地定义每一个词,禁止宽松的替代词(“组件”、“服务”、“API”、“边界”),并陈述由这些词引出的一小撮原则。
它是一个参考,而不是流程。它不运行循环,不产出工件,也从不停下来向你提问。其他所有涉及设计的技能都使用它的词汇。单独使用时,它只给你这些词,然后停止。在调用它之前要知道这一点。如果你把一个 会话 指向一个没有流程、也没有停止规则的技能,并说“开始”,智能体就会自行发明一个流程。下面的问题展示了那会是什么样子。
何时使用它¶
输入 /codebase-design,或者当设计任务匹配时,智能体会自动使用它。
当你已经知道要重构哪段代码,并且需要思考它的形状时,使用它:接缝放在哪里、接口可以有多小、一次提取是否能让调用方摆脱复杂性。也用它来结束关于某个设计词含义的争论。
有几个技能与它相近。根据你面临的问题选择:
| 问题 | 技能 |
|---|---|
| 单个模块的形状:它的接口、接缝、深度 | codebase-design |
| 领域词汇:“account” 有三种含义,两个人对 “cancellation” 的理解不同 | domain-modeling |
| 你还不知道要重构哪个模块 | improve-codebase-architecture(用于发现候选项的调查) |
| 你希望设计被质疑,而不仅仅是被命名 | grilling |
| 有一个具体行为要构建,并且你希望测试能在重构后存活 | tdd |
词汇表¶
术语表就是这个技能。该技能将每个术语与其他术语相对地定义,并给出每个术语所替代的词。
| 术语 | 含义 | 不要说 |
|---|---|---|
| 模块 | 任何具有接口和实现的东西。刻意不区分规模:一个函数、一个类、一个包、一个跨层级的切片。 | 单元、组件、服务 |
| 接口 | 调用方必须知道才能正确使用它的一切:类型签名,以及不变量、顺序约束、错误模式、必需配置、性能特征。 | API、签名 |
| 深度 | 接口处的杠杆:调用方或测试每学习一个单位的接口,能行使多少行为。深:小接口背后有大量行为。浅:接口几乎和实现一样复杂。 | 无 |
| 接缝 | Michael Feathers 的术语:一个可以在不编辑该处的情况下改变行为的地方。它是接口的位置,放在哪里是一个独立决策,与背后放什么分开。 | 边界 |
| 适配器 | 在接缝处满足接口的具体事物。它命名的是一个角色,而不是一类事物:内存假对象和 Postgres 仓库都是适配器。 | 无 |
| 杠杆 | 调用方从深度中得到的东西:每学习一个单位的接口,获得更多能力。 | 无 |
| 局部性 | 维护者从深度中得到的东西:变更、缺陷和验证都留在一个地方,因此一次修复覆盖所有调用方。 | 无 |
该技能不将深度定义为实现行数与接口行数之比,这是 Ousterhout 自己的定义。该指标会奖励填充实现,因此技能改用“深度即杠杆”。
四项原则¶
- 深度是接口的属性,而不是实现的属性。 一个深模块内部可以由小的可替换部件构建。调用方只是看不到它们。一个模块可以有供自身测试使用的内部接缝,以及位于其接口处的一个外部接缝。
- 删除测试。 想象删除该模块。如果复杂性消失了,该模块只是一个直通层。如果复杂性在 N 个调用方中重新出现,该模块隐藏了真实复杂性。
- 接口就是测试表面。 调用方和测试跨越同一个接缝。如果你想测试越过接口,模块的形状就错了。
- 一个适配器意味着一个假设的接缝。两个适配器意味着一个真实的接缝。 在某个东西跨越它变化之前,不要切出接缝。单适配器接缝只是间接层。
两个支持文件走得更远,技能按需读取它们,而不是预先读取。DEEPENING.md 将候选项的依赖分为四类(进程内、本地可替换、远程但自有、真正外部),因为类别决定了你如何跨越其接缝测试被深化的模块。DESIGN-IT-TWICE.md 启动并行 子智能体,为同一模块生成三个或更多截然不同的接口,然后按深度、局部性和接缝位置比较它们。
常见问题¶
我实际上如何在 TypeScript 中构建一个深模块?
这是关于该技能被问得最多的问题,而技能并不回答它。它定义了深模块是什么;它没有说明如何阻止一个杂散导入越过接口。Issue #458 直截了当地说:“假设我们对接口很满意,它隐藏了细节等等。但我们如何强制执行?我认为如果没有 linting 或清晰的护栏,人类和 LLM 都会随着时间让它变得混乱。”该线程中的回答给出了三个选项:把它包装在一个类或 IIFE 中,并接受类会变得巨大;把它做成 monorepo 中的一个包,并接受 monorepo 工具链;或者使用 dependency-cruiser 之类的 linter 来禁止绕过接口的导入。在这些机制中,Effect 最好,dependency-cruiser 次之。仓库的 in-progress/ 目录中有一个 setup-ts-deep-modules 技能,它建立 src/packages/<name>/index.ts 约定,但它是一个 beta 通道技能,没有文档页面,也不附带 lint 规则。
我把一个会话指向了它,它烧掉了 100k tokens,重新设计了我从未要求过的东西。
这是一个已知问题,已提交为 问题 #449。该技能由模型调用,并把自己描述为词汇,但其中没有任何内容能硬性阻止代理把它当作可运行的流程。当被告知“在 /codebase-design 中恢复并推动未决决策”时,一个代理选择了该技能中最接近流程的部分:DESIGN-IT-TWICE.md 中的并行子代理。它重新探索了前一个会话已经映射过的代码,并且在询问任何问题之前工作了很长时间。该技能没有驱动技能所具备的任何护栏(检查点、一次只问一个问题、不自动推进),因为它是一个参考资料。变通方法是调用一个驱动技能(/grill-with-docs、/improve-codebase-architecture 或 /tdd),并将 codebase-design 用作其词汇。该问题仍处于打开状态。
design-an-interface 去哪了?是否存在 /interface-design 技能?
该技能取代了 design-an-interface,并接管了其内容。没有缺失任何内容。它的“design it twice”技术(并行子代理生成截然不同的设计,源自 Ousterhout)在此以 DESIGN-IT-TWICE.md 的形式提供。另外,一些人要求为深模块/薄接口理念提供一个专门的 /interface-design 技能;该技能已经覆盖了这一理念,并且没有计划单独的技能。如果你是在寻找其中任何一个名称,这就是该页面。
这难道不是一种文件结构约定,例如文件夹、barrel 文件、功能切片?
不是。人们多次对此提出反对,该技能也没有改变。问题 #95 提出了一种形式化的分形树文件结构,作为深模块的具体实现;回复称两者是独立的:“深模块关乎接口的设计以及通过严格接口进行访问,无论文件系统看起来如何。采用这种方法,完全可能出现浅模块。”同样的问题也出现在 #458 中:“我认为你可能把模块的概念与文件系统联系得太紧密了。文件系统当然可以为模块的形状提供有用的提示,但在构建深模块时,没有必要使用文件系统。”术语表有意将 模块 定义为与规模无关。
tdd 是否真的使用这套词汇?
现在它确实使用了,但很长一段时间都没有。v1.0 从 tdd 中移除了内联的深模块说明,转而采用这个共享技能,但没有人添加一个指针来替代它们,因此 tdd 自行定义了“接缝”,并且没有引用任何内容。该指针现在位于 tdd 中,当未决问题是接口的形状而非测试时,代理会遵循它。tdd 仍然拥有“接缝”作为你测试的边界;该技能拥有其背后的模块形状。
design-it-twice 模式在 Claude Code 之外是否有效?
并不完全适用。DESIGN-IT-TWICE.md 说“使用 Agent 工具并行生成 3 个或更多子代理”,而“Agent”是 Claude Code 工具 的名称。该仓库为其他 harnesses 提供了元数据,包括 Codex,而这些可能没有名为 Agent 的工具。因此,并行设计阶段的可移植性不如该技能的元数据所暗示的那样。问题 #564 跟踪了这一点,并且仍处于打开状态。
我能否向术语表添加自己的概念,例如 connascence、模块秘密、渐进式披露?
人们已经提出过这些。问题 #180 添加了 Parnas 的模块秘密和 Page-Jones 的 connascence,作为什么正在跨越接缝泄漏的命名层,并附带了一个可用的 diff;问题 #303 提出在实现内部使用渐进式披露,使得一个在公共接口上很深的模块在其内部也具有结构。两者都处于打开状态且未合并。发布的术语表有意保持精简,而技能本身给出了原因:一致的语言才是重点,一个没有人一致使用的术语比没有术语更糟。
如果它正在起作用¶
- 设计对话不再产生“组件”、“服务”和“边界”这些词,而是开始产生“模块”、“接口”和“接缝”。
- 有人可以指向一个提议的提取,并明确说明它是否通过删除测试,而不含糊其辞。
- 一个提议的接缝会附带一个被命名的第二个适配器,而不仅仅是第一个。
- 对接口的讨论涵盖不变量、顺序和错误模式,而不仅仅是类型签名。
- 调用它不会启动一个会话。如果代理仅从
/codebase-design开始读取文件并提出重构,它就把参考资料误当成了驱动技能。
它适合的位置¶
codebase-design 是一个可随时单独使用的技能,也是工程技能之下的词汇层,而不是任何链条中的一个步骤。它最接近的邻居是 domain-modeling,这是问题域词汇的平行参考资料,而不是模块的形状。两者通常一起使用,因为要把一个深模块命名好需要两者。improve-codebase-architecture 是另一个:它调查代码库中的深化候选项,并用本术语表写出其中每一个,因此它找到模块,而你用本技能的词汇来设计它。当你不确定哪个技能或流程适合时,ask-matt 会引导你。
本页原文 Markdown:在 AtomGit 查看·内容源自开源项目 skills13/skills