关于 codebase-design
面向编码 Agent 的设计技能,将深模块原则用于代码设计与重构。它强调用小接口隐藏大量行为,提升调用方的杠杆效益和维护局部性,并让测试通过相同的接缝进行。
主要功能
- 01以小接口设计深模块
- 02区分模块、接口与实现
- 03审慎选择接缝和适配器
- 04用删除测试评估模块
- 05设计便于测试的接口
- 06比较不同的接口设计
适用场景
- 01围绕清晰接缝重构代码
- 02减少调用方需要面对的复杂度
- 03判断模块是否只是透传
- 04通过接口设计提升可测试性
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ leandrocfe/ skills --skill codebase-design安装后在 Claude Code 中输入/codebase-design确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
DEEPENING.mdDESIGN-IT-TWICE.mdagents/openai.yaml详细介绍
代码库设计
设计深模块(deep modules):在一个简洁的接口后封装大量行为,并放置在清晰的 seam 上,可通过该接口进行测试。设计或重构代码时,始终使用这套术语和原则。目标是为调用者提供 leverage,为维护者提供 locality,并让所有人都能轻松测试。
术语表
请严格使用这些术语——不要用「component」「service」「API」或「boundary」替代。术语一致是首要目标。
Module — 任何具有接口和实现的事物。刻意不限定规模:可以是函数、类、包,也可以是横跨多个层的 slice。避免使用:unit、component、service。
Interface — 调用者正确使用模块所需了解的一切:类型签名,以及不变量、顺序约束、错误模式、所需配置和性能特征。避免使用:API、signature(范围过窄——仅指类型表面)。
Implementation — 模块内部的内容,即其代码体。它不同于 Adapter:一个事物可以是实现很大的小型 adapter(Postgres 仓库),也可以是实现很小的大型 adapter(内存中的 fake)。讨论 seam 时使用「adapter」;其他情况下使用「implementation」。
Depth — 接口提供的 leverage:调用者(或测试)通过每单位需要学习的接口所能运用的行为量。当大量行为封装在简洁的接口后,模块就是深的;当接口几乎与实现一样复杂时,模块就是浅的。
Seam (Michael Feathers) — 一个可以在不修改该处代码的情况下改变行为的位置;也就是模块接口所在的位置。seam 的放置位置本身就是一项独立的设计决策,与 seam 后封装的内容无关。避免使用:boundary(容易与 DDD 中的 bounded context 混淆)。
Adapter — 在 seam 上满足某个接口的具体事物。描述的是角色(填补哪个槽位),而非实质(内部有什么)。
Leverage — 调用者从 depth 中获得的收益:每单位所学接口所能获得的更多能力。一项实现可以在 N 个调用点和 M 个测试中带来收益。
Locality — 维护者从 depth 中获得的收益:更改、bug、知识和验证都集中在一个地方,而不是散布在调用者中。修复一次,处处修复。
深模块与浅模块
深模块 = 简洁的接口 + 大量实现:
┌─────────────────────┐
│ Small Interface │ ← Poucos métodos, parâmetros simples
├─────────────────────┤
│ │
│ Deep Implementation│ ← Lógica complexa escondida
│ │
└─────────────────────┘
浅模块 = 庞大的接口 + 少量实现(应避免):
┌─────────────────────────────────┐
│ Large Interface │ ← Muitos métodos, parâmetros complexos
├─────────────────────────────────┤
│ Thin Implementation │ ← Apenas repassa
└─────────────────────────────────┘
设计接口时,请思考:
- 我能减少方法数量吗?
- 我能简化参数吗?
- 我能在内部隐藏更多复杂性吗?
原则
- Depth 是接口的属性,而非实现的属性。 深模块的内部可以由小型、可模拟且可替换的部分组成——它们只是不属于接口。模块可以拥有 internal seams(仅供其实现内部使用的私有 seam,由模块自身的测试使用),也可以拥有接口上的 external seam。
- 删除测试。 想象一下删除这个模块。如果复杂性随之消失,它只是一个透传层。如果复杂性在 N 个调用者中再次出现,那么它就发挥了作用。
- 接口就是测试面。 调用者和测试会跨越同一个 seam。如果你想在接口之外测试,这个模块的设计可能就不合适。
- 一个 adapter 意味着一个假设的 seam。两个 adapter 意味着一个真实的 seam。 除非确实有事物会在 seam 上变化,否则不要引入 seam。
面向可测试性进行设计
优秀的接口让测试变得自然而然:
-
接受依赖,而不是创建依赖。
// Testável function processOrder(order, paymentGateway) {} // Difícil de testar function processOrder(order) { const gateway = new StripeGateway(); } -
返回结果,而不是产生副作用。
// Testável function calculateDiscount(cart): Discount {} // Difícil de testar function applyDiscount(cart): void { cart.total -= discount; } -
保持表面精简。 方法越少,需要的测试就越少。参数越少,测试准备就越简单。
关系
- 一个模块恰好有一个接口(它呈现给调用方和测试的表面)。
- 深度是模块的属性,以其接口为衡量基准。
- 接缝是模块的接口所在之处。
- 适配器位于接缝上,并满足接口。
- 深度为调用方带来杠杆效应,为维护者带来局部性。
不采用的表述
- 将深度视为实现代码行数与接口代码行数的比率(Ousterhout):这会鼓励膨胀实现。我们将深度视为杠杆效应,而非比率。
- 将“接口”视为 TypeScript 的
interface关键字或类的公共方法:范围太窄——这里的接口包括调用方需要了解的所有信息。 - “边界”:这个词与 DDD 中的有界上下文含义重叠。请使用接缝或接口。
深入了解
- 结合依赖关系加深集群——参见 DEEPENING.md:依赖类别、接缝规范,以及测试中的“替换而非叠加”策略。
- 探索替代接口——参见 DESIGN-IT-TWICE.md:并行启动多个子 Agent,以截然不同的方式设计接口,然后从深度、局部性和接缝位置等方面进行比较。