关于 codebase-design
这是一项面向现有代码库模块与接口设计的技能,适用于开发者。它从调用方需求出发,关注行为归属和可测试性,并避免不能减少调用方负担的封装。
主要功能
- 01从典型调用场景推导接口
- 02为行为明确模块归属
- 03区分深模块与浅层包装
- 04指导依赖接缝与行为测试
适用场景
- 01为现有工作流设计模块
- 02决定共享规则应由何处负责
- 03在不暴露内部细节时测试行为
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ marcellocurto/ skills --skill codebase-design安装后在 Claude Code 中输入/codebase-design确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
DEEPENING.mdDESIGN-IT-TWICE.mdDOMAIN-STRUCTURE.mdTYPE-DESIGN.mdagents/openai.yaml详细介绍
代码库设计
将相关规则和行为放入模块中,让调用方无需了解其内部实现即可使用。评估设计时,应看调用方必须了解什么、规则改变时需要修改多少处,以及行为是否易于测试。
术语
使用项目中现有的名称。这些区分有助于解释设计,但并不意味着要重命名代码或领域概念。
- 模块: 函数、类、包或其他同时具有接口和实现的单元。
- 接口: 调用方正确使用模块必须了解的一切,包括类型、必需的调用顺序、错误、配置和性能限制。它不只是 TypeScript 的
interface声明。 - 实现: 接口背后的代码。
- 适配器: 将接口连接到特定依赖项(例如数据库或外部服务)的实现。这个术语描述的是其作用,而非规模。
- 接缝: 可以替换行为或依赖项的位置,包括用于测试的位置。需要区分时,应说明它是模块内部的,还是向应用调用方公开的。
深层模块会隐藏调用方原本必须自行做出的决定。浅层包装器只增加接口,却没有减少调用方的工作。代码量、方法数量以及接口与实现的行数比例,都不能说明模块属于哪一种。
从调用方出发设计
先勾勒有代表性的调用点,再设计类型或方法。根据调用方的需求推导接口。除非调用方需要控制,否则应将处理阶段、存储细节和协调工作保留在模块内部。
对于每个拟议的接口,请思考:
- 调用方能否在不了解内部规则或协调无关操作的情况下完成任务?
- 每个参数是否都代表调用方需要做出的选择?
- 规则改变时,能否在其所属模块中更新,而不必修改各个调用方?
- 移除该模块会消除不必要的转发,还是会迫使调用方自行处理其行为?
即使只有一个调用方或一个适配器,模块也可能有用。如果它能隔离协议、副作用或独立变化的规则,就应保留。不要为了证明模块有必要而凭空增加适配器、配置选项或未来用例。
将行为放在其所属模块中
将维护相同规则、数据或工作流的代码归在一起。如果拆分模块能为独立变化的职责提供明确归属,就应拆分。不要提取只移动代码、却没有减少调用方理解成本的包装器。
模块可以使用更小的内部模块和依赖接口。如果应用调用方不需要这些细节,就应将其保留在内部。测试可以使用内部模块的接口来检查其自身行为,而无需将该接口纳入应用 API。
当某项需求导致反复变通时,应先勾勒能妥善满足该需求的设计,再选择经授权且能保持必要兼容性的迁移方案。现有结构是背景信息,并不意味着必须保留每一层。这种比较并不授权无关的重写。
让行为可测试
通过合适的接口接收依赖,以便测试能够控制这些依赖,而无需替换待测试的行为。例如,将支付网关传入订单处理逻辑,测试就能检验支付失败的情况,而无需实际扣款。
当计算和副作用各自承担不同职责时,应将它们分开。返回计算出的折扣,就能在不修改购物车或调用存储的情况下测试该规则。
通过拥有相关行为的接口来测试行为。如果内部测试能捕获其他测试无法发现的重要故障,就保留这些测试。不要暴露私有状态、添加公共方法,或为了简化测试准备而让调用方了解实现细节。
如果精简方法和参数能去掉调用方不需要的选项,就应当精简。方法更少并不一定意味着要测试的行为更少。
进一步指导
- 将相关模块合并,同时保留依赖处理方式和有用的测试时,请阅读 DEEPENING.md。
- 当类型、外部输入或类型转换会影响代码必须执行的规则时,请阅读 TYPE-DESIGN.md。
- 当有状态代码重复执行决策、状态转换规则或查找操作,却没有明确的归属对象时,请阅读 DOMAIN-STRUCTURE.md。
- 检查需求和仓库约定后,如果仍有多种合理的设计方案,请阅读 DESIGN-IT-TWICE.md。