Codebase Design
设计 deep module:大量行为藏在小 interface 后面,放在干净的 seam 上,通过该 interface 可测试。在设计与重构代码的任何地方使用这套语言与原则。目标是 caller 的 leverage、维护者的 locality、所有人的 testability。
Glossary
精确使用这些术语 — 不要用 "component"、"service"、"API" 或 "boundary" 替代。一致语言就是重点。
Module — 有 interface 和 implementation 的任何东西。刻意与规模无关:function、class、package,或跨 tier 的 slice。避免:unit、component、service。
Interface — caller 要正确使用 module 必须知道的一切:type signature,还有 invariant、ordering constraint、error mode、required configuration、performance characteristic。避免:API、signature(太窄 — 仅指 type-level surface)。
Implementation — module 内部,其代码体。与 Adapter 不同:某物可以是小 adapter 配大 implementation(Postgres repo),或大 adapter 配小 implementation(in-memory fake)。seam 是话题时用 "adapter";否则用 "implementation"。
Depth — interface 处的 leverage:caller(或 test)每学一单位 interface 能 exercise 多少行为。Deep module 是大量行为在小 interface 后,shallow 是 interface 几乎与 implementation 一样复杂。
Seam (Michael Feathers) — 可在此 alter 行为而无需就地编辑的位置;module interface 所在的 位置。seam 放哪是独立设计决定,与 seam 后放什么不同。避免:boundary(与 DDD bounded context 过载)。
Adapter — 在 seam 处 satisfy interface 的具体事物。描述 角色(填哪个 slot),非 实质(里面是什么)。
Leverage — caller 从 depth 得到什么:每学一单位 interface 获得更多能力。一份 implementation 在 N 个 call site 和 M 个 test 上回报。
Locality — 维护者从 depth 得到什么:change、bug、knowledge、verification 集中一处而非 spread 到 caller。修一次,处处修好。
Deep vs shallow
Deep module = 小 interface + 大量 implementation:
┌─────────────────────┐
│ Small Interface │ ← Few methods, simple params
├─────────────────────┤
│ │
│ Deep Implementation│ ← Complex logic hidden
│ │
└─────────────────────┘
Shallow module = 大 interface + 少 implementation(避免):
┌─────────────────────────────────┐
│ Large Interface │ ← Many methods, complex params
├─────────────────────────────────┤
│ Thin Implementation │ ← Just passes through
└─────────────────────────────────┘
设计 interface 时问:
- 能否减少 method 数量?
- 能否简化 parameter?
- 能否在内部藏更多 complexity?
Principles
- Depth 是 interface 的属性,不是 implementation 的。 deep module 内部可由小、可 mock、可替换部分组成 — 它们只是 interface 的一部分。module 可有 internal seam(implementation 私有,供自身 test 用)以及 interface 处的 external seam。
- Deletion test。 想象删除该 module。若 complexity 消失,它是 pass-through。若 complexity 在 N 个 caller 重现,它在 earn keep。
- Interface 就是 test surface。 caller 和 test 过同一 seam。若想 test 穿过 interface,module 形状可能不对。
- One adapter 意味着 hypothetical seam。Two adapters 意味着 real seam。 除非 seam 上真有东西在变,否则不要引入 seam。
Designing for testability
好 interface 让 testing 自然:
- Accept dependencies,不要 create 它们。
```typescript
// Testable
function processOrder(order, paymentGateway) {}
// Hard to test
function processOrder(order) {
const gateway = new StripeGateway();
}
```
- Return results,不要 produce side effects。
```typescript
// Testable
function calculateDiscount(cart): Discount {}
// Hard to test
function applyDiscount(cart): void {
cart.total -= discount;
}
```
- Small surface area。 更少 method = 更少 test。更少 param = 更简单 test setup。
Relationships
- 一个 Module 恰好有一个 Interface(呈现给 caller 和 test 的 surface)。
- Depth 是 Module 的属性,相对其 Interface 衡量。
- Seam 是 Module 的 Interface 所在处。
- Adapter 坐在 Seam 上 satisfy Interface。
- Depth 为 caller 产生 Leverage,为 maintainer 产生 Locality。
Rejected framings
- Depth 作为 implementation 行数与 interface 行数之比(Ousterhout):奖励 padding implementation。我们用 depth-as-leverage。
- "Interface" 指 TypeScript
interface关键字或 class 的 public method:太窄 — 此处 interface 包含 caller 必须知道的一切事实。 - "Boundary":与 DDD bounded context 过载。说 seam 或 interface。
Going deeper
- 在给定依赖下加深一个 cluster — 见 DEEPENING.md:依赖类别、seam discipline、replace-don't-layer testing。
- 探索 alternative interface — 见 DESIGN-IT-TWICE.md:并行 sub-agent 以多种 radically 不同方式设计 interface,再按 depth、locality、seam placement 比较。