Chengshu@skadai · 2026.10.20
RSS
1,734 字 · 387 词 · 约 6 分钟

codebase-design 精读:一份词汇表,怎么用『删掉它』判定一个模块该不该存在

codebase-design 是一份词汇表而不是一次会话:它定义 module / interface / depth / seam / adapter / leverage / locality 七个概念,并给每个词配一个被禁的替代词。本文走一遍它的真实触发路径,讲清它的四个判定工具(deletion test、接口即测试面、一个 adapter 是假缝两个才是真缝、depth 是接口的属性),它为什么公开否掉 Ousterhout 的 depth 定义,以及一个没有流程、没有停止条件的技能会付出什么代价。

(本文读的是 mattpocock/skills 快照 5beb35b,2026-10-19。引用原文都能按文中路径回仓库核对。这是「逐个技能精读」系列的第十三篇,聚焦 codebase-design 一个技能。)

skills/engineering/codebase-design/SKILL.md 的第一句就是它的全部野心,也是一切后续判断的基准:

Design deep modules: a lot of behaviour behind a small interface, placed at a clean seam, testable through that interface. Use this language and these principles wherever code is being designed or restructured.

它的 description 则交代了触发方式——你敲 /codebase-design,或者别的技能需要「深模块词汇」时把它拉进来(or when another skill needs the deep-module vocabulary)。两种情况产出都一样:一次对话里换了一套词。 没有文件,没有 diff,没有工单。

它怎么工作:定义词,并给每个词配一个「不许说」

这个技能的实际机制,是把七个术语连同它们的边界一起钉死:

术语 它是什么
Module 任何有 interface 和 implementation 的东西,刻意与尺度无关:一个函数、一个类、一个包、一个跨层的切片
Interface 调用方为了正确使用它必须知道的一切:类型签名,还有不变量、顺序约束、错误模式、必需的配置、性能特征
Implementation 模块内部那段代码本身
Depth 接口处的杠杆:调用方(或测试)每学一单位接口,能驱动多少行为
Seam (借 Michael Feathers)不改动此处就能改变行为的地方;也就是 interface 所在的位置
Adapter 在 seam 处满足某个 interface 的具体东西;它描述角色,不描述内容
Leverage 调用方从 depth 得到的东西:每学一单位接口换来更多能力
Locality 维护者从 depth 得到的东西:改动、bug、知识与验证都收敛在一处

比表格更狠的地方,是每个词后面挂着一个 _Avoid_,而且开头就下了命令:

Use these terms exactly: don’t substitute “component,” “service,” “API,” or “boundary.” Consistent language is the whole point.

Interface: … Avoid: API, signature (too narrow, they refer only to the type-level surface).

注意 module 的定语是 Deliberately scale-agnostic。它不是文件夹、不是包边界——文档里为这条专门挡过好几次「把它变成目录规范」的提议。

四个能直接拿来判定的工具

词汇之外,它给了四条可以当场裁定的规则:

  • deletion test(删除测试):想象删掉这个模块。如果复杂度消失了,它就是一个转手器(pass-through);如果复杂度重新出现在 N 个调用方,说明它确实在扛东西。
  • 接口就是测试面:调用方和测试走的是同一个 seam。想「绕过接口去测」,通常说明模块的形状是错的。
  • 一个 adapter 是假缝,两个才是真缝:Don't introduce a seam unless something actually varies across it. 只有一个实现的接口,本质是多余的间接层。
  • Depth 是接口的属性,不是实现的属性。内部可以是一堆小的、可替换的零件,调用方只是看不见它们;模块可以在接口处有一个外部 seam,同时内部有只给自己的测试用的内部 seam。

Rejected framings:公开否掉三个说法

这是它最有性格的一节:它不只说自己对,还逐条说明别人常说的那个定义为什么不用。

  • Depth as ratio of implementation-lines to interface-lines (Ousterhout): rewards padding the implementation. We use depth-as-leverage instead.
  • “Interface” as the TypeScript interface keyword or a class’s public methods: too narrow: interface here includes every fact a caller must know.
  • “Boundary”: overloaded with DDD’s bounded context. Say seam or interface.

第一条值得停一下:它否掉的,正是这个概念提出者 Ousterhout 的原始定义。理由是指标会反向激励——一旦用「实现行数 ÷ 接口行数」衡量深度,把实现写肿就成了得分手段。换成「depth-as-leverage」之后,就没法靠注水拿分。否掉一个更出名的定义,是为了堵住一个可被优化的漏洞。

两个按需读取的支撑文件

SKILL.md 只做词汇和原则,「怎么做」交给两个子文件,而且是按需读:

  • DEEPENING.md:把候选模块的依赖分成四类——in-process / local-substitutable / remote but owned / true external——因为依赖的类别决定了跨界怎么测。里面还有一条很好用的原则 replace, don’t layer:加深之后,旧的浅模块上的单元测试变成浪费,删掉,把测试写到新接口上。
  • DESIGN-IT-TWICE.md:来自 Ousterhout 的同名技巧,但用并行子代理实现。给同一个模块开 3 个以上子代理,各自产出一个激进不同的接口(一个「接口最小化」、一个「最大化灵活性」、一个「优化最常见调用方」),然后按 depth、locality、seam placement 三个维度对比,最后给出一个自己的推荐,而不是一份菜单。

它和「一般技能」的差别:它是一条词汇层,不是一次执行

一般技能的形状是「输入 → 步骤 → 产物」。codebase-design 故意不是。文档把它定位成 the vocabulary layer underneath the engineering skills rather than a step in any chain;它把「写不写代码」完全交出去:不找候选(那是 improve-codebase-architecture)、不逼问设计(那是 grilling)、不写测试(那是 tdd)。它只做一件别人做不了的事——当你开始说「component」的时候,有一份被公开维护的文档能指出:你要说的是 module、interface 还是 seam。

它有意识摒弃了什么

一、摒弃了流程和停止条件。 它是参照物,不是驱动者,代价是真实的。文档记着 issue #449:有人对一个「没有流程的技能」说「继续推进」,agent 就挑了最像流程的 DESIGN-IT-TWICE.md,去重探一个上个会话已经测绘过的代码库,烧掉 100k token。作者的结论不是补护栏,而是明确它没有护栏——要驱动就用 /grill-with-docs / /improve-codebase-architecture / /tdd,把它当词汇表用。

二、摒弃了一整套更常见的词。 不许说 component / service / API / boundary。理由开头就给了:Consistent language is the whole point. 文档补了更锋利的一条:a term nobody uses consistently is worse than no term.

三、摒弃了 Ousterhout 的 depth 定义。 见上:会被注水。

四、摒弃了把 module 绑到文件系统。 issue #95 提议把它落成一套固定的目录树,被拒,理由是两个问题相互独立:deep modules are about the design of the interface and accessing through a strict interface, no matter what the file system looks like.

五、摒弃了一次性加载全部内容。 两个子文件按需读:词汇表进上下文,四类依赖和并行设计只在真要用时才进。

原文证据

「它在工作」的判据里,前两条最能说明它是词汇层:

  • The design conversation stops producing the words “component”, “service” and “boundary”, and starts producing “module”, “interface” and “seam”.
  • Someone can point at a proposed extraction and say whether it passes the deletion test, without hedging.

还有两处诚实值得看。它自己的边界写得很清楚:How do I actually build a deep module in TypeScript? 这个问题,文档说 the skill does not answer it——它定义了什么算深,却没给任何强制手段(没有 lint、没有护栏)。以及 DESIGN-IT-TWICE.md 依赖 Claude Code 的 Agent 工具,Agent 是某个 harness 的专有名字,换到别的 harness 就不成立(issue #564)。

可迁移结论

  1. 技能可以是纯词汇,但要把「它不是流程」写在最显眼处。 最像流程的那个文件先被误用,就是「没有停止条件」的直接代价。
  2. 定义术语时,顺手定义它替换掉哪个词。 _Avoid_ 列表比正面定义更能防止语言漂移。
  3. 能给出判定工具就别只给原则。 deletion test 让一次设计争论变得可裁决:一个具体动作,一个是/否的答案。
  4. 敢否掉自己领域的权威定义,只要你指得出它的失效模式。 「Ousterhout 的比值会奖励注水」就是一次示范。
  5. 厚内容按需读取,别一股脑塞进上下文。 主文件给词和原则,子文件各自解决一个具体的「怎么做」。

codebase-design 是这个系列里唯一一篇「不产出任何东西」的精读——它连一次对话都不承诺,只承诺你在对话里用的词。而正是这个立场,让它成为一整套工程技能底下那条最不显眼、却最难被替代的地基。

讨论

用 GitHub 账号留言;评论保存在公开仓库chengshu-blog-discussions的 Discussions 里。也可通过 RSS 订阅后续文章。