mcpField
B
balanced-coupling

balanced-coupling

github.com/vladikk开发57079 次安装30 周前更新
代码仓库

关于 balanced-coupling

balanced-coupling 是介绍 Vlad Khononov 耦合模型的参考技能。它帮助软件设计者从集成强度、距离和易变性分析耦合如何影响模块化及变更成本。

主要功能

  1. 01说明四种集成强度
  2. 02分析组件间距离
  3. 03纳入易变性因素
  4. 04联系耦合与模块化
  5. 05结合领域驱动设计概念

适用场景

  1. 01评估系统设计中的耦合
  2. 02分析跨组件变更成本

安装

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

$npx skills add https://github.com/vladikk/modularity --skill balanced-coupling

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

详细介绍

平衡耦合模型

本指南全面介绍 Vlad Khononov 所描述的平衡耦合模型。本文综合了 coupling.dev 上配套博客中的完整模型,涵盖其基础、维度、平衡机制、与先前耦合模型的关系,以及与领域驱动设计的联系。


1. 基础:耦合为何重要

1.1 耦合并非敌人

长期以来,“耦合”一词一直是糟糕设计的代名词。平衡耦合模型对这一假设提出了挑战。根据词典,耦合只是一种用于连接各部分的装置。如果系统是一组相互作用以实现目标的组件,那么耦合就是连接这些组件、使目标得以实现的方式。耦合让系统的价值大于其各部分价值的总和。

你无法用完全独立的组件构建系统——那与“系统”一词本身的定义相悖。问题不在于是否耦合,而在于如何耦合。有些耦合形式会带来模块化;另一些则会造成复杂性。平衡耦合模型不把耦合视为需要消除的麻烦,而是将其视为一种可以有意识地运用的设计工具。

1.2 复杂性(基于 Cynefin)

该模型采用 Cynefin 框架对复杂性的定义:

  • 清晰(简单): 你进行一项更改,就能确切知道结果会是什么。
  • 繁杂: 你不知道结果,但专家知道,你可以向他们咨询。
  • 复杂: 确定结果的唯一方法就是进行更改并观察会发生什么。只有事后才能识别因果关系。
  • 混乱: 即使事后,也无法识别行动与结果之间的关系。

在软件设计中,复杂性指的是这样一种系统:更改的结果无法预测。模块化设计的目标是避免出现只有事后才能确定更改影响的情况。

重要的是,复杂性具有主观性——它同时取决于系统设计和我们的认知局限:

complexity = f(system design, cognitive limits)

研究表明,人类的工作记忆一次只能容纳 4 +/- 1 个信息单元。当进行更改所需的信息超过这一认知容量时,设计就具有复杂性。

1.3 模块化:复杂性的反面

如果复杂性使更改结果无法预测,那么模块化就是它的反面。模块化设计满足两个标准:

  1. 需要更改系统的哪些部分是清楚的。
  2. 更改的结果清楚且可预测。

模块化将系统的目标延伸到未来。这并不意味着要预先实现未来的需求——那是不可能的。它的意思是,通过尽量减少所需的认知负担,让实现合理的未来更改变得容易。

系统是一组相互作用以实现目标的组件。即使是“泥球”也能实现当前目标。模块化设计通过确保系统在目标不断演变时仍能继续实现目标,从而拓展系统的目标。

1.4 共同成因

复杂性和模块化是相互对立的概念,但它们由同一个根本因素驱动:耦合。组件的连接方式决定了系统是模块化还是复杂。


2. 耦合的三个维度

两个组件发生耦合时,决定系统是模块化还是复杂的,是它们之间的连接,而非组件本身。平衡耦合模型从三个维度评估这种连接:集成强度、距离和易变性。

2.1 集成强度(知识)

集成要求各组件相互交换知识。共享的知识越多,一个组件的变更引发其他组件连锁变更的概率就越高。

与其尝试定量“衡量”知识,不如对知识类型进行分类。每种类型涉及的共享知识量都明显不同。这就是集成强度模型,它划分了四个级别(从最强/知识最多到最弱/知识最少):

侵入式耦合(最强)

当集成依赖私有接口或实现细节时,就会出现这种情况,例如内部数据库、私有对象、未记录的 API。我们必须假设组件实现的所有知识都是共享的。侵入式耦合既脆弱又隐式——被“侵入”组件的作者甚至可能不知道这种集成的存在。

对应经典模块耦合模型中的内容耦合(病态耦合)。

功能耦合

关注点从组件的实现方式转向其实现的内容:即其功能需求。当多个组件共享其业务需求的知识,并且这些需求演进时必须一起变更,就会出现这种情况。

一种极端情况是重复知识——同一业务规则同时在前端和后端实现。如果规格变更未能同时反映在两者中,系统就会进入不一致状态。

与侵入式耦合一样,功能耦合也可能是隐式的:组件中重复的业务逻辑虽然没有显式耦合,但仍需要一起变更。

对应经典模块耦合模型中的公共耦合、外部耦合和控制耦合。

模型耦合

当组件共享业务领域模型的知识时,就会出现这种情况。如果模型因新的领域洞察而发生变化,所有耦合的组件都必须相应变更。

领域驱动设计强调使用多个专用模型,而不是一个庞大且包罗万象的模型。当组件共享其中一个模型时,就会产生模型耦合。

对应经典模块耦合模型中的标记耦合。

契约耦合(最弱)

共享知识量最低的级别。集成契约会封装实现细节、功能需求和业务模型,使集成变得显式且稳定。

引入契约的设计模式包括:Facade、开放主机服务/发布语言(DDD)、防腐层(DDD)、数据传输对象(DTO)。

对应经典模块耦合模型中的数据耦合,并对其进行了细化,区分了内部模型与显式集成契约。

隐式-显式光谱

集成强度的四个级别也反映了共享知识的隐式或显式程度:

  • 契约耦合 = 最显式
  • 模型耦合 = 显式,但范围更广
  • 功能耦合 = 可能是隐式的
  • 侵入式耦合 = 最隐式、最脆弱
集成强度的细分程度

在每个级别内,可以使用共现性的级别进行更细致的比较:

  • 静态共现性级别(名称、类型、含义、算法、位置)可用于比较契约耦合和模型耦合的程度。
  • 动态共现性级别(执行、时序、值、身份)可用于比较功能耦合的程度。
2.2 距离

耦合组件之间的物理和逻辑距离会影响变更成本。在同一文件中共同演进两个方法,远比在不同微服务或独立系统中的两个对象容易。

距离会随着抽象层级的提升而增加:方法 -> 对象 -> 命名空间/包 ->(微)服务 -> 系统。在每个层级,同时变更所需的认知投入和协调成本都会增加。

距离取决于所观察的抽象层级

距离不是绝对的度量,而是相对于正在分析的抽象层级而言。模型具有分形特性:无论观察的是类中的方法、模块中的类、服务中的模块,还是系统中的服务,都适用同一条平衡规则。

在任何给定的抽象层级上,最高距离就是该层级的边界。如果一个项目由单个服务和多个模块组成,那么这些模块之间的距离就是该情境中的最高距离。相同的原则仍然适用:如果两个模块具有较高的集成强度,那么这种耦合就是不平衡的——即使从整个系统的角度来看,它们属于同一个服务。

要遇到高距离耦合问题,并不需要微服务或分布式系统。即使是由一个人开发、只有一个可部署单元的项目,也可能存在模块之间的紧密耦合,因为在模块这一抽象层级上,跨模块集成就是可达到的最高距离。

生命周期耦合(反作用力)

与变更成本相对的是生命周期耦合:组件越接近,其生命周期绑定得越紧密——它们必须一起测试和部署。许多“从单体迁移到微服务”的重构,主要是为了减少生命周期依赖。

因此,距离是一种权衡:距离较近意味着共同演进的成本更低,但生命周期耦合更紧密;距离较远意味着生命周期更独立,但共同演进的成本更高。

距离具有社会技术属性

距离并非纯粹的技术因素。组织结构会显著影响距离:

  • 由同一团队实现的两个微服务,其有效距离小于由不同团队实现的两个微服务,即使代码结构完全相同。
  • 多个团队必须协调变更时,所需的认知投入和协作开销会大幅增加。

这体现了康威定律:组织结构会塑造系统架构。

运行时耦合

组件之间的运行时依赖也会影响距离。同步集成比异步集成会造成更紧密的生命周期耦合。紧密的运行时依赖会将组件的生命周期绑定在一起——一个组件发生故障,可能会连带影响另一个组件。

降低运行时耦合(例如通过异步消息传递)会增加组件之间的有效距离。为保持耦合平衡,应通过降低集成强度来补偿距离的增加。

2.3 波动性

集成强度反映变更跨越边界传播的可能性。距离决定级联变更的成本。作为第三个维度,波动性反映组件需要变更的概率。

波动性越高,设计问题就越突出,也越“痛苦”。一个集成强度高、距离远但从不变更的组件,实际上不会造成问题。但如果同一个组件频繁变更,它就会持续带来痛苦。

使用 DDD 的子域评估波动性

未来无法预测,但领域驱动设计中的子域概念提供了一些实用的判断依据:

  • 核心子域(竞争优势、“有意思的问题”)-> 波动性最高。公司会持续优化和改进这些子域。
  • 支撑子域(没有竞争优势,没有现成解决方案、“枯燥的问题”)-> 波动性低。通常是 CRUD 数据录入或 ETL 流程。
  • 通用子域(已解决的问题、现成解决方案)-> 功能波动性低,实现波动性不定。功能本身已经成熟,很少变化——这是一个已解决的问题。不过,实现方式可能会有波动:更换提供商、采用不同技术或并行运行多个实现,都是现实可行的变化方向。例如,TTS 引擎(AWS Polly)属于通用功能,但切换到 Google TTS、ElevenLabs 或可自托管的解决方案都是合理预期。在这种情况下,强有力的集成契约对于封装提供商特定的知识至关重要。反过来,有些通用子域的实现方式本质上不易更换——选择 Auth0 处理身份和访问意味着,无论契约设计如何,改用 Clerk 都会是一个重大事件。设计通用子域的边界时,应评估更换实现方式或同时使用多种实现方式的可能性,并据此落实集成契约。

Wardley Maps 的商品化轴为波动性提供了另一种视角。

本质性与偶然性的(非)波动

一个关键区别:

  • 偶然波动:频繁提交并不一定意味着业务领域波动。糟糕的设计——未能有效管理集成强度和距离——可能会导致组件频繁变化。
  • 偶然不波动:一个看似稳定的组件可能并非本质上稳定。业务可能希望改变它,但修改设计不佳的系统风险高、工作量大,因此无法实现。

因此,应从业务领域视角(本质波动性)评估波动性,而不能只看提交历史。


3. 分形几何

软件设计具有分形特性。你会在不同抽象层级设计组件及其交互,从对象、模块、服务到整个系统之间的交互。平衡耦合模型适用于所有这些层级。

不过,集成强度各级别的含义会随着所处抽象层级而变化。什么构成集成契约、实现模型或功能需求,在每个层级都各不相同。较低抽象层级的集成契约(例如,类的公共接口),在更高抽象层级(例如,服务边界内)可能只是实现细节。同样,两个对象在同一模块内共享的模型,在对象层级属于模型耦合,但从系统层级看,它完全属于模块内部,既不可见,也无关紧要。

距离也是如此。在任一给定抽象层级上,最高距离就是该层级的边界。如果你正在设计单个服务内的模块,那么跨模块边界就是你的最高距离,尽管从系统视角看,这些模块都位于同一处。如果你正在设计系统之间的交互,那么跨系统边界就是最高距离。

因此,应用该模型时,必须始终明确当前所处的抽象层级。这决定了:

  • 集成强度:在此层级,什么算作契约、模型、功能依赖或侵入式依赖。
  • 距离:在此层级,什么构成最低距离和最高距离。

如果没有这一背景,同一种耦合关系会因观察者的视角不同而显得平衡或不平衡。


4. 平衡各个维度

4.1 四种极端组合(强度 x 距离)

通过集成强度与距离的极端组合,可以理解两者之间的相互作用:

低距离高距离
低强度低内聚(复杂性)——无关组件彼此距离很近,增加认知负荷,并逐渐演变成大泥球。松散耦合(模块化)——低强度最大限度地减少级联变更。高距离与之平衡。
高强度高内聚(模块化)——组件必须协同演进,但低距离使级联变更的成本很低。紧密耦合(复杂性)——频繁的级联变更(高强度)实施起来代价高昂(高距离),这是迈向分布式单体的一步。
4.2 平衡规则

关键洞见:当集成强度和距离相互平衡时(一个高、一个低),就会产生模块化。当两者相等时(都高或都低),就会产生复杂性。

用二元逻辑表示:

MODULARITY = STRENGTH XOR DISTANCE
COMPLEXITY = STRENGTH AND DISTANCE

这解释了为什么“松散耦合”和“高内聚”都值得追求——它们都是平衡耦合的形式,只是维度取值不同。

4.3 实用平衡(加入易变性)

Eric Evans 指出:“大型系统中并非所有部分都设计得很好。”易变性这一维度引入了实用主义。

如果耦合不平衡(高强度 + 高距离),但预计组件不会发生变化,那么低易变性会抵消其影响。一个具有高侵入式耦合、却从不演进的遗留系统是可以容忍的——这种耦合在技术上“糟糕”,但在实践中无害。

完整的平衡表达式:

BALANCE = (STRENGTH XOR DISTANCE) OR NOT VOLATILITY

这意味着,在以下任一情况下,耦合都是平衡的:

  • 强度和距离相互制衡(XOR),或者
  • 组件的易变性低(无论强度和距离之间的关系如何)。

注意:本文为便于说明,采用了简化的二元模型。《Balancing Coupling in Software Design》一书包含更复杂的数学模型,采用连续值而非二元极值。


5. 与领域驱动设计的联系

平衡耦合模型与 DDD 有着深刻的联系。许多 DDD 模式和实践都与耦合的各个维度直接相关:

战略设计
  • 限界上下文为共享业务领域模型的组件定义物理边界(中等距离、共享生命周期)(模型耦合)。
  • 限界上下文集成模式(共享内核、合作关系、遵从者、防腐层、开放主机服务、各行其道)决定知识共享的程度。临时性模式允许更多知识交流;正式模式则会减少交流。这既体现了集成强度,也体现了距离的组织层面。
  • 一些集成模式(防腐层、开放主机服务)会将内部模型封装在专用于集成的契约后面(契约耦合),适用于距离较大的情况。
战术设计
  • 聚合和值对象模式将共享事务边界(高度功能耦合)的行为放在一起(距离较短)。这是一种平衡:较强的耦合由较短的距离抵消。
  • 业务逻辑实现模式(事务脚本、活动记录、领域模型、事件溯源领域模型)根据领域复杂度选择,而领域复杂度与易变性直接相关。
子领域与易变性
  • 核心子领域 = 易变性最高 -> 需要最谨慎地管理耦合
  • 支撑子领域 = 易变性低 -> DDD 明确允许在此采用务实的捷径(“大型系统并非所有部分都能设计完善” — Eric Evans),因为低易变性可以抵消不平衡的耦合
  • 通用子领域 = 功能易变性低(问题已解决),但实现易变性各不相同。当切换提供商或使用多种实现的可能性较高时(例如 TTS 引擎、支付网关、通知服务),应强制采用严格的集成契约,以封装特定于提供商的知识。当切换成本本质上较高且不太可能切换时(例如身份提供商、核心数据库),可以采用务实的捷径——但在做出决定前,应验证这一假设并集思广益,找出可能的变更方向

DDD 的子领域是评估平衡耦合模型中组件易变性维度的主要工具。


6. 总结:一览模型

+-----------------------------------------------------+
|                 BALANCED COUPLING                     |
|                                                       |
|  Three Dimensions:                                    |
|  +---------------------+                              |
|  | Integration Strength | -> Likelihood of cascading  |
|  | (Knowledge shared)   |   changes                   |
|  |                      |                              |
|  | Levels:              |                              |
|  |  Intrusive (highest) |                              |
|  |  Functional          |                              |
|  |  Model               |                              |
|  |  Contract  (lowest)  |                              |
|  +---------------------+                              |
|  +---------------------+                              |
|  | Distance             | -> Cost of cascading         |
|  | (Socio-technical)    |   changes                   |
|  |                      |                              |
|  | Factors:             |                              |
|  |  Code structure      |                              |
|  |  Org structure       |                              |
|  |  Runtime deps        |                              |
|  +---------------------+                              |
|  +---------------------+                              |
|  | Volatility           | -> Probability of change     |
|  | (Rate of change)     |   occurring at all           |
|  |                      |                              |
|  | Evaluated via:       |                              |
|  |  DDD subdomains      |                              |
|  |  Wardley Maps        |                              |
|  |  (NOT commit history)|                              |
|  +---------------------+                              |
|                                                       |
|  Balance Rule:                                        |
|  MODULARITY = STRENGTH XOR DISTANCE                   |
|  BALANCE = (STRENGTH XOR DISTANCE) OR NOT VOLATILITY  |
|                                                       |
|  Key Insight:                                         |
|  Loose coupling and high cohesion are BOTH forms of   |
|  balanced coupling, not separate concepts.             |
+-----------------------------------------------------+
核心原则
  1. 耦合并非天生有害。 它让系统得以运行。目标是平衡耦合,而非消除耦合。
  2. 耦合体现在三个维度上。 只评估其中一个维度(例如依赖数量)会忽略全貌。
  3. 模块化 = 维度平衡。 当集成强度与距离相互抵消时,设计就是模块化的。
  4. 复杂性 = 维度失衡。 当强度与距离都高(紧密耦合 / 分布式单体)或都低(内聚性低 / 大泥球)时,设计就会变得复杂。
  5. 易变性带来务实考量。 低易变性可以抵消不平衡的耦合。系统并非每个部分都需要完美设计。
  6. 该模型具有分形特征。 它适用于每个抽象层级:方法、对象、包、服务和系统。
  7. 该模型兼具社会技术属性。 组织结构对距离的影响不亚于代码结构。
  8. 已有模型是被纳入,而非被取代。 模块耦合为集成强度等级提供了基础。共因性为这些等级细分了程度。平衡耦合模型则弥补了两者的盲点。

与用户沟通

不要假设用户熟悉领域驱动设计术语。讨论易变性和子域分类时,请用通俗语言解释这些概念:

  • 核心子域 — 为业务带来竞争优势的系统部分;是组织正在积极投入的“有趣问题”。变化频繁。
  • 支撑子域 — 业务需要,但无法带来差异化优势,且没有现成解决方案的部分。通常是数据录入、ETL 或内部工具等简单工作流。很少变化。
  • 通用子域 — 已有成熟解决方案的问题,例如身份验证、支付处理和邮件发送。其功能已经成熟,但具体的服务提供商或技术可能会变化。

请使用 DDD 术语,但首次使用时务必附上简短的通俗解释。不要指望用户在缺少上下文的情况下知道“核心子域”是什么意思。

评论

支持 Markdown