Lesson 0005 · Engineering · 人和 AI 都能启动(model-invoked)· 词汇课

codebase-design:模块设计的共享词汇

codebase-design 是一套用来谈论模块设计的共享词汇: 什么算模块、接口、深度、接缝,每个词都有精确定义,不许随便换成近义词。 你什么时候会碰到它?当你想说「这个模块只是在转发调用」却找不到准确的词时, 或者当 improve-codebase-architecturetddto-spec 这些 skill 的正文里引用这套词汇时。 词汇本身在 SKILL.md 里,它还有两个搭档文件: DEEPENING.md 讲怎么把浅模块加深,DESIGN-IT-TWICE.md 讲怎么并行设计几种接口。 学完这节课,你能用这套词判断一个模块是深还是浅、接缝放得对不对, 也知道行为不对时该去改哪个文件的哪一段。

1. 它在整个系统里站在哪

0001 把 22 个 skill 分成三层:配置层(跑一次性的初始设置)、编排层(你手动启动的完整流程)、 纪律层(被反复调用的基本功)。codebase-design 属于纪律层,而且是纪律层里特殊的一类: 它不规定流程,只规定词汇,作者把这类 skill 叫词汇地板(ask-matt 里写作 Vocabulary underneath)——主流程上的 skill 都可以踩在这块地板上说话。 它是 model-invoked 的(人和 AI 都能启动):别的 skill 想用它, 只要在正文里写一句自然语言的「Run the /codebase-design skill」就能把它拉进来 (0001 讲过,这叫 prose 调用),你也可以单独把它当参考书翻。

它和 0004 讲的 domain-modeling 是一对平行的词汇地板,各管一套词:

词汇地板 管哪套词 帮你纠正什么 主要用在哪
domain-modeling 领域语言(Order、Account 这些业务概念) 概念模糊、一个词有几个意思、不可逆的决策 写进 CONTEXT.md 和 ADR(架构决策记录)
codebase-design 模块形状的词(module、interface、seam…) 浅模块、接缝放错位置、代码不可测、接口太大 设计讨论;被 architecture / tdd / to-spec 引用

ask-matt 的原文把这两个地板放在 Vocabulary underneath 一节: 当你的问题是用词问题、而不是要跑一整条流程时,直接用它; 如果是跑流程的过程中碰到用词问题,让上面那个 skill 把它拉进来就行。 注意别把两件事混进同一段对话:「改领域术语」和「加深模块形状」是两块地板, 混着用,两边留下的记录都会被搞脏。

罗盘:MISSION.md · 地图:0001 · 平行的一课:0004 domain-modeling · 路由规则:ask-matt/SKILL.md 的 Vocabulary underneath 一节

2. 它是什么、什么时候用

2.1 它是一套语言,不是一条流程

docs 和 SKILL.md 的说法一致:这个 skill 讲的是怎么设计 deep module(深模块)——一个模块把大量行为藏在一个小接口后面, 立在一条干净的 seam(接缝,就是不用改那处代码就能换行为的位置)上, 并且通过那个接口就能测试。目标是:给调用它的人 leverage (杠杆,学会一点接口就能驱动很多行为),给维护它的人 locality (局部性,改动和知识集中在一处),给所有人可测性。 这几个词在第 3 节都有精确定义,先有个印象就行。

一句话定性 codebase-design 是 deep-module 词汇的唯一权威出处(single source of truth)。 作者故意把它拆成一个独立的、人和 AI 都能启动的 skill, 这样任何别的 skill 只要引用它,而不用把同样的定义再抄一遍。 它替你重构代码、给你重构计划、也写架构 HTML 报告—— 那些是 improve-codebase-architecture 那条流程干的事。 你单独用它时,做的是「把词说对、把模块形状想清楚」, 不会触发 architecture 那条「扫描 → 报告 → 面试(grilling)」的流水线。

2.2 AI 什么时候会自己加载它

frontmatter 里的 description 字段写明了触发条件:当你要设计或改进一个模块的接口、 想找加深模块的机会、要决定接缝放在哪、想让代码更好测或对 AI 更好读, 或者别的 skill 需要这套词汇时——用它。

情境 该用 codebase-design 更该去哪
接口该长什么样、接缝切在哪、模块够不够深 (给你词汇、原则,还能并行设计几种接口)
扫描整个仓库找浅模块、出一份 HTML 候选报告、再面试着挑一个加深 它会被 architecture 在内部拉进去 improve-codebase-architecture(只能人启动)
做红绿循环(先写测试再写实现)、在约定好的接缝上写测试 说的是同一套词(接缝、接口) tdd;implement 内部也应该用 tdd
写需求文档时画测试接缝 词汇保持一致 to-spec(它会先画接缝草图)
「account 这个词同时干三份活」 不该(那是领域词的问题) domain-modeling
不知道该走哪条流程 不该 ask-matt

权威原文: skills/engineering/codebase-design/SKILL.md · 给人看的叙事版: docs/engineering/codebase-design.mdaihero.dev/skills-codebase-design

3. 核心词汇:八个词,一个都不能换

SKILL.md 开篇就要求:用这些词,不要换成 component / service / API / boundary。 词汇统一是这个 skill 存在的理由——换成近义词,会把真正要区分的差别抹掉。 下面这张表把每个词的定义按 SKILL.md 原文压缩了一遍,第一次读可以慢一点。

术语 定义(按 SKILL.md 原文压缩) 故意不用的近义词 常见误用
Module(模块) 任何「有接口 + 有实现」的东西都算模块。它故意不限定尺度:一个函数、一个类、一个包、一个跨层的切片,都行 unit、component、service 默认「一个模块 = 一个文件 / 一个微服务」——尺度理解错了,后面就没法谈深度
Interface(接口) 调用者想正确使用这个模块,必须知道的一切:类型签名、必须恒成立的条件(invariants)、调用顺序的约束、出错时的表现、必填的配置、性能特征 API、signature(这两个词太窄,只指类型那一面) 把 TypeScript 的 interface 关键字、或者一份 public 方法清单,当成接口的全部
Implementation(实现) 模块内部的那坨代码 和 adapter 混着用:谈「模块里面是什么」用 implementation;谈「接缝上插的是哪个件」用 adapter
Depth(深度) 衡量接口上的杠杆:调用者(或测试)每学会一单位接口,能驱动多少行为。行为多、接口小 → (deep);接口几乎和实现一样复杂 → (shallow) 拿「实现行数 ÷ 接口行数」当深度——这个算法被明确否定了
Seam(接缝)
Feathers
不用改那处的代码、就能改变行为的位置;也就是模块接口所在的位置。接缝放哪,是一个独立的设计决策 boundary(和 DDD 的 bounded context「限界上下文」撞车) 把任何 import 边界都叫接缝;或者测试偷偷摸进私有内部,还自称「测在接缝上」
Adapter(适配器) 插在接缝上、满足某个接口的具体东西。这个词描述的是角色(填哪个槽),不描述它内部是什么 同一个 Postgres 仓库,谈体量时可以是「小 adapter + 大 implementation」;一个内存 fake 也可以是「adapter 角色重、implementation 很小」——按当下的话题选词
Leverage(杠杆) 调用者从深度得到的好处:每学一单位接口,得到更多能力。一份实现,在 N 个调用点和 M 个测试上反复回收价值 只数「功能列表长」,不看「接口是不是仍然小」
Locality(局部性) 维护者从深度得到的好处:变更、缺陷、知识、验证都集中在一处,而不是散落在各个调用者之间。修一次,处处都修好 把「为了单测,把纯函数拆得到处都是」误当成局部性——那其实是局部性的反面
Implementation ≠ Adapter 同一块代码,在不同话题下挂不同的标签:谈「体量和藏起来的逻辑」时叫 implementation; 谈「这个槽位上插的是生产用的 HTTP 还是测试用的内存 fake」时叫 adapter。 别嘴上说「写一个 adapter」,实际只是在加一层只负责转发的薄壳、而且从头到尾只有一个实现—— 那通常是假接缝(见第 5 节和第 8 节)。

4. 深模块 vs 浅模块,和删除测试

Deep module                          Shallow module(要避免)
┌─────────────────────┐              ┌─────────────────────────────────┐
│   Small Interface   │  少方法、简参 │       Large Interface           │  多方法、复杂参
├─────────────────────┤              ├─────────────────────────────────┤
│                     │              │  Thin Implementation            │  几乎只转发
│  Deep Implementation│  复杂逻辑隐藏 │                                 │
│                     │              └─────────────────────────────────┘
└─────────────────────┘

设计接口时,问自己三个问题:
  · 能否减少方法数?
  · 能否简化参数?
  · 能否把更多复杂度藏进里面?

4.1 删除测试(deletion test)

想象把这个模块整个删掉,然后看复杂度去了哪:

improve-codebase-architecture 在找浅模块时会主动套用这个测试,问: 「删掉它,复杂度是被集中消灭了,还是只是搬了个家?」 答案是「会集中」,才是你想要的加深信号。

5. 四条原则

四条原则全部围绕一件事:深度是长在接口上的,不是长在实现上的。

原则 意思 实际后果
Depth 是接口的属性,不是实现的属性 深模块内部可以拆成小的、可仿冒(mock)、可替换的零件——只要这些零件不出现在对外接口里 允许两种接缝并存:内部接缝(实现私有,只给自己的测试用)和外部接缝(对外接口);别为了测试把内部接缝暴露成公共接口
删除测试(deletion test) 见 §4.1 筛掉「看着像有一层、实际只在转发」的假模块
接口就是测试面(Interface is the test surface) 调用者和测试走同一条接缝 如果你想「穿过」接口去测内部,多半是模块形状错了
一个 adapter = 假想的接缝;两个 adapter = 真接缝 不存在「将来真的要换实现」的理由,就不要开接缝 典型的正当双 adapter 是「生产 + 测试」;只有一个实现的 port(端口,跨接缝的插槽)只是多绕一层

6. 为可测性设计

SKILL.md 给了三条可以直接照做的经验法则(和 tdd、mocking 的纪律同方向):

  1. Accept dependencies, don't create them. 依赖从参数传进来(注入),别在函数体里 new StripeGateway()
  2. Return results, don't produce side effects. 优先返回值(比如 calculateDiscount(cart): Discount), 而不是原地改状态、留下难以断言的副作用(side effect:函数除了返回值之外,对外界造成的改动)。
  3. Small surface area. 接口面要小:方法越少,要写的测试越少;参数越少,测试数据越好准备。
和 tdd 对上的那句话 tdd 那条 skill 规定:测试通过公共接口验证行为,活在接缝上,绝不捅内部; 写任何测试之前,先把接缝写下来并跟你确认。 你刚学的「接口就是测试面」就是那条纪律的设计侧: 先把面设计对,再在这个面上做红绿循环(red-green:先写一个失败的测试,再写实现让它通过)。

7. 关系图,和被明确否定的说法

                    ┌──────────────┐
                    │   Module     │  恰好有一个 Interface
                    └──────┬───────┘
                           │ presents
                           ▼
                    ┌──────────────┐
         Depth ───► │  Interface   │  Depth 相对 Interface 度量
                    └──────┬───────┘
                           │ lives at
                           ▼
                    ┌──────────────┐
                    │    Seam      │  位置决策 ≠ 背后装什么
                    └──────┬───────┘
                           │ filled by
                           ▼
                    ┌──────────────┐
                    │   Adapter    │  满足 Interface 的具体物
                    └──────────────┘

Depth ──produces──► Leverage(调用者)
                 └──► Locality(维护者)

7.1 被明确否定的三种说法

8. DEEPENING.md:怎么把浅模块加深

DEEPENING.md 假定你已经会第 3 节的词汇,它回答的问题是: 给定一个依赖,怎么安全地把一簇浅模块加深。 「deepening opportunity」(加深机会)在 architecture 那条 skill 里, 指「把浅模块变深」的重构候选(目的是可测性和 AI 可导航性——让 AI 更容易读懂代码、找到路); 在这个文件里,它落成三件具体的事:依赖分类、接缝纪律、测试策略。

8.1 依赖分四类,决定跨接缝怎么测

类别 例子 能否加深 测试怎么写
1. In-process(进程内) 纯计算、内存状态、没有 I/O 总是可以 把模块合并起来,直接通过新接口测,不需要 adapter
2. Local-substitutable(本地可替代) 用 PGLite 代替 Postgres、内存文件系统 只要存在本地 stand-in(本地替身)就可以 测试套件跑本地替身;接缝留在模块内部,不在对外接口上开 port
3. Remote but owned(远程但自己拥有)
Ports & Adapters
自己的微服务、内部 API 可以:逻辑放进深模块,网络传输当 adapter 定义 port;生产环境用 HTTP/gRPC/队列 adapter,测试用内存 adapter
4. True external(真正的外部依赖,用 mock) Stripe、Twilio 这类你控制不了的第三方 加深后的模块把外部依赖以 port 的形式注入进来 测试提供 mock adapter(仿冒的 adapter)

对类别 3,推荐的表达形状是(按原文的意图): 「在接缝上定义 port,实现一个 HTTP adapter 给生产环境、一个内存 adapter 给测试, 这样即使部署横跨网络,逻辑也落在一个深模块里。」

8.2 加深时的接缝纪律

8.3 测试策略:替换,不叠加(replace, don't layer)

9. DESIGN-IT-TWICE.md:同一个接口并行设计几种

这个文件出自 Ousterhout 的「Design It Twice」(设计两遍):第一个想法多半不是最好的。 当你已经选定了一个加深候选、想探索几种不同的接口方案时,用并行子代理模式 (sub-agent:主 agent 派出去、各自独立干活的 agent)。 architecture 的面试循环结尾也会点名它:想看多种接口设计 → 运行 /codebase-design 并走这个模式。

9.1 三步走

做什么 注意
1. 框定问题空间 给用户看一份说明:新接口的约束、依赖及其 DEEPENING 类别、粗略 sketch(草图)。这不是提案,只是把约束说具体 展示给用户后立刻进第 2 步:用户边读边想,agent 同时开始并行设计
2. 派生 3 个以上子代理 每个子代理产出一个截然不同的接口,各自写一份独立的技术简报(路径、耦合、依赖类别、接缝背后藏什么) 简报独立于第 1 步给用户看的文案;简报里必须用上 SKILL 的词汇和项目 CONTEXT.md 里的领域词
3. 展示并比较 按顺序展示各设计 → 用散文对比深度、局部性、接缝位置 → 给出有倾向的推荐(可以是几个方案的混合体) 用户要的是一个明确的判断,不是一份没有立场的菜单

9.2 每个 agent 各自的设计约束(默认四个方向)

Agent 1  Minimize interface — 1–3 entry points;最大化每点 leverage
Agent 2  Maximise flexibility — 多用例与扩展
Agent 3  Optimise for most common caller — 默认路径极简
Agent 4  (若适用)Ports & adapters,围绕跨 seam 依赖

9.3 每个子代理必须交出五样东西

  1. 接口(类型、方法、参数,加上必须恒成立的条件、调用顺序、出错模式)
  2. 用法示例(调用者怎么用)
  3. 实现在接缝后面藏了什么
  4. 依赖策略和 adapters(对照 DEEPENING 的分类)
  5. 取舍:哪里杠杆厚、哪里薄

10. 谁在用这套词

                ┌─────────────────────────┐
                │   codebase-design       │  词汇的唯一权威出处
                │   (+ DEEPENING,         │
                │    DESIGN-IT-TWICE)     │
                └───────────┬─────────────┘
           ┌────────────────┼────────────────────┐
           ▼                ▼                    ▼
 improve-codebase-     tdd / implement      to-spec
 architecture          (在接缝上测)       (画接缝草图)
 · 找浅点、套删除测试   · 先确认接缝        · 优先复用已有接缝
 · 报告里用这套词      · 接口=测试面       · 接缝尽量少(理想 1)
 · 面试后可回本 skill   · 反对测试耦合实现
   做 design-it-twice
Skill 启动 怎么使用这套词汇
improve-codebase-architecture User 开篇就强制运行 /codebase-design 来对齐词汇;每条建议必须使用 module/interface/depth/seam/adapter/leverage/locality,禁止 component/service/API/boundary。它说的「候选」就是加深机会。选定后面试你;想看多种接口设计 → 再回来走 design-it-twice。
tdd Model 接缝就是测试的落点;只在你预先确认的接缝上写测试;好测试通过公共接口验证行为。和「接口就是测试面」是同一件事。
implement User 写得很薄的入口(正文只有几行):尽量用 tdd,在预先约定的接缝上写。形状决策应该已经在上游(spec、面试、design-it-twice)用这套词汇定好了。
to-spec User 写需求文档前先画测试接缝的草图:优先复用已有的、位置尽量高、数量越少越好(理想是一个),并和你确认。文档的 Implementation Decisions 一节谈的是模块和接口,不是文件路径。
diagnosing-bugs Model 发现没有好接缝能锁住这个 bug 时,在复盘(post-mortem)里把问题移交给 improve-codebase-architecture,那边就会用上这套词汇。
ask-matt User 在它的地图里,architecture 是「找候选的扫描」,codebase-design 是「设计所选候选的工作台」;tdd 和 architecture 都说这套词。
Architecture vs codebase-design(别搞混它们留下的痕迹) architecture 会:探索 → 把报告写成 $TMPDIR/architecture-review-<timestamp>.html(Tailwind + Mermaid CDN)并自动打开 → 你挑一个 → 面试 + 可能更新 CONTEXT/ADR → 可选地 design-it-twice。
codebase-design 只提供:词汇、原则、依赖分类、并行接口设计。默认不写仓库里的任何文件,也不写临时 HTML。

11. 会留下什么、用完接什么、想微调改哪里

11.1 每个动作会不会写文件

动作 会写文件吗 说明
单独跑 /codebase-design 对齐词汇、讨论深度 什么都不写 只在对话里推理;0001 的全表里标的就是「主要是词汇和设计讨论」
design-it-twice 子代理的输出 通常只在对话里 比较和推荐都发生在会话里;真正落地走后面的流程
improve-codebase-architecture 的报告 写进操作系统临时目录的 HTML,不进仓库 $TMPDIR / /tmp / %TEMP%
architecture 面试过程中的命名和决策 可能写 CONTEXT/ADR 那是通过 domain-modeling 写的,不是 codebase-design 自己写文件
真的去改业务模块的形状 改代码库和测试 通过 to-spec / tickets / implement(+tdd) 来做,或你明确要求重构——这个 skill 不代劳

11.2 用完之后,下一步去哪

  1. 只是把词说对了、形状也想清楚了 → 回到你当前的流程;如果你在写功能,让 to-spec 记下模块、接口和测试决策,或者直接在约定好的接缝上用 tdd 开写。
  2. 从 architecture 报告里挑中了一个加深候选 → 走面试的决策树 → 想法进 /grill-with-docs 主流程 → to-spec / tickets / implement(ask-matt 的说法:扫描产生想法,设计工作台是 codebase-design)。
  3. 跑了 design-it-twice → 采纳推荐方案(或混合体)→ 把接口决策写进 spec 或 ADR(如果不可逆)→ 实现阶段 tdd 只测那条外部接缝。
  4. 加深完成了 → 按 DEEPENING 的要求:新测试写在加深后的接口上;删掉浅层的旧测试;之后的内部重构不应该逼你改测试。

11.3 行为不对时,去改哪个文件的哪一段

症状 先查 不要误改
对话里冒出 component/service/boundary,深度讨论跑偏 codebase-design/SKILL.md 的 Glossary 和 Rejected framings 两节 architecture 报告的 HTML 样式
乱开 port、只有一个假 adapter SKILL.md 的原则一节 + DEEPENING.md 的接缝纪律 tdd 的红绿循环细则
加深后测试仍耦合内部、旧的浅层测试还在堆 DEEPENING.md 的「替换,不叠加」;tdd 的反面模式清单 本课的 quiz 文案
依赖跨网络,却把逻辑测成一团 mock 地狱 DEEPENING 的类别 3 和类别 4 之分;ports & adapters 的形状 domain-modeling 的 ADR 三条件
design-it-twice 出来的方案雷同、没有激进差异 DESIGN-IT-TWICE.md 的 agent 约束和简报内容 ask-matt 的路由句
整库扫描没按热点(hot spot)来、报告被写进了仓库 improve-codebase-architecture/SKILL.md 和它的 HTML-REPORT 文件 codebase-design 的词汇段(那是设计工作台,不是扫描器)
AI 从不主动加载这个 skill frontmatter 里 description 的 “Use when…” 一段 plugin.json 是否发布(它本来就是已发布的 model skill)

12. 检索练习

先别往回翻表,凭记忆答。选项的长度刻意对齐,不会从版式泄题。术语以本课和 SKILL.md 的精确定义为准。

自测(立即反馈)

1. 按本仓库定义,Depth 主要是什么的属性?
2. 「One adapter means a hypothetical seam」意味着什么?
3. 删除测试(deletion test)里,「模块赚 keep」的信号是?
4. Local-substitutable(如 PGLite)加深时,DEEPENING 建议?
5. 「Interface is the test surface」与 tdd 哪条最对齐?
6. improve-codebase-architecture 的 HTML 报告默认写到哪里?
7. Design-it-twice 对比方案时,明文要求用哪三个轴?
8. 下列哪项是本 skill 明确 rejected 的 framing?
额外提取练习(无选项) 合上本页,默写这八个术语:module、interface、implementation、depth、seam、adapter、leverage、locality(implementation 最常被漏掉)。 再给每个写一句「禁用的近义词 / 常见误用」。写完对照第 3 节的表。 最后用删除测试描述你当前项目里一个可疑的浅模块——不要求真的重构,只要求会问问题。

13. 下一课与一手材料

本课主一手材料(请打开原文读,不要只背本页摘要):

速查页(本课同步): reference/codebase-design.html

导航: 上一课 0004 domain-modeling (平行的词汇地板:一个管领域词,一个管模块形状)。 总览仍回 0001 系统地图; 面试编排见 0003

建议下一课(0006,还没写):NOTES.md 的课程顺序,两个词汇地板之后是 handoff(再往后是 research / prototype)——跨会话的桥,和「可运行答案」的岔路。 和本课的交接点:当 design-it-twice 或 architecture 的面试把上下文塞满时, 用 handoff 换个新会话,别在一个已经变迟钝的窗口里硬写加深后的接口。

老师就在会话里。 对本课任何一个术语的边界有疑问——比如内部接缝能不能被集成测试碰到、 类别 2 和类别 3 之间的灰色地带、design-it-twice 到底要不要第四个 agent——直接在对话里问。 回答会回到 SKILL.md / DEEPENING.md / DESIGN-IT-TWICE.md 的原文,不会临场编造。 做完检索练习后,回复「练习结果 / 哪里卡住 / 开 0006 或先补 0004」,我们安排下一课。