Communitygithub.com

Castle-Solutions/landing

用于设计深度模块的共享词汇。当用户需要设计或改进模块接口、寻找深化机会、确定接缝位置、使代码更易测试或更易于AI导航,或其他技能需要深度模块词汇时使用。

landing 是什么?

这个技能提供了一套精确的、专为“深度模块”设计而定义的共享词汇表,旨在帮助开发者系统性地思考和改善代码设计。它明确区分了模块、接口、实现、深度、接缝、适配器等核心概念,并避免了“组件”、“服务”、“API”等常见但语义模糊的术语。该技能的核心目标是设计出行为丰富但接口简洁的深度模块,从而为调用方提供最大的杠杆效应,为维护者提供代码修改的局部性,并提升所有参与方的可测试性。通过遵循这里的原则,开发者可以更清晰地分析模块的职责与边界,找到合适的接缝位置,并设计出更易测试和AI理解的接口。该技能支持作为AI编码助手的指南,在代码设计或重构的任何阶段使用,并能与DEEPENING.md和DESIGN-IT-TWICE.md等其他指导文件协同工作,进一步深化设计实践。

兼容平台~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/Castle-Solutions/landing/tree/HEAD/.agents/skills/codebase-design

在你喜欢的 AI 中提问

打开一个已预加载此 Agent Skill 的新对话。

文档

代码设计

设计深度模块:大量行为隐藏在简洁的接口之后,接口位于清晰的接缝处,并可通过该接口进行测试。在设计和重构代码的任何地方使用这些语言和原则。目的是为调用方提供杠杆效应,为维护者提供局部性,为所有人提供可测试性。

术语表

请精确使用这些术语:不要用“组件”、“服务”、“API”或“边界”来替代。保持语言一致是重点。

模块:任何具有接口和实现的事物。刻意保持规模无关性:可以是一个函数、类、包或跨层切片。避免使用:单元、组件、服务。

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

实现:模块内部的内容,其代码主体。与适配器不同:某个东西可以是带有大型实现的小型适配器(如PostgreSQL仓库),也可以是带有小型实现的大型适配器(如内存模拟)。当讨论主题是接缝时,使用“适配器”;否则使用“实现”。

深度:接口处的杠杆效应。调用方(或测试)每学习一个接口单元所能触发的行为量。当一个模块通过简洁的接口封装了大量行为时,它被称为深度模块;当接口的复杂度和实现几乎相当时,则称为浅度模块。

接缝 (Michael Feathers):能够在不直接修改某处的情况下改变其行为的位置;它是模块接口所在的位置。确定接缝的位置本身就是一个设计决策,与接口背后是什么不同。避免使用:边界(与DDD的限界上下文概念混淆)。

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

杠杆效应:调用方从深度中获得的好处。即每学习一个接口单元,就能获得更多的能力。一份实现能够回馈N个调用点和M个测试。

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

深度与浅度对比

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

┌─────────────────────┐
│   简洁的接口       │  ← 少量方法,简单参数
├─────────────────────┤
│                     │
│  深层的实现         │  ← 隐藏复杂逻辑
│                     │
└─────────────────────┘

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

┌─────────────────────────────────┐
│         大型接口                 │  ← 许多方法,复杂参数
├─────────────────────────────────┤
│  薄薄的实现                     │  ← 只是透传
└─────────────────────────────────┘

在设计接口时,请问:

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

原则

  • 深度是接口的属性,而非实现的属性。 一个深度模块的内部可以由可模拟、可替换的小部分构成;但它们不是接口的一部分。一个模块可以拥有内部接缝(对其实现私有,由其自身的测试使用)以及位于其接口处的外部接缝。
  • 删除测试。 想象删除该模块。如果复杂性消失,那它就是个透传。如果复杂性重新出现在N个调用者那里,那它本身就值得存在。
  • 接口即测试面。 调用者和测试跨越相同的接缝。如果你想绕过接口进行测试,那模块的形状可能有问题。
  • 一个适配器意味着一个假设的接缝。两个适配器才意味着一个真实的接缝。 除非有东西确实跨接缝变化,否则不要引入接缝。

可测试性设计

好的接口使测试变得自然:

  1. 接受依赖,而不是创建它们。

    // 可测试
    function processOrder(order, paymentGateway) {}
    
    // 难以测试
    function processOrder(order) {
      const gateway = new StripeGateway();
    }
    
  2. 返回结果,而不是产生副作用。

    // 可测试
    function calculateDiscount(cart): Discount {}
    
    // 难以测试
    function applyDiscount(cart): void {
      cart.total -= discount;
    }
    
  3. 表面面积小。 方法越少 = 需要的测试越少。参数越少 = 测试设置越简单。

关系

  • 模块 有且只有一个 接口(它向调用者和测试展示的表面)。
  • 深度 是 模块 的属性,依据其 接口 来衡量。
  • 接缝 是 模块 的 接口 所在之处。
  • 适配器 位于 接缝 处并满足 接口。
  • 深度 为调用者产生 杠杆效应,为维护者产生 局部性。

已放弃的表述

  • 深度作为实现行数与接口行数的比率(Ousterhout):奖励填充实现。我们使用深度即杠杆效应的概念。
  • “接口”作为TypeScript的interface关键字或类的公共方法:过于狭窄:这里的接口包含调用方必须知道的全部事实。
  • “边界”:与DDD的限界上下文混淆。请说接缝或接口。

深入探索

  • 根据依赖关系深化一个集群,请参阅 DEEPENING.md:依赖分类、接缝纪律和替换而非分层测试。
  • 探索替代接口,请参阅 DESIGN-IT-TWICE.md:启动并行子代理,以多种截然不同的方式设计接口,然后比较深度、局部性和接缝位置。

Individual skills in this repo

This repo contains 4 individual skills — each has its own dedicated page.

相关技能