关于 codebase-design
面向开发者和编码 Agent 的代码设计技能,聚焦于以小接口承载丰富行为的深模块。它提供统一术语和原则,帮助改善可测试性、调用方的能力杠杆及维护局部性。
主要功能
- 01设计深模块
- 02降低接口复杂度
- 03有意识地设置接缝
- 04显式传入依赖
- 05通过模块接口测试
适用场景
- 01重构转发型模块
- 02设计可测试的接口
- 03确定行为应放置的位置
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ utarn/ engineer-skills --skill codebase-design安装后在 Claude Code 中输入/codebase-design确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
DEEPENING.mdDESIGN-IT-TWICE.mdagents/openai.yaml详细介绍
代码库设计
设计深层模块:在简洁的接口后封装大量行为,放置在清晰的接缝处,并可通过该接口进行测试。在设计或重构代码时,始终使用这套语言和原则。目标是为调用方提供杠杆效益,为维护者提升局部性,并让所有人都能轻松测试。
术语表
请严格使用以下术语——不要替换成“组件”“服务”“API”或“边界”。统一用语正是关键所在。
模块——任何具有接口和实现的东西。刻意不限定规模:函数、类、程序包,或跨层切片都可以。避免使用: 单元、组件、服务。
接口——调用方正确使用模块所必须了解的一切:类型签名,以及不变量、顺序约束、错误模式、必需配置和性能特征。避免使用: API、签名(范围过窄——它们只指类型层面的表面)。
实现——模块内部的内容,即其代码主体。它与适配器不同:一个东西可以是实现很大的小型适配器(Postgres 仓库),也可以是实现很小的大型适配器(内存中的伪实现)。讨论接缝时使用“适配器”;其他情况使用“实现”。
深度——接口所提供的杠杆效益:调用方(或测试)每学习单位接口所能使用的行为量。大量行为藏在简洁接口后面的模块是深层模块;接口几乎与实现一样复杂的模块是浅层模块。
接缝(Michael Feathers)——无需在某处编辑即可改变行为的位置;即模块接口所在的位置。接缝放在哪里是独立的设计决策,与接缝后面放什么不同。避免使用: 边界(该词在 DDD 中常指有界上下文,容易引起歧义)。
适配器——在接缝处满足某个接口的具体事物。它描述的是角色(填补什么位置),而不是内容(内部有什么)。
杠杆效益——调用方从深度中获得的收益:每学习单位接口即可获得更多能力。一份实现能在 N 个调用点和 M 个测试中持续带来回报。
局部性——维护者从深度中获得的收益:变更、缺陷、知识和验证集中在一处,而不是散布到各个调用方。修复一次,处处生效。
深层与浅层
深层模块 = 简洁的接口 + 大量实现:
┌─────────────────────┐
│ Small Interface │ ← Few methods, simple params
├─────────────────────┤
│ │
│ Deep Implementation│ ← Complex logic hidden
│ │
└─────────────────────┘
浅层模块 = 复杂的接口 + 少量实现(应避免):
┌─────────────────────────────────┐
│ Large Interface │ ← Many methods, complex params
├─────────────────────────────────┤
│ Thin Implementation │ ← Just passes through
└─────────────────────────────────┘
设计接口时,请思考:
- 我能减少方法数量吗?
- 我能简化参数吗?
- 我能把更多复杂性隐藏在内部吗?
原则
- 深度是接口的属性,而非实现的属性。 深层模块内部可以由小型、可模拟、可替换的部分组合而成——它们只是接口的一部分。模块既可以在实现内部设有内部接缝(仅供实现内部使用,并由自身测试使用),也可以在接口处设有外部接缝。
- 删除测试。 想象删除这个模块。如果复杂性随之消失,它就只是个传递层。如果复杂性在 N 个调用方中重新出现,它就发挥了应有的作用。
- 接口就是测试面。 调用方和测试都通过同一个接缝。如果你想越过接口进行测试,这个模块很可能形态不对。
- 一个适配器意味着假设存在接缝。两个适配器意味着接缝确实存在。 除非接缝两侧确实存在变化,否则不要引入接缝。
面向可测试性进行设计
好的接口让测试变得自然:
-
接收依赖,不要创建依赖。
// Testable function processOrder(order, paymentGateway) {} // Hard to test function processOrder(order) { const gateway = new StripeGateway(); } -
返回结果,不要产生副作用。
// Testable function calculateDiscount(cart): Discount {} // Hard to test function applyDiscount(cart): void { cart.total -= discount; } -
缩小接口表面。 方法越少,需要的测试越少。参数越少,测试设置就越简单。
关系
- 模块恰有一个接口(它向调用方和测试展示的表面)。
- 深度是模块的一个属性,以其接口为基准衡量。
- 接缝是模块的接口所在之处。
- 适配器位于接缝处,并满足接口。
- 深度为调用方带来杠杆效应,为维护者带来局部性。
不采用的表述方式
- 将深度定义为实现代码行数与接口代码行数之比(Ousterhout):这会鼓励堆砌实现代码。我们采用「以杠杆效应衡量深度」的定义。
- 将「接口」限定为 TypeScript 的
interface关键字或类的公有方法:范围太窄——这里的接口包括调用方必须了解的所有信息。 - 「边界」:这个词与 DDD 中的限界上下文含义重叠。请说接缝或接口。
深入探索
- 基于依赖关系深化集群——参见 DEEPENING.md:依赖类别、接缝规范,以及以替换而非分层的方式进行测试。
- 探索不同的接口设计方案——参见 DESIGN-IT-TWICE.md:启动多个并行子 Agent,以截然不同的方式设计接口,然后根据深度、局部性和接缝位置进行比较。