返回 Skills
vinvcn/mattpocock-skills-zh-cn· MIT 内容可用

codebase-design

用于设计深模块的共享词汇。适用于用户想设计或改进模块接口、寻找深化机会、决定 seam 放在哪里、让代码更容易测试或更适合 AI 导航,或其他技能需要深模块词汇时。

安装

与 skills.sh 相同的 Command / Prompt 安装方式


name: codebase-design description: 用于设计深模块的共享词汇。适用于用户想设计或改进模块接口、寻找深化机会、决定 seam 放在哪里、让代码更容易测试或更适合 AI 导航,或其他技能需要深模块词汇时。

Codebase Design

设计 deep modules:把大量行为放在小 interface 之后,把 interface 放在清晰 seam 上,并通过该 interface 测试。凡是在设计或重构代码时,都使用这套语言和原则。目标是给 callers 带来 leverage,给 maintainers 带来 locality,并让每个人都更容易测试。

Glossary

准确使用这些术语,不要替换成 "component"、"service"、"API" 或 "boundary"。一致语言就是重点。

Module - 任何拥有 interface 和 implementation 的东西。它故意不限定尺度:function、class、package,或跨层 slice 都可以。Avoid: unit, component, service.

Interface - caller 为了正确使用 module 必须知道的一切:type signature,以及 invariants、ordering constraints、error modes、required configuration 和 performance characteristics。Avoid: 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,就能触达多少行为。大量行为藏在小 interface 后面时,module 是 deep;interface 几乎和 implementation 一样复杂时,module 是 shallow

Seam(Michael Feathers)- 你可以在不编辑当前位置的情况下改变行为的地方;也就是 module 的 interface 所在的 location。seam 放在哪里是独立设计决策,不同于 seam 后面放什么。Avoid: boundary(它和 DDD bounded context 过载)。

Adapter - 在 seam 上满足某个 interface 的具体东西。描述的是 role(填哪个槽位),不是 substance(内部是什么)。

Leverage - callers 从 depth 获得的收益:每学习一单位 interface,就得到更多能力。一个 implementation 会在 N 个 call sites 和 M 个 tests 中回本。

Locality - maintainers 从 depth 获得的收益:change、bugs、knowledge 和 verification 集中在一个地方,而不是散到 callers 里。修一次,到处都修好。

Deep vs shallow

Deep module = small interface + lots of implementation:

+------------------+
| Small Interface  | -> few methods, simple params
+------------------+
|                  |
| Deep             | -> complex logic hidden
| Implementation   |
|                  |
+------------------+

Shallow module = large interface + little implementation(避免):

+-------------------------------+
| Large Interface               | -> many methods, complex params
+-------------------------------+
| Thin Implementation           | -> mostly pass-through
+-------------------------------+

设计 interface 时问:

  • 我能减少 methods 数量吗?
  • 我能简化 parameters 吗?
  • 我能把更多复杂度藏到内部吗?

Principles

  • Depth 是 interface 的属性,不是 implementation 的属性。 Deep module 内部可以由小的、mockable、swappable parts 组成,只是它们不属于 interface。一个 module 可以同时拥有 internal seams(implementation 私有,供自身 tests 使用)和位于 interface 的 external seam
  • Deletion test。 想象删除这个 module。如果复杂度消失了,它只是 pass-through。如果复杂度重新散落到 N 个 callers 里,它就在发挥价值。
  • Interface is the test surface。 Callers 和 tests 穿过同一个 seam。若你想测试 interface 之后的内部细节,这个 module 形状可能不对。
  • One adapter means a hypothetical seam. Two adapters means a real one. 除非确实有东西会跨 seam 变化,否则不要引入 seam。

Designing for testability

好的 interfaces 让测试自然发生:

  1. Accept dependencies, don't create them.

    // Testable
    function processOrder(order, paymentGateway) {}
    
    // Hard to test
    function processOrder(order) {
      const gateway = new StripeGateway();
    }
    
  2. Return results, don't produce side effects.

    // Testable
    function calculateDiscount(cart): Discount {}
    
    // Hard to test
    function applyDiscount(cart): void {
      cart.total -= discount;
    }
    
  3. Small surface area. 更少 methods = 需要更少 tests。更少 params = 更简单的 test setup。

Relationships

  • 一个 Module 恰好有一个 Interface(它呈现给 callers 和 tests 的 surface)。
  • DepthModule 的属性,并以其 Interface 衡量。
  • SeamModuleInterface 所在的位置。
  • Adapter 位于 Seam 上,并满足 Interface
  • Depth 为 callers 产生 Leverage,为 maintainers 产生 Locality

Rejected framings

  • 把 depth 当作 implementation-lines 与 interface-lines 的比例(Ousterhout):这会奖励 padding implementation。这里使用 depth-as-leverage。
  • 把 "Interface" 理解为 TypeScript interface keyword 或 class public methods:太窄;这里的 interface 包括 caller 必须知道的所有事实。
  • "Boundary":与 DDD bounded context 过载。说 seaminterface

Going deeper

  • Deepening a cluster given its dependencies - 见 DEEPENING.md:dependency categories、seam discipline 和 replace-don't-layer testing。
  • Exploring alternative interfaces - 见 DESIGN-IT-TWICE.md:启动并行 sub-agents,用几种截然不同的方式设计 interface,再按 depth、locality 和 seam placement 比较。

附带文件

DEEPENING.md
# Deepening

在已知 dependencies 的情况下,安全地深化一组 shallow modules。本文件假设你已经使用 [SKILL.md](SKILL.md) 中的词汇:**module**、**interface**、**seam**、**adapter**。

## Dependency categories

评估 deepening candidate 时,先分类它的 dependencies。分类决定 deepened module 如何跨 seam 测试。

### 1. In-process

纯计算、内存状态、无 I/O。总是可以 deepen:合并 modules,并直接通过新的 interface 测试。不需要 adapter。

### 2. Local-substitutable

有本地 test stand-ins 的 dependencies(例如 Postgres 的 PGLite、in-memory filesystem)。如果 stand-in 存在,就可以 deepen。Deepened module 在 test suite 中带着 stand-in 一起测试。seam 是 internal 的;module external interface 上不需要 port。

### 3. Remote but owned (Ports & Adapters)

你拥有的跨网络服务(microservices、internal APIs)。在 seam 上定义 **port**(interface)。Deep module 拥有 logic;transport 作为 **adapter** 注入。Tests 使用 in-memory adapter。Production 使用 HTTP/gRPC/queue adapter。

推荐形状:*"Define a port at the seam, implement an HTTP adapter for production and an in-memory adapter for testing, so the logic sits in one deep module even though it's deployed across a network."*

### 4. True external (Mock)

你无法控制的第三方服务(Stripe、Twilio 等)。Deepened module 把外部 dependency 作为 injected port;tests 提供 mock adapter。

## Seam discipline

- **One adapter means a hypothetical seam. Two adapters means a real one.** 除非至少有两个 adapters 合理存在(通常是 production + test),否则不要引入 port。单 adapter seam 只是 indirection。
- **Internal seams vs external seams。** Deep module 可以有 internal seams(implementation 私有,供自身 tests 使用),也可以有 interface 上的 external seam。不要只因为 tests 使用 internal seams,就把它们暴露到 interface。

## Testing strategy: replace, don't layer

- 一旦 deepened module interface 上有了 tests,旧的 shallow modules unit tests 就变成 waste,删除它们。
- 在 deepened module 的 interface 上写新 tests。**Interface is the test surface**。
- Tests 通过 interface 断言 observable outcomes,而不是 internal state。
- Tests 应能承受 internal refactors;它们描述 behavior,不描述 implementation。若 implementation 改动会迫使 test 改动,那就是在越过 interface 测试。
DESIGN-IT-TWICE.md
# Design It Twice

当用户想为某个 deepening candidate 探索 alternative interfaces 时,使用这个并行 sub-agent pattern。它基于 Ousterhout 的 "Design It Twice":你的第一个想法很可能不是最好的。

使用 [SKILL.md](SKILL.md) 中的词汇:**module**、**interface**、**seam**、**adapter**、**leverage**。

## Process

### 1. Frame the problem space

在启动 sub-agents 之前,先为选中的 candidate 写一段面向用户的问题空间说明:

- 新 interface 需要满足的 constraints
- 它会依赖哪些 dependencies,以及这些 dependencies 属于哪一类(见 [DEEPENING.md](DEEPENING.md))
- 一个粗略的 illustrative code sketch,用来让 constraints 具体化;这不是 proposal,只是帮助理解约束

把这些展示给用户,然后立即进入 Step 2。用户可以一边读一边思考,sub-agents 同时并行工作。

### 2. Spawn sub-agents

使用 Agent tool 并行启动 3+ 个 sub-agents。每个都必须为 deepened module 产出一个 **radically different** interface。

给每个 sub-agent 单独的 technical brief(file paths、coupling details、来自 [DEEPENING.md](DEEPENING.md) 的 dependency category、seam 后面是什么)。这个 brief 独立于 Step 1 的用户-facing problem-space explanation。给每个 agent 不同的 design constraint:

- Agent 1: "Minimize the interface - aim for 1-2 entry points max. Maximise leverage per entry point."
- Agent 2: "Maximise flexibility - support many use cases and extension."
- Agent 3: "Optimise for the most common caller - make the default case trivial."
- Agent 4(如适用): "Design around ports & adapters for cross-seam dependencies."

Brief 中同时包含 [SKILL.md](SKILL.md) vocabulary 和 `CONTEXT.md` vocabulary,确保每个 sub-agent 的命名同时符合 architecture language 和项目 domain language。

每个 sub-agent 输出:

1. Interface(types、methods、params,以及 invariants、ordering、error modes)
2. Usage example,展示 callers 如何使用它
3. Implementation 在 seam 后隐藏了什么
4. Dependency strategy 和 adapters(见 [DEEPENING.md](DEEPENING.md))
5. Trade-offs:leverage 哪里高,哪里薄

### 3. Present and compare

顺序展示各个 designs,让用户能逐个吸收,然后用 prose 比较。按 **depth**(interface 上的 leverage)、**locality**(change 集中在哪里)和 **seam placement** 对比。

比较后,给出你的推荐:你认为哪个 design 最强,以及为什么。如果不同 designs 的元素可以组合,提出 hybrid。要有观点;用户需要强判断,不是菜单。