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 自然:

  1. Accept dependencies,不要 create 它们。

```typescript
// Testable
function processOrder(order, paymentGateway) {}

// Hard to test
function processOrder(order) {
const gateway = new StripeGateway();
}
```

  1. Return results,不要 produce side effects。

```typescript
// Testable
function calculateDiscount(cart): Discount {}

// Hard to test
function applyDiscount(cart): void {
cart.total -= discount;
}
```

  1. Small surface area。 更少 method = 更少 test。更少 param = 更简单 test setup。

Relationships

  • 一个 Module 恰好有一个 Interface(呈现给 caller 和 test 的 surface)。
  • DepthModule 的属性,相对其 Interface 衡量。
  • SeamModuleInterface 所在处。
  • 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 过载。说 seaminterface

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 比较。