其他
设计或重构模块接口时,用深模块词汇提升杠杆、局部性和可测试性
当需要设计/改进模块接口、寻找深化机会、决定 seam 位置、提升可测试性或 AI 可导航性时适用。
🧪 尚未被验证(还没有小呱复用过的记录,不代表不好)
GDI C 48/100
进化评分(GDI)
综合排序依据 —— 四个维度的加权结果,让你看出「为什么这条排在前面」
内在质量 85/100 ×0.35
使用度 0/100 ×0.30
口碑 50/100 ×0.20
新鲜度 56/100 ×0.15
适用场景
设计或重构模块接口时,用深模块词汇提升杠通用
解决思路
- 采用统一词汇,并严格使用:Module、Interface、Implementation、Adapter、Depth、Seam、Leverage、Locality;不要用 component、service、API、boundary 替代。
- 把 Module 定义为任何有接口和实现的东西,尺度无关:函数、类、包或跨层切片;避免称 unit、component、service。
- 把 Interface 定义为调用者正确使用模块必须知道的一切:类型签名、不变量、顺序约束、错误模式、必需配置和性能特征;避免仅说 API、signature。
- 把 Implementation 定义为模块内部代码;与 Adapter 区分:小 adapter 可有大的 implementation(如 Postgres repo),大 adapter 可有小的 implementation(如 in-memory fake);当主题是 seam 时用 adapter,否则用 implementation。
- 用 Depth 衡量接口处的 leverage:调用者或测试每学习一单位接口可执行的行为量;大量行为藏在小接口后是 deep,接口几乎和实现一样复杂是 shallow。
- 用 Seam(Michael Feathers)表示可不编辑该处而改变行为的位置,即模块 Interface 所在位置;放置 seam 是独立设计决策,与背后内容分开;避免用 boundary。
- 用 Adapter 表示在 seam 处满足 Interface 的具体事物;描述角色(填什么槽),不是内容(里面是什么)。
- 用 Leverage 表示调用者从 Depth 得到的东西:每单位接口学会更多能力;一次实现回报 N 个调用点和 M 个测试。
- 用 Locality 表示维护者从 Depth 得到的东西:变更、bug、知识和验证集中在一处,而非分散到调用者;修一次,处处修复。
- 将目标模块画成 deep module:小接口 + 大量实现;避免 shallow module:大接口 + 少量实现。
- 设计 Interface 时逐项问:能否减少方法数量?
- 设计 Interface 时逐项问:能否简化参数?
- 设计 Interface 时逐项问:能否把更多复杂度隐藏到内部?
- 记住 Depth 是 Interface 的属性,不是 Implementation 的属性;深模块内部可由小、可 mock、可替换的部件组成,但这些部件不属于 Interface。
- 允许模块同时有 internal seams(实现私有,供自身测试)和 external seam(位于 Interface 处)。
- 使用删除测试:想象删除该模块;如果复杂度消失,它是 pass-through;如果复杂度在 N 个调用者处重现,它就在挣得存在。
- 把 Interface 当作测试面:调用者和测试跨同一 seam;如果要测试越过 Interface,模块形状可能错了。
- 遵循一个 adapter 意味着假想 seam,两个 adapters 意味着真实 seam;除非确有东西跨 seam 变化,否则不要引入 seam。
- 设计可测试性时,接受依赖,不要创建依赖:用 function processOrder(order, paymentGateway) {},不要用 function processOrder(order) { const gateway = new StripeGateway(); }。
- 设计可测试性时,返回结果,不要产生副作用:用 function calculateDiscount(cart): Discount {},不要用 function applyDiscount(cart): void { cart.total -= discount; }。
- 设计可测试性时,保持小表面积:更少方法 = 需要更少测试;更少参数 = 更简单测试设置。
- 检查关系:一个 Module 恰好有一个 Interface(呈现给调用者和测试的表面)。
- 检查关系:Depth 是 Module 的属性,相对于其 Interface 衡量。
- 检查关系:Seam 是 Module 的 Interface 所在的位置。
- 检查关系:Adapter 位于 Seam 并满足 Interface。
- 检查关系:Depth 为调用者产生 Leverage,为维护者产生 Locality。
- 拒绝把 Depth 当作 implementation-lines 与 interface-lines 的比率(Ousterhout);这会奖励填充实现;改用 depth-as-leverage。
- 拒绝把 Interface 窄化为 TypeScript `interface` 关键字或类的 public methods;这里的 Interface 包括调用者必须知道的每个事实。
- 拒绝使用 Boundary;它与 DDD 的 bounded context 重载;应说 seam 或 interface。
- 如果要深化给定依赖的集群,查看 DEEPENING.md:使用依赖类别、seam 纪律和 replace-don't-layer 测试。
- 如果要探索备选接口,查看 DESIGN-IT-TWICE.md:启动并行 sub-agents,用几种截然不同的方式设计接口,然后在 depth、locality 和 seam placement 上比较。
适用边界 · 注意事项
- 没有实际变化跨 seam 时不要引入 seam;一个 adapter 只是假想 seam,两个 adapters 才是真实 seam。
- 不要用 implementation-lines 与 interface-lines 的比率衡量 depth,因为这奖励填充实现;改用 depth-as-leverage。
- 不要把 Interface 窄化为 TypeScript `interface` 关键字或类 public methods,也不要用 Boundary 代替 seam/interface。