Skill Learning

codebase-design

1. Bilingual SKILL.md

左右滑动可直接切换英文版与中文版;两种语言按段落自动对齐。英文中的蓝色虚线词语可点击查看解释。共标记 15 处。

入口 skill:codebase-design左右滑动切换语言 · 段落位置自动对齐
# Codebase Design
Design ****: a lot of behaviour behind a small interface, , testable through that interface. Use this language and these principles wherever code is being designed or restructured. The aim is leverage for callers, locality for maintainers, and testability for everyone.
## Glossary
Use these terms exactly: don't substitute "component," "service," "API," or "boundary." Consistent language is the whole point.
**Module**: anything with an interface and an implementation. Deliberately : a function, class, package, or . _Avoid_: unit, component, service.
**Interface**: everything a caller must know to use the module correctly: the type signature, but also invariants, ordering constraints, error modes, required configuration, and performance characteristics. _Avoid_: API, signature (too narrow, they refer only to the ).
**Implementation**: what's inside a module, its body of code. Distinct from **Adapter**: a thing can be a small adapter with a large implementation (a Postgres repo) or a large adapter with a small implementation (an in-memory fake). when the seam is the topic; "implementation" otherwise.
**Depth**: . The amount of behaviour a caller (or test) can exercise per unit of interface they have to learn. A module is **deep** when a large amount of behaviour , **shallow** when the interface is nearly as complex as the implementation.
**Seam** _(Michael Feathers)_: a place where you can alter behaviour without editing in that place; the *location* at which a module's interface lives. Where to put the seam is its own design decision, distinct from what goes behind it. _Avoid_: boundary (overloaded with DDD's bounded context).
**Adapter**: a concrete thing that satisfies an interface at a seam. Describes *role* (what slot it fills), not substance (what's inside).
**Leverage**: what callers get from depth. More capability per unit of interface they learn. One implementation pays back across N call sites and M tests.
**Locality**: what maintainers get from depth. Change, bugs, knowledge, and verification concentrate in one place rather than spreading across callers. Fix once, fixed everywhere.
## Deep vs shallow
**Deep module** = small interface + lots of implementation:
``` ┌─────────────────────┐ │ Small Interface │ ← Few methods, simple params ├─────────────────────┤ │ │ │ Deep Implementation│ ← Complex logic hidden │ │ └─────────────────────┘ ```
**Shallow module** = large interface + little implementation (avoid):
``` ┌─────────────────────────────────┐ │ Large Interface │ ← Many methods, complex params ├─────────────────────────────────┤ │ Thin Implementation │ ← Just passes through └─────────────────────────────────┘ ```
When designing an interface, ask:
- Can I reduce the number of methods? - Can I simplify the parameters? - Can I hide more complexity inside?
## Principles
- **Depth is a property of the interface, not the implementation.** A deep module can be internally composed of small, mockable, swappable parts; they just aren't part of the interface. A module can have **internal seams** (private to its implementation, used by its own tests) as well as the **external seam** at its interface. - **The deletion test.** Imagine deleting the module. If complexity vanishes, it was a pass-through. If complexity reappears across N callers, it was . - **The interface is the test surface.** Callers and tests cross the same seam. If you want to test *past* the interface, the module is probably the wrong shape. - **One adapter means a hypothetical seam. Two adapters means a real one.** Don't introduce a seam unless .
## Designing for testability
Good interfaces make testing natural:
1. ****
```typescript // Testable function processOrder(order, paymentGateway) {} // Hard to test function processOrder(order) { const gateway = new StripeGateway(); } ```
2. **Return results, don't .**
```typescript // Testable function calculateDiscount(cart): Discount {} // Hard to test function applyDiscount(cart): void { cart.total -= discount; } ```
3. **** Fewer methods = fewer tests needed. Fewer params = simpler test setup.
## Relationships
- A **Module** has exactly one **Interface** (the surface it presents to callers and tests). - **Depth** is a property of a **Module**, measured against its **Interface**. - A **Seam** is where a **Module**'s **Interface** lives. - An **Adapter** sits at a **Seam** and satisfies the **Interface**. - **Depth** produces **Leverage** for callers and **Locality** for maintainers.
## Rejected framings
- **Depth as ratio of implementation-lines to interface-lines** (Ousterhout): rewards padding the implementation. We use depth-as-leverage instead. - **"Interface" as the TypeScript `interface` keyword or a class's public methods**: too narrow: interface here includes every fact a caller must know. - **"Boundary"**: overloaded with DDD's bounded context. Say **seam** or **interface**.
## Going deeper
- **Deepening a cluster given its dependencies**, see [DEEPENING.md](DEEPENING.md): dependency categories, seam discipline, and replace-don't-layer testing. - **Exploring alternative interfaces**, see [DESIGN-IT-TWICE.md](DESIGN-IT-TWICE.md): to design the interface several radically different ways, then compare on depth, locality, and seam placement.
# 代码库设计
设计**深模块(deep modules)**:在一个小接口背后承载大量行为,把它放在清晰的接缝处,并且能够通过该接口进行测试。凡是设计或重构代码,都使用这套语言与原则。目标是让调用者获得杠杆效应,让维护者获得局部性,并让所有人都更容易测试。
## 术语表
严格使用这些术语:不要用“component”“service”“API”或“boundary”替代。统一语言正是这一套词汇的全部意义。
**模块(Module)**:任何拥有接口和实现的东西。它刻意不限定规模:可以是函数、类、包,也可以是贯穿多个层级的切片。_避免使用_:unit、component、service。
**接口(Interface)**:调用者为正确使用模块而必须知道的一切:不仅是类型签名,还包括不变量、顺序约束、错误模式、必要配置和性能特征。_避免使用_:API、signature(两者都太窄,只指类型层面的表面)。
**实现(Implementation)**:模块内部的东西,也就是它的代码主体。它不同于**适配器(Adapter)**:一个东西可能是实现很大的小适配器(如 Postgres repo),也可能是实现很小的大适配器(如内存 fake)。讨论接缝时用“adapter”,其他场景用“implementation”。
**深度(Depth)**:接口所提供的杠杆效应。也就是调用者(或测试)每学习一单位接口,能够驱动多少行为。大量行为藏在小接口背后的模块是**深的**;接口几乎与实现一样复杂的模块是**浅的**。
**接缝(Seam)**(Michael Feathers):无需在某处直接编辑,就能改变该处行为的位置;也就是模块接口所在的*位置*。接缝放在哪里,本身就是独立的设计决策,与接缝背后放什么不同。_避免使用_:boundary(它与 DDD 的 bounded context 含义重叠)。
**适配器(Adapter)**:在某个接缝处满足接口的具体对象。它描述的是*角色*(填补哪个插槽),而不是实质(内部是什么)。
**杠杆效应(Leverage)**:调用者从深度中得到的东西。每学习一单位接口,就获得更多能力。一个实现能够在 N 个调用点和 M 个测试中反复回报投入。
**局部性(Locality)**:维护者从深度中得到的东西。变更、缺陷、知识和验证集中在一个位置,而不是扩散到各个调用者。修一次,处处都修好。
## 深模块与浅模块
**深模块** = 小接口 + 大量实现:
``` ┌─────────────────────┐ │ 小接口 │ ← 方法少、参数简单 ├─────────────────────┤ │ │ │ 深实现 │ ← 复杂逻辑被隐藏 │ │ └─────────────────────┘ ```
**浅模块** = 大接口 + 少量实现(应避免):
``` ┌─────────────────────────────────┐ │ 大接口 │ ← 方法多、参数复杂 ├─────────────────────────────────┤ │ 薄实现 │ ← 只是原样转发 └─────────────────────────────────┘ ```
设计接口时,问自己:
- 我能减少方法数量吗? - 我能简化参数吗? - 我能把更多复杂性藏到内部吗?
## 原则
- **深度是接口的属性,而不是实现的属性。** 深模块内部可以由小型、可 mock、可替换的部件组成;只是这些部件不属于接口。模块既可以有**内部接缝**(对实现私有,供自身测试使用),也可以在接口处有**外部接缝**。 - **删除测试。** 想象把模块删除。如果复杂性也消失了,它只是原样转发;如果复杂性重新散落到 N 个调用者中,它就在发挥应有价值。 - **接口就是测试表面。** 调用者和测试跨越同一条接缝。如果你想测试到接口*后面*,模块的形状很可能不对。 - **一个适配器只说明假设中的接缝;两个适配器才说明真实接缝。** 除非确实有东西会跨接缝变化,否则不要引入接缝。
## 为可测试性而设计
好的接口让测试自然发生:
1. **接收依赖,不要创建依赖。**
```typescript // 易于测试 function processOrder(order, paymentGateway) {} // 难以测试 function processOrder(order) { const gateway = new StripeGateway(); } ```
2. **返回结果,不要制造副作用。**
```typescript // 易于测试 function calculateDiscount(cart): Discount {} // 难以测试 function applyDiscount(cart): void { cart.total -= discount; } ```
3. **小表面积。** 方法越少,需要的测试越少;参数越少,测试准备越简单。
## 关系
- 一个**模块**恰好拥有一个**接口**(它向调用者和测试呈现的表面)。 - **深度**是**模块**的属性,以其**接口**为参照进行衡量。 - **接缝**是**模块**的**接口**所在的位置。 - **适配器**位于**接缝**处,并满足该**接口**。 - **深度**为调用者产生**杠杆效应**,为维护者产生**局部性**。
## 被拒绝的表述框架
- **把深度定义为实现代码行与接口代码行之比**(Ousterhout):这会奖励给实现灌水。这里改用“深度即杠杆效应”。 - **把“Interface”理解为 TypeScript 的 `interface` 关键字或类的 public 方法**:太窄。这里的接口包括调用者必须知道的每一项事实。 - **“Boundary”**:它与 DDD 的 bounded context 含义重叠。应说**接缝(seam)**或**接口(interface)**。
## 继续深入
- **根据依赖加深一个集群**,参见 [DEEPENING.md](DEEPENING.md):依赖分类、接缝纪律,以及“替换而非叠加”的测试方式。 - **探索备选接口**,参见 [DESIGN-IT-TWICE.md](DESIGN-IT-TWICE.md):启动多个并行子代理,用若干种截然不同的方式设计接口,再从深度、局部性和接缝位置三个方面比较。

Entry SHA-256: 2c20617f87ec8af6a434859f381b2f061a69b530444e74eb39e78bb016a6d1e2

2. Why This Skill Is Clever

一句话核心机制

把“深度”重新定义为单位接口知识能够驱动多少行为,再用 seam、adapter、leverage 与 locality 这组固定词汇,把接口设计、可测试性和维护成本放进同一个判断框架。

触发与边界

直接来源: 当任务涉及设计或改进模块接口、寻找加深机会、决定 seam 的位置、提升可测试性或 AI 可导航性时使用。它不是一条从输入执行到产出的工作流,而是一套供其他设计活动调用的共享词汇与判断准则。

边界: 文中明确要求不要把 Module、Interface、Seam 随意替换成 component、API 或 boundary。这个限制不是字眼洁癖,而是为了避免不同团队成员用相同词指不同概念。spin up parallel sub-agents 则属于特定 agent harness 的能力假设,移植到普通开发流程时可以替换为多人并行设计或两轮独立草案。

信息架构 / 执行顺序

  1. 先固定词义。 Glossary 先定义 Module、Interface、Implementation、Depth、Seam、Adapter、Leverage、Locality。
  2. 再建立视觉模型。 Deep vs shallow 用两张字符图,把“小接口 + 深实现”与“大接口 + 薄实现”直接对照。
  3. 然后给出判定规则。 deletion test、test surface 与“两适配器才是真接缝”把抽象判断变成可操作检查。
  4. 最后落到测试与扩展。 依赖注入、返回结果、小表面积让原则进入代码;两个 companion 文档继续处理依赖集群与备选接口设计。

最巧妙的设计点

1. 深度不是代码量,而是调用者获得的杠杆

“Depth: leverage at the interface.”

直接来源: 深度按“调用者每学习一单位接口,可以驱动多少行为”衡量。

解读: 这避开了“实现行数 / 接口行数”这种容易被灌水操纵的指标。设计目标从“把实现做大”转成“让调用者少学而多得”,因此同时连接了易用性、复用和测试成本。

2. 把 seam 的位置与模块内容拆成两个决策

“Where to put the seam is its own design decision”

直接来源: Seam 是模块接口所在的位置;放在哪里与背后实现什么不同。

解读: 很多设计讨论把“需要一个抽象”与“抽象放在哪”混成一件事。这项区分迫使设计者分别回答变化在哪里发生、调用者需要知道什么,从源头减少错误分层。

3. 删除测试让抽象价值可证伪

“If complexity vanishes, it was a pass-through.”

直接来源: 删除模块后,如果复杂性消失,它只是转发;如果复杂性重新散落到 N 个调用者,模块就在发挥价值。

解读: 这是一个反事实测试。它不靠命名或架构图判断“抽象是否优雅”,而是观察删除后复杂性去了哪里,能有效识别 middle-man 与无价值 wrapper。

4. 测试不应偷偷穿过接口

“The interface is the test surface.”

直接来源: 调用者和测试都跨越同一 seam;如果测试想越过接口,模块形状可能不对。

解读: 这把测试困难视为接口反馈,而不是让测试无限增加后门。它也保护内部实现可以自由重构,避免测试与私有结构绑定。

5. 用真实变化约束过早抽象

“Two adapters means a real one.”

直接来源: 一个 adapter 只支持假设中的 seam;两个 adapter 才证明差异真实存在。

解读: 这是一条简单而强的证据门槛。它不会禁止预留变化点,但会要求为 seam 的成本提供现实变化证据,从而压低 speculative generality。

防失败机制与 trade-off

明确不变量(直接来源):

防失败设计: Rejected framings 专门封堵三种常见漂移:用代码行比例计算深度、把 interface 缩窄为语言关键字、以及用 overloaded 的 boundary 替代 seam。关系清单随后把术语重新连成一个小型概念模型,降低各定义彼此矛盾的风险。

Trade-off(解读): “Use these terms exactly” 提高了团队内一致性,却降低了与既有 DDD、Hexagonal Architecture 或框架术语直接对接的便利。一个真实 adapter 的项目仍可能出于测试、合规或进程隔离需要 seam,因此“两 adapter”更适合作为强默认值,而非脱离上下文的绝对定律。

可迁移原则

  1. 先定义单位,再定义指标。 如果要评价“好接口”,先说明 Interface 包含哪些知识。
  2. 用删除后的复杂性流向评估抽象。 价值不在抽象名字,而在它是否集中规则。
  3. 让生产调用与测试走同一个入口。 不为测试暴露第二套私有协议。
  4. 给变化点设置证据门槛。 至少看到两个真实实现或两种真实行为,再支付 seam 的长期成本。
  5. 为术语列出 rejected framings。 正面定义告诉读者是什么,反例负责阻止概念慢慢漂移。

今日实践题

选一个你熟悉的 helper 或 service:如果今天删掉它,复杂性会消失,还是会重新散落到多个调用点?这个结果说明它是深模块、浅转发,还是 seam 放错了位置?

Evidence note

Source & provenance
Repository path
skills/engineering/codebase-design/SKILL.md
Generated
2026-09-01 08:22 Asia/Shanghai
Upstream commit
6654f6b60cd9d5be8b54c6fafe44346dabeb3b76
Source
View on GitHub