关于 codebase-design
codebase-design 是用于设计内聚模块的技能,将较多行为置于精简、稳定的调用方契约之后。它帮助开发者分析职责、调用方负担和模块深度,并设计与测试契约。
主要功能
- 01内聚深层模块的设计指导
- 02分析调用方契约负担
- 03接缝与适配器设计原则
- 04设计和测试契约的流程
- 05关联架构技能的任务分流
适用场景
- 01整合相关模块行为
- 02设计新模块或变更现有契约
- 03评估模块是否隐藏了决策
- 04规划安全的架构迁移
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ citypaul/ .dotfiles --skill codebase-design安装后在 Claude Code 中输入/codebase-design确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
LICENSEagents/openai.yamlreferences/deepening.mddesign-it-twice.mdsource-notes.md详细介绍
代码库设计
设计连贯的深层模块:将实质且相关的行为与设计决策隐藏在简洁、稳定、面向调用方的契约之后。在不创建上帝模块的前提下,最大化调用方的杠杆效益和维护者的局部性。
使用此技能处理逻辑职责和契约形态。使用 structure-codebase 处理物理路径、包、导出、依赖方向、约束实施和文件夹迁移。当选定目标是基于证据、针对整个路径机制的净减少主张,而非更深的契约时,使用 reduce-system-complexity。当需要对重要且尚未解决的库、工具、应用、服务、框架或平台进行选择时,使用 evaluate-existing-solutions。
当 improve-codebase-architecture 在未选定目标的审计期间加载此技能时,只使用其中的术语、原则、证据测试和薄边界保护措施。不要运行契约设计工作流,也不要提出精确接口,除非用户选定了候选项。
在提出重要设计之前,阅读相关参考资料:
- 合并现有模块、对依赖分类或规划安全的深化迁移时,阅读
references/deepening.md。 - 新契约或变更后的契约难以撤销,或存在多种可信形态时,阅读
references/design-it-twice.md。 - 解释来源或将此改编与其来源进行比较时,阅读
references/source-notes.md。
术语
| 术语 | 含义 |
|---|---|
| 模块 | 一个具有实现和一个或多个按角色塑造的调用方契约的内聚单元:可以是函数、对象、包或能力。规模本身并不能构成模块。 |
| 接口 / 公共契约 | 调用方正确使用模块所需了解的一切:操作、类型、不变量、错误、顺序、配置、生命周期、副作用和相关性能特征。其范围比 TypeScript interface 或类型签名更广。 |
| 实现 | 隐藏在面向调用方契约之后的决策与行为。私有函数可以既小又多,而不因此成为公共模块。 |
| 深度 | 调用方通过所需了解的契约负担所获得的连贯能力与决策能力。不要用代码行数衡量深度。 |
| 杠杆效益 | 深度为调用方带来的收益:学会一份契约,就能在许多场景中一致地应用有用行为。 |
| 局部性 | 深度为维护者带来的收益:相关知识、变更、缺陷和验证集中在一个负责人处。 |
| 接缝 | 根据 Michael Feathers 的定义,接缝是一个无需在该处编辑即可更改行为的位置;每个接缝都有一个启用点。并非每份模块契约都是接缝。 |
| 适配器 | 在接缝处选定的具体转换器或实现。在六边形架构中,保留该技能对驱动方、被驱动方和测试交互方的区分。 |
使用这些术语来消除歧义,而不是抹去有用且已有的术语。API、component、service、signature、boundary、port 和 bounded context 在指代这些特定概念时仍然有效。
设计原则
隐藏决策,而不只是隐藏代码
当调用方不应各自了解某项策略、顺序安排、表示方式、错误恢复或提供方机制时,应将相关职责置于模块之后。提取私有辅助函数并不会加深模块,若相同知识仍通过其参数和调用顺序泄露出去。
保持深度一致
不连贯实现之上的微小契约只是上帝模块,不是好的深模块。只有在行为具有共同含义、不变量、所有权、生命周期或真实的变化轴时,才将其组合在一起。如果模块会独立演进、故障、部署或授权,就应保持分离。只有当读者能说出模块负责的决策或职责,并且无需跨文件重建上下文就能理解调用方时,提取才真正建立了边界;每个辅助函数单独放一个文件,或串联一连串毫无实质内容的转发函数,都无法确立所有权。
应用保留行为的内联测试
设想将模块内联到每个调用方,同时保留原有行为:
- 如果策略、调用顺序、错误处理或表示形式相关知识因此分散到各个调用方,该模块就有存在的价值。
- 如果移除一个直通层后调用方变得更简单,而且没有重复任何知识,那么该模块就很浅,或位置不当。
这才是删除测试的有效形式。不要设想删除行为本身。
有意识地将复杂性向下收拢
让常见调用保持简单。如果在实现内部接受复杂性,能免去调用方处理配置、顺序、特殊情况或提供方知识,就应这样做。要明确说明副作用、故障模式、资源所有权和性能成本,让调用方能安全地使用该模块。
用证据证明边界合理
为具体需求建立边界:替换、独立测试、隔离易变性、所有权、信任、运行时故障或部署。适配器的数量只是证据,不是规则;一个生产适配器加上一个忠实的测试交互器,可能足以证明边界合理;两个无意间产生的包装层则不行。
在稳定契约处测试
将调用方可观察到的行为作为主要测试面。不要仅为测试而导出私有辅助函数或暴露内部边界。如果私有子系统本身是一个连贯的模块,或者某个算法需要精确定位故障,可以为其编写有针对性的测试;但这些测试不能将偶然的编排方式固定下来。
保留有意设计的轻薄边缘
不要仅仅因为路由叶节点、CLI 命令、适配器、生成的客户端或组合根很薄,就认定它们很浅。转换和接线通常就应该很薄。要判断的是:策略是否隐藏在正确的内部模块中,以及边缘是否通过契约泄漏了提供方或传输层知识。
工作流
1. 明确职责和调用方
说明模块负责的行为、当前和预期的调用方、常见情况,以及哪些内容必须留在模块之外。在命名任何新内容之前,先阅读项目说明、架构决策、术语表约定和相关测试。
2. 梳理完整的契约负担
列出每个调用方如今必须了解的内容:
- 操作和数据形状;
- 不变量和前置条件;
- 调用顺序和生命周期;
- 配置和依赖构造;
- 错误、重试、部分失败和副作用;
- 延迟、吞吐量、一致性和事务预期。
不要把简短的类型签名误认为小型接口。
3. 绘制隐藏知识和泄漏知识的关系图
追踪调用方和协作者。找出重复的决策、直通链、提供方类型、重复编排、共同变化的文件,以及必须重建内部实现的测试。同时记录反面证据:独立的所有权、不同的故障域,或确实需要采用不同策略的调用方。
4. 先确定职责,再划分边界
用一句话定义模块连贯的职责。确定哪些知识应封装在模块之后。之后再划分边界并选择依赖策略。阅读 references/deepening.md,了解现有的聚类。
如果尚未规定某项重要的通用依赖或子系统,应先将这项职责、调用方场景、影响和约束提交给 evaluate-existing-solutions,再最终确定依赖形态的契约。若将选定的提供方或库封装在本地应用语言之后,能够保留有用的变更边界,就这样做;不要习惯性地封装每个稳定的基础组件,也不要把供应商 API 原样复制到模块契约中。
5. 根据使用场景设计契约
从有代表性的调用方示例出发进行设计,并在相关时考虑无效输入、部分失败、取消、重试和生命周期。明确类型、不变量、顺序、错误、影响和性能预期,而不只是方法。
对于影响重大的选择,请阅读 references/design-it-twice.md,并比较真正不同的设计方案,再给出建议。
6. 检验深度主张
请思考:
- 常见调用方是否需要了解更少、协调更少?
- 实现知识是否被移到了契约之后,还是只是换了名字?
- 策略变更是否会集中在这里,而不是向外扩散?
- 影响和失败是否仍如实呈现?
- 是否能通过契约和忠实的依赖来测试行为?
- 设计是否避免了推测性的灵活性和通用命令式的万能接口?
7. 将交付工作交给负责的技能
- 对公开的 HTTP 接口,以及在所有权边界间发布或版本化的契约,使用
api-design。普通的进程内组件属性仍由此技能和适用的框架/设计系统指南负责。 - 对文件/包的布局和机械式依赖约束,使用
structure-codebase。 - 如果当前组合方式尚不可见,先使用
render-code-shape:它会返回带引用的边界、签名和调用图,这些正是此技能作出判断所需的证据。 - 如果被接受的结果必须减少分支、状态、依赖、层次或运维活动的总量,而不只是提升调用方的杠杆作用,则使用
reduce-system-complexity。 - 如果明确职责和约束后,仍有重要的通用实现选择尚未解决,则使用
evaluate-existing-solutions。 - 仅当系统选择采用具有明确目的的参与方交互的端口与适配器架构时,才使用
hexagonal-architecture。 - 如果现有硬编码依赖阻碍了测试框架的搭建,则使用
finding-seams。 - 在重构未经测试的行为之前,使用
characterisation-tests。 - 根据行为是否发生变化,以及安全网是否可信,在实现期间使用
tdd、testing和refactoring。在 PR 就绪时,遵循仓库的变异测试策略;如果变异测试没有意义,则记录适当的替代证据。 - 如果需要提出或更改领域术语,则使用
ubiquitous-language;切勿默默杜撰术语。
设计输出
产出:
- 模块的一句话职责及明确排除的内容。
- 调用方和有代表性的使用场景。
- 完整的拟议契约,包括非类型义务。
- 封装在契约之后的知识和行为。
- 依赖、边界、适配器或测试交互器,以及它们的保真策略。
- 考虑过的备选方案,以及推荐方案胜出的原因。
- 兼容性和渐进式迁移约束。
- 在实现变更后仍应通过的行为测试。
- 假设、风险以及仍需的证据。
完成检查
- 契约是否比它所提供的一致行为更简单?
- 它是否隐藏了决策,而不是暴露编排选项?
- 常见情况是否一目了然,同时又不让少见情况变得不可能?
- 内聚性、所有权、故障和运行时边界是否仍如实呈现?
- 接缝是否由实际的变化或隔离需求所证明?
- 调用方和行为测试能否使用同一稳定契约?
- 边缘层是否仍然精简且不包含策略?
- 每项兼容性或迁移声明是否都有验证途径?
归属
改编自 Matt Pocock 以 MIT 许可发布的 codebase-design skill 及其关联资源;深模块和 Design It Twice 概念归功于 John Ousterhout,接缝术语归功于 Michael Feathers。固定来源信息和许可条款见 references/source-notes.md 和 LICENSE。