用途:为设计或重构代码提供一套统一的语言与原则,目标是让调用方获得杠杆(leverage)、让维护者获得局地性(locality),并让所有人获得可测试性。涉及设计或改进模块接口、寻找加深机会、决定接缝位置时可使用。
术语(须严格照用,不要用 component、service、API、boundary 替代):Module 指任何带接口与实现的单元,刻意不限定规模,可为函数、类、包或跨层切片,避免说 unit、component、service。Interface 指调用方正确使用模块所需知道的一切,包括类型签名、不变量、顺序约束、错误模式、必需配置与性能特征,避免说 API、signature。Implementation 指模块内部代码体;当话题是接缝时用 Adapter,否则用 implementation。Depth 指接口处的杠杆,即调用方(或测试)每学习一单位接口能触发的行为量,行为多而接口小为深,接口复杂度接近实现为浅。Seam 源自 Michael Feathers,指无需在该处编辑即可改变行为的位置,即模块接口所在之处,避免说 boundary。Adapter 指在接缝处满足接口的具体物,描述角色而非内容。Leverage 是调用方从深度获得的收益;Locality 是维护者从深度获得的收益:变更、缺陷、知识与验证集中一处,修一处即处处修复。
主要能力与原则:深模块 = 小接口 + 大量实现;浅模块 = 大接口 + 极少实现,应避免。设计接口时问:能否减少方法数、简化参数、隐藏更多复杂度。深度是接口属性而非实现属性,深模块内部可由小的、可 mock、可替换的部分组成,只是它们不属于接口;模块可有内部接缝(实现私有,供自身测试用)以及接口处的外部接缝。删除测试:假想删除该模块,若复杂度消失说明只是直通,若复杂度在 N 个调用方处重现说明它在创造价值。接口即测试面:调用方与测试跨同一接缝;若想绕过接口测试,模块形状可能不对。一个 adapter 只是假想接缝,两个 adapter 才是真实接缝,不要在没有实际变化时引入接缝。
设计为可测试:接受依赖而不是创建依赖,例如 function processOrder(order, paymentGateway) {} 可测试,而内部 new StripeGateway() 则难以测试;返回结果而非产生副作用,例如 function calculateDiscount(cart): Discount {} 可测试,而 applyDiscount(cart): void 直接改 cart.total 难以测试;保持小表面:更少方法、更少参数。
关系:Module 恰有一个 Interface;Depth 是相对 Interface 的 Module 属性;Seam 是 Module 的 Interface 所在之处;Adapter 位于 Seam 并满足 Interface;Depth 为调用方产生 Leverage,为维护者产生 Locality。
被拒绝的表述:不以实现行数与接口行数之比定义深度(Ousterhout 式,会奖励填充实现),改用“深度即杠杆”;不把 Interface 窄化为 TypeScript interface 关键字或类的公开方法;不用 boundary(与 DDD 限界上下文混淆),改说 seam 或 interface。
进一步:给定依赖加深一组模块见 DEEPENING.md(依赖类别、接缝纪律、替换而非分层测试);探索替代接口见 DESIGN-IT-TWICE.md(并行子代理以几种迥异方式设计接口,再按深度、局地性与接缝位置比较)。
限制:资料未提供安装命令、代码包、收费或平台信息,暂未提供。