mcpField
C
codebase-design

codebase-design

github.com/mattpocock设计与 UI305.7k872.4k 次安装1 天前更新
代码仓库

关于 codebase-design

codebase-design 是一项用于设计深模块的 skill,即在简单接口背后封装大量行为,并将接口放置在合适的接缝处。它为代码设计与重构提供统一术语和原则,目标是调用方的杠杆效应、维护者的局部性以及可测试性。

主要功能

  1. 01以深模块为目标,小接口隐藏复杂实现
  2. 02提供模块、接口、接缝、适配器等统一术语
  3. 03通过删除测试判断模块是否真正有价值
  4. 04强调依赖注入与返回结果而非副作用的可测试设计

适用场景

  1. 01重构过于浅层、接口与实现复杂度相近的模块
  2. 02决定依赖与实现的接缝位置
  3. 03评估是否需要为某个接缝引入第二个适配器

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/mattpocock/skills --skill codebase-design

安装后在 Claude Code 中输入/codebase-design确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

DEEPENING.md
DESIGN-IT-TWICE.md
agents/
openai.yaml

详细介绍

代码库设计

设计深模块:在一个小接口背后隐藏大量行为,放置在清晰的接缝处,并能通过该接口进行测试。在设计或重构代码时使用这套语言和原则。目标是为调用方提供杠杆效应,为维护者提供局部性,并为所有人提供可测试性。

术语表

请准确使用这些术语:不要替换为“组件”“服务”“API”或“边界”。统一的语言正是这套体系的核心。

Module(模块):任何同时具有接口和实现的东西。刻意不限定规模:可以是函数、类、包,或跨层的切片。避免使用:unit、component、service。

Interface(接口):调用方正确使用该模块所需了解的全部内容:类型签名,以及不变量、顺序约束、错误模式、必需配置和性能特征。避免使用:API、signature(范围过窄,仅指类型层面的表面)。

Implementation(实现):模块内部的全部内容,即其代码主体。与 Adapter(适配器) 不同:一个东西可以是体积小的适配器却有庞大的实现(例如 Postgres 仓储),也可以是体积大的适配器却有简单的实现(例如内存假对象)。只有在讨论接缝时才使用“adapter”;其他情况使用“implementation”。

Depth(深度):接口处的杠杆效应。调用方(或测试)每学习一单位的接口,就能够行使的行为量。当大量行为隐藏在小接口之后时,模块是深的;当接口几乎与实现一样复杂时,模块是浅的。

Seam(接缝)(Michael Feathers):无需在该位置编辑代码,即可改变行为的位置;也是模块接口所在的位置。接缝放在哪里是一项独立的设计决策,与其后面放什么是不同的问题。避免使用:boundary(与 DDD 的限界上下文含义重叠)。

Adapter(适配器):在接缝处满足某个接口的具体事物。描述的是角色(它填充什么槽位),而非实质(其内部是什么)。

Leverage(杠杆效应):调用方从深度中获得的收益。即每学习一单位接口,获得更多能力。一个实现可以在 N 个调用点和 M 个测试中持续回报。

Locality(局部性):维护者从深度中获得的收益。变更、缺陷、知识和验证集中在一个地方,而不是分散到各个调用方。修复一次,处处生效。

深模块与浅模块

深模块 = 小接口 + 大量实现:

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

浅模块 = 大接口 + 少量实现(应避免):

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

在设计接口时,可以问自己:

  • 能否减少方法数量?
  • 能否简化参数?
  • 能否在内部隐藏更多复杂性?

原则

  • 深度是接口的属性,而非实现的属性。 深模块内部可以由小的、可模拟、可替换的部件组成;这些部件只是不属于接口的一部分。模块既可以拥有内部接缝(仅对其实现可见,供其自身测试使用),也可以拥有位于接口处的外部接缝。
  • 删除测试。 设想删除该模块。如果复杂性随之消失,说明它只是一个透传层;如果复杂性在 N 个调用方中重新出现,说明它确实有存在的价值。
  • 接口即测试面。 调用方和测试穿越的是同一个接缝。如果你想测试接口之外的部分,那么该模块的形态大概率不对。
  • 一个适配器意味着假想的接缝;两个适配器才意味着真实的接缝。 除非确实有东西在该接缝处发生变化,否则不要引入接缝。

面向可测试性的设计

良好的接口能让测试变得自然:

  1. 接收依赖,而不是自行创建依赖。

    // Testable
    function processOrder(order, paymentGateway) {}
    
    // Hard to test
    function processOrder(order) {
      const gateway = new StripeGateway();
    }
    
  2. 返回结果,而不是产生副作用。

    // Testable
    function calculateDiscount(cart): Discount {}
    
    // Hard to test
    function applyDiscount(cart): void {
      cart.total -= discount;
    }
    
  3. 小表面积。 方法越少,需要的测试越少;参数越少,测试准备越简单。

关系

  • 一个 模块 恰好有一个 接口(即它向调用方和测试呈现的表面)。
  • 深度 是 模块 的属性,以其 接口 来衡量。
  • 接缝 是 模块 的 接口 所在之处。
  • 适配器 位于 接缝 处,并满足 接口。
  • 深度 为调用方带来 杠杆效应,为维护者带来 局部性。

已否决的表述

  • 将深度定义为实现行数与接口行数之比(Ousterhout):这会鼓励为实现注水。我们改用“以杠杆效应衡量深度”。
  • 将“接口”理解为 TypeScript 的 interface 关键字或类的公开方法:范围过窄,此处的接口包含调用方必须知道的每一项事实。
  • “边界(Boundary)”:与 DDD 的限界上下文(bounded context)含义重叠。请使用 接缝 或 接口。

深入阅读

  • 在给定依赖关系的前提下深化一个模块簇,参见 DEEPENING.md:涵盖依赖类别、接缝纪律以及“替换而非叠加”的测试方法。
  • 探索替代接口,参见 DESIGN-IT-TWICE.md:启动并行的子 Agent,以数种截然不同的方式设计接口,然后在深度、局部性和接缝位置上进行比较。

评论

支持 Markdown