Sanity 仓库的深模块设计:小接口 + 深实现的 TDD 测试驱动原则
2026/9/17 16:14:50 网站建设 项目流程

Sanity 仓库的深模块设计:小接口 + 深实现的 TDD 测试驱动原则

【免费下载链接】sanitySanity Studio – Rapidly configure content workspaces powered by structured content项目地址: https://gitcode.com/GitHub_Trending/sa/sanity

在 Sanity Studio 的 Agent 技能库(.agents/skills/tdd/)中,deep-modules.md 定义了 TDD 工作流里最核心的模块设计准则——"深模块"(Deep Module):用小而简单的公开接口隐藏大量复杂实现,让测试只针对行为而非内部细节。读完本篇,你将掌握深模块与浅模块的判别方法、设计接口时的三个自检问题,并能以 Sanity 仓库中真实的@sanity/mutator包为例,看清这一原则在大型开源项目源码中的落地形态。

深模块的定义:小接口 + 大量实现

文档开篇引用 John Ousterhout 的《A Philosophy of Software Design》给出定义:

Deep module= small interface + lots of implementation(深模块 = 小接口 + 大量实现)

原文档用两个 ASCII 图刻画了理想模块与应被避免的模块的形态对比:

深模块

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

接口层只有"少量方法、简单参数";实现层则隐藏了复杂逻辑。

浅模块(should avoid):

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

浅模块的特征是"大接口 + 薄实现":暴露了大量方法和复杂参数,内部却几乎只是透传。这样的模块维护成本高、难以测试,也是重构时的首要目标。

设计接口时的三个自检问题

文档给出了设计接口时必须反复追问自己的三个问题:

  • Can I reduce the number of methods?(能否减少方法数量?)
  • Can I simplify the parameters?(能否简化参数?)
  • Can I hide more complexity inside?(能否把更多复杂性藏进内部?)

这三个问题同时也是判断一个既有模块"深浅"的标尺:当接口几乎与实现一样复杂时,说明模块过浅——这一点在仓库的另一篇技能文档 refactoring.md 中被直接列为重构候选项之一("Shallow modules → Combine or deepen",即浅模块应合并或加深)。

深模块在 TDD 工作流中的位置

deep-modules.md并非孤立存在,它是 TDD 技能整体流程的组成部分,可以从两处调用链印证其在实际开发流程中的地位:

1. 规划阶段就识别深模块机会。TDD 主文档 SKILL.md 的 Planning 检查清单中明确写道:

- [ ] Identify opportunities for [deep modules](https://link.gitcode.com/i/8b0df77ddd78ad22a97be79426e538be) (small interface, deep implementation)

也就是说,在写任何代码之前,规划环节就要主动寻找"用小接口封住复杂性"的机会。这与 SKILL.md 的核心哲学一脉相承:测试应通过公开接口验证行为,而非实现细节——深模块天然提供了这样的稳定接口,"代码可以彻底改动,测试不应该变"。

2. 重构阶段以加深模块为目标。SKILL.md 的 Refactor 清单中同样要求 "Deepen modules (move complexity behind simple interfaces)",与 refactoring.md 中"浅模块合并或加深"的重构候选形成呼应:先让测试全绿,再在保持公共接口测试不变的前提下,把复杂度往模块内部搬运。

此外,仓库中还有一个专门围绕该理念的架构技能 improve-codebase-architecture/SKILL.md,它把"深模块"定义为"接口小到足以在边界处测试,而非在内部测试",并给出了系统化的探查问题,例如:

  • Where are modules so shallow that the interface is nearly as complex as the implementation?(哪些模块浅到接口几乎和实现一样复杂?)
  • Where do tightly-coupled modules create integration risk in the seams between them?(哪些紧耦合模块在接缝处制造了集成风险?)

仓库实证:@sanity/mutator 就是一个教科书式的深模块

Sanity 仓库中最能体现深模块理念的代码,是文档变更(Mutation)引擎 packages/@sanity/mutator。

看它的公开接口有多小。包入口 packages/@sanity/mutator/src/index.ts 的完整导出只有两行:

export { BufferedDocument, type CommitHandlerMessage, type Doc, type Document, type Mut, Mutation, type MutationParams, type SquashingBuffer, type SubmissionResponder, } from './document' export {arrayToJSONMatchPath, extract, extractWithPath} from './jsonpath'

调用方只需要认识BufferedDocumentMutationDocumentSquashingBuffer这几个类以及extract等少量函数——这正是"小接口、少方法、参数简单"。

再看它内部藏了多少实现。从源码目录结构看,接口之下是三个实现密集的模块:

  • document/BufferedDocument.tsDocument.tsMutation.tsSquashingBuffer.ts(变更缓冲、提交、合并);
  • jsonpath/:16 个文件构成一套完整的 JSON 路径表达式引擎——tokenize.tsparse.tsMatcher.tsExpression.tsProbe.tsDescender.tsarrayToJSONMatchPath.ts等,公开出来的却只有extractextractWithPatharrayToJSONMatchPath等少数函数。以 extract.ts 为例,对外签名只是extract(path: string, value: unknown): unknown[]——一行参数,内部却要完成路径词法分析、表达式求值、匹配器执行的全过程;
  • patch/Patcher.tsSetPatch.tsUnsetPatch.tsInsertPatch.tsIncPatch.tsSetIfMissingPatch.ts乃至DiffMatchPatch.ts,把各类补丁的具体执行逻辑全部隔离在这一层。

深接口带来的直接收益是测试与调用都发生在边界。在 Sanity 主包中检索from '@sanity/mutator'的引用,可以看到 Studio 核心代码(如 packages/sanity/src/core/store/document/buffered-doc/createObservableBufferedDocument.ts、packages/sanity/src/core/form/utils/mutationPatch.ts 等)只经由上述几个类与函数与引擎交互。由于接口足够稳定、足够小,mutationPatch.ts这类工具函数可以只依赖Mutation/Patcher的公开行为编写与测试,而不必关心tokenizeMatcher等内部细节如何变化——这正是 TDD 技能文档强调的"测试描述行为、可存活于内部重构之后"。

实践要点小结

结合deep-modules.md原文档与仓库中的 TDD 技能文档、@sanity/mutator源码,可以提炼出以下可复用的操作清单:

  1. 识别浅模块:接口复杂度接近实现复杂度、方法多且参数复杂、内部只是透传——符合任一条即为浅模块候选;
  2. 规划先行:写代码前按 TDD Planning 清单检查,主动寻找可以"收口"的接口,用三个问题(减少方法数、简化参数、藏住复杂性)逐一拷问设计;
  3. 测试落在边界:针对深模块的公开接口编写集成风格测试,使其能描述"系统做什么"而非"怎么做",从而在内部重构时保持绿色;
  4. 重构加深:测试全绿后,按 refactoring.md 的候选项把浅模块合并或加深,把复杂度搬运到简单接口之后;
  5. 参考标杆:想理解什么叫"小而深",直接读 packages/@sanity/mutator/src/index.ts 及其document/jsonpath/patch/三个实现目录的对比,是比任何抽象解释都直观的范例。

【免费下载链接】sanitySanity Studio – Rapidly configure content workspaces powered by structured content项目地址: https://gitcode.com/GitHub_Trending/sa/sanity

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询