# Codebase Design

> 提供深模块设计的共享术语。当用户想设计或改进模块接口、找深化机会、决定接口放哪、让代码更可测或更便于 AI 导航，或别的 skill 需要这套深模块术语时使用。

- Skill: `cislunarspace/codebase-design` (Agent Skill, multi-file: 3 files)
- Install (CLI): `npx skillmds@latest add cislunarspace/codebase-design`
- Raw SKILL.md: https://api.skillmd.com/api/skills/cislunarspace/codebase-design/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: cislunarspace (https://skillmd.com/u/cislunarspace)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/cislunarspace/codebase-design

---


# 代码库设计

设计**深模块**：小接口背后藏大量行为，接口位置干净，可通过该接口测试。凡是在设计或重构代码的地方，都用这套语言和这些原则。目标是给调用方杠杆、给维护者局部性、给所有人可测性。

## 术语表

严格使用这些术语——不要替换成"组件"、"服务"、"API"或"边界"。统一的语言就是全部意义所在。

**模块（module）** —— 任何有接口和实现的东西。刻意不分规模：一个函数、一个类、一个包，或一条横跨多层的切片。_避免_：单元、组件、服务。

**接口（interface）** —— 调用方要正确使用该模块所必须知道的一切：类型签名，也包括不变量、顺序约束、错误模式、必需的配置，以及性能特征；同时也是模块与外界相接、两侧可独立变化的那道边界（Michael Feathers 的 seam 就是它，不必另起一名）。接口放哪里、对外暴露多宽，本身就是一项独立的设计决策，与接口背后放什么分开。_避免_：API、签名（太窄——只指类型层面的那一面）、接缝/缝/seam（就是接口）、边界（与 DDD 的限界上下文撞了）。

**实现（implementation）** —— 模块里面的东西，它的代码体。与**适配器**区分开：一个东西可以是小适配器 + 大实现（一个 Postgres 仓库），也可以是大适配器 + 小实现（一个内存 fake）。接口位置是话题时用"适配器"，否则用"实现"。

**深度（depth）** —— 接口处的杠杆：调用方（或测试）每学一个单位的接口，能驱动多少行为。一个模块是**深**的，当大量行为藏在一个小接口背后；是**浅**的，当接口几乎和实现一样复杂。

**适配器（adapter）** —— 在接口处满足某个接口的具体物。描述的是*角色*（填哪个坑），不是内容（里面是什么）。

**杠杆（leverage）** —— 调用方从深度里得到的东西：每学一个单位的接口，能拿到更多能力。一份实现，在 N 个调用点和 M 个测试里都还回来了。

**局部性（locality）** —— 维护者从深度里得到的东西：变更、bug、知识与验证集中在一处，而不是散落到各个调用方。修一次，处处都修好。

## 深与浅

**深模块** = 小接口 + 大量实现：

```
┌─────────────────────┐
│   Small Interface   │  ← Few methods, simple params
├─────────────────────┤
│                     │
│  Deep Implementation│  ← Complex logic hidden
│                     │
└─────────────────────┘
```

**浅模块** = 大接口 + 少量实现（要避免）：

```
┌─────────────────────────────────┐
│       Large Interface           │  ← Many methods, complex params
├─────────────────────────────────┤
│  Thin Implementation            │  ← Just passes through
└─────────────────────────────────┘
```

设计接口时问自己：

- 能不能减少方法数？
- 能不能简化参数？
- 能不能把更多复杂度藏到里面？

## 原则

- **深度是接口的属性，不是实现的属性。** 一个深模块内部可以由小的、可 mock 的、可替换的部件组成——它们只是不暴露在接口上。一个模块可以有**内部接口**（对其实现私有、给它自己的测试用），也可以有**对外接口**。
- **假想删除法。** 想象删掉这个模块。如果复杂度消失了，它就是个透传层。如果复杂度在 N 个调用方那里重新出现，那它是在扛活的。
- **接口就是测试面。** 调用方和测试穿过的是同一个接口。要是你想测到接口*背后*，这个模块大概形状不对。
- **一个适配器意味着一个假想的接口；两个适配器才意味着真的接口。** 没有什么真的跨接口变化，就不要引入新接口。

## 为可测性而设计

好的接口让测试变得自然：

1. **接收依赖，不要自己造依赖。**

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

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

2. **返回结果，不要产生副作用。**

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

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

3. **表面要小。** 方法少 = 需要的测试少。参数少 = 测试准备简单。

## 关系

- 一个**模块**恰好有一个**接口**（它呈现给调用方和测试的那一面）。
- **深度**是一个**模块**的属性，对着它的**接口**来衡量。
- 一个**适配器**坐在**接口**处，满足该**接口**。
- **深度**给调用方产出**杠杆**，给维护者产出**局部性**。

## 不采纳的提法

- **把深度定义为实现行数与接口行数之比**（Ousterhout）：会奖励往实现里灌水。我们改用"深度即杠杆"。
- **把"接口"等同于 TypeScript 的 `interface` 关键字或某个类的公开方法**：太窄——这里的接口包含调用方必须知道的每一个事实。
- **"边界"**：和 DDD 的限界上下文撞了。说**接口**。
- **接缝/缝（seam）**：与接口同义，统一说**接口**，不为位置另造一词。

## 更深一层

- **给定依赖后深化一个模块簇** —— 见 [DEEPENING.md](DEEPENING.md)：依赖分类、接口纪律、"替换而非叠加"的测试策略。
- **探索多个候选接口** —— 见 [DESIGN-IT-TWICE.md](DESIGN-IT-TWICE.md)：起几个并行子代理，用截然不同的方式设计同一个接口，再从深度、局部性、接口位置上对比。

