mcpField
C
codebase-design

codebase-design

github.com/utarn开发4977 次安装9 周前更新
代码仓库

关于 codebase-design

面向开发者和编码 Agent 的代码设计技能,聚焦于以小接口承载丰富行为的深模块。它提供统一术语和原则,帮助改善可测试性、调用方的能力杠杆及维护局部性。

主要功能

  1. 01设计深模块
  2. 02降低接口复杂度
  3. 03有意识地设置接缝
  4. 04显式传入依赖
  5. 05通过模块接口测试

适用场景

  1. 01重构转发型模块
  2. 02设计可测试的接口
  3. 03确定行为应放置的位置

安装

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

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

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

包含的文件

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

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

详细介绍

代码库设计

设计深层模块:在简洁的接口后封装大量行为,放置在清晰的接缝处,并可通过该接口进行测试。在设计或重构代码时,始终使用这套语言和原则。目标是为调用方提供杠杆效益,为维护者提升局部性,并让所有人都能轻松测试。

术语表

请严格使用以下术语——不要替换成“组件”“服务”“API”或“边界”。统一用语正是关键所在。

模块——任何具有接口和实现的东西。刻意不限定规模:函数、类、程序包,或跨层切片都可以。避免使用: 单元、组件、服务。

接口——调用方正确使用模块所必须了解的一切:类型签名,以及不变量、顺序约束、错误模式、必需配置和性能特征。避免使用: API、签名(范围过窄——它们只指类型层面的表面)。

实现——模块内部的内容,即其代码主体。它与适配器不同:一个东西可以是实现很大的小型适配器(Postgres 仓库),也可以是实现很小的大型适配器(内存中的伪实现)。讨论接缝时使用“适配器”;其他情况使用“实现”。

深度——接口所提供的杠杆效益:调用方(或测试)每学习单位接口所能使用的行为量。大量行为藏在简洁接口后面的模块是深层模块;接口几乎与实现一样复杂的模块是浅层模块。

接缝(Michael Feathers)——无需在某处编辑即可改变行为的位置;即模块接口所在的位置。接缝放在哪里是独立的设计决策,与接缝后面放什么不同。避免使用: 边界(该词在 DDD 中常指有界上下文,容易引起歧义)。

适配器——在接缝处满足某个接口的具体事物。它描述的是角色(填补什么位置),而不是内容(内部有什么)。

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

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

深层与浅层

深层模块 = 简洁的接口 + 大量实现:

┌─────────────────────┐
│   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 关键字或类的公有方法:范围太窄——这里的接口包括调用方必须了解的所有信息。
  • 「边界」:这个词与 DDD 中的限界上下文含义重叠。请说接缝或接口。

深入探索

  • 基于依赖关系深化集群——参见 DEEPENING.md:依赖类别、接缝规范,以及以替换而非分层的方式进行测试。
  • 探索不同的接口设计方案——参见 DESIGN-IT-TWICE.md:启动多个并行子 Agent,以截然不同的方式设计接口,然后根据深度、局部性和接缝位置进行比较。

评论

支持 Markdown