---
title: 什么是耦合(共生)，什么是内聚
date: "2020-05-30 21:30"
tags: ["架构"]
published: true
---

## 耦合和内聚的适用范围

架构设计常以“高内聚、低耦合”为目标，但实际讨论中，这两个概念的边界经常不清楚：

- 将所有依赖都视为应避免的耦合。
- 知道要“高内聚”，但缺少衡量方式。
- 无法判断某个耦合是否会带来额外的变更成本。

在讨论小程序架构演进中的“逻辑内聚”时，需要先明确这两个概念的定义和度量范围。

## 概念定义

### 基础定义

| 概念 | 定义 | 说明 |
|------|------|------|
| **耦合（Coupling）**<br/>共生（Connascence） | 有 A、B 两个模块，修改 A 时必须修改 B，则 B 耦合 A | • 耦合发生在模块之间，不表示父子层级关系<br/>• 静态类型语言中可将其理解为由编译错误提示的连接约束<br/>• 业务上经常同时修改的代码不必然构成耦合 |
| **内聚（Cohesion）** | 若干模块因某种关系被包含在一起的紧密程度 | • 具有父子层级关系<br/>• 内聚 = 子模块间耦合数量 ÷ 子模块总数<br/>• 关注代码组织，而非业务逻辑 |

### 关系本质

内聚依赖模块内部的耦合关系，二者都是模块化的度量指标。设计时需要识别会扩大修改范围或增加运行时约束的耦合。

## 内聚性的 7 个层次

根据《软件架构：架构模式、特征及实践指南》，内聚性有7个层次（按强度从弱到强）：

![e95c2a33-03a5-4dac-ac5d-2d5d73a940d7](../../assets/2020/05-30-什么是耦合(共生)，什么是内聚/e95c2a33-03a5-4dac-ac5d-2d5d73a940d7.png)

### 内聚性层次表

| 层次 | 类型 | 定义 | 特征 | 示例 |
|------|------|------|------|------|
| 1 | **偶然内聚** | 模块内元素间没有任何联系 | 最低内聚度，巧合组合 | 工具类大杂烩 |
| 2 | **逻辑内聚** | 相关功能组合，通过参数选择 | 功能相关但实现独立 | 各种验证函数放在一起 |
| 3 | **时间内聚** | 需要同时执行的动作组合 | 时间相关性 | 初始化模块 |
| 4 | **过程内聚** | 按特定顺序执行的处理元素 | 顺序相关但无数据传递 | 操作流程步骤 |
| 5 | **通信内聚** | 操作同一数据结构的元素 | 数据相关性 | 用户信息CRUD |
| 6 | **顺序内聚** | 前一个输出是后一个输入 | 数据流相关性 | 数据处理管道 |
| 7 | **功能内聚** | 所有元素为完成同一功能存在 | 最高内聚度，单一职责 | 计算水费模块 |

### 内聚性改进示例

**低内聚的代码**：
```javascript
class Module {
  a = 1;
  b = 2;
  
  fn1() {
    this.a = 1;
  }
  
  fn2() {
    return this.b;
  }
  
  fn3() {
    console.log('fn');
  }
}
```

**改进后的高内聚代码**：
```javascript
class ModuleA {
  a = 1;
  
  fn1() {
    this.a = 1;
  }
}

class ModuleB {
  b = 3;
  
  fn2() {
    return this.b;
  }
}

class ModuleC {
  fn3() {
    console.log('fn');
  }
}
```

## 用共生性（Connascence）度量耦合

1996年 Meilir Page-Jones 在《What Every Programmer Should Know About Object-Oriented Design》中完善了耦合的度量，命名为"Connascence"：

> 如果一个组件的改变会要求另一个组件进行修改，才能保持系统的整体正确性，那么这两个组件就是共生的。

### 静态共生性（Static Connascence）

#### 1. 名称共生性（Connascence of Name, CoN）

**特征**：引用相同的名称

```javascript
class Customer {
  Age = 25; // 第3行
}

const customer = new Customer();
console.log(customer.Age); // 第8行 - 如果第3行改名，这里必须同步修改
```

现代 IDE 可以自动处理名称重命名，因此这类共生通常较容易管理。

#### 2. 类型共生性（Connascence of Type, CoT）

**特征**：必须就数据类型达成一致

```javascript
function sayHello(name: string): string {
  return `Hello ${name}`;
}

sayHello(123); // 错误：类型不匹配
```

#### 3. 意义共生性（Connascence of Meaning, CoM）

**特征**：必须就特定值的含义达成一致（Magic Number）

```javascript
// 不好的例子
if (status === 1) { /* 激活 */ }
if (status === 2) { /* 禁用 */ }

// 改进
const STATUS = {
  ACTIVE: 1,
  DISABLED: 2
};
if (status === STATUS.ACTIVE) { /* 激活 */ }
```

#### 4. 位置共生性（Connascence of Position, CoP）

**特征**：必须就顺序达成一致

```javascript
// 位置耦合
function createUser(firstName, lastName, address) {
  // ...
}
createUser("Bob", "Marley", "Jamaica");

// 改进：名称耦合
function createUser(userInfo) {
  const { firstName, lastName, address } = userInfo;
  // ...
}
createUser({
  firstName: "Bob",
  lastName: "Marley", 
  address: "Jamaica"
});
```

#### 5. 算法共生性（Connascence of Algorithm, CoA）

**特征**：必须就算法达成一致

```javascript
// 客户端和服务端必须使用相同的加密算法
const clientHash = md5(password + salt);
const serverHash = md5(password + salt); // 必须保持一致
```

### 动态共生性（Dynamic Connascence）

#### 1. 执行共生性（Connascence of Execution, CoE）

**特征**：代码执行顺序的耦合

```javascript
// 错误的顺序
email = new Email();
email.setRecipient("foo@example.com");
email.setSender("me@me.com");
email.send(); // 先发送了
email.setSubject("whoops"); // 后设置主题

// 正确的顺序
email = new Email();
email.setRecipient("foo@example.com");
email.setSender("me@me.com");
email.setSubject("Important Message");
email.send();
```

#### 2. 时间共生性（Connascence of Timing, CoT）

**特征**：执行时间上的耦合

```javascript
// Bootstrap Modal 的时间耦合问题
$(element).modal('hide');
$(element).modal('show'); // 错误！动画未完成

// 正确的做法
$(element).modal('hide');
$(element).on('hidden.bs.modal', () => {
  $(element).modal('show'); // 等待动画完成
});
```

#### 3. 值共生性（Connascence of Values, CoV）

**特征**：必须同时更改多个值

```javascript
// 测试中的值耦合
function calculateTotal() {
  return 50; // 函数返回值
}

// 测试代码
expect(calculateTotal()).toBe(50); // 必须保持一致
```

#### 4. 身份共生性（Connascence of Identity, CoI）

**特征**：必须引用同一个对象实例

```javascript
// 问题代码：对象副本导致的身份耦合
function changeMovieTitle(movie, newTitle) {
  const updatedMovie = { ...movie, title: newTitle };
  return updatedMovie; // 返回新对象
}

function displayMovie(movie) {
  console.log(movie.title); // 显示旧标题
}

// 改为返回对象 ID
function changeMovieTitle(movieId, newTitle) {
  // 直接修改数据库
  database.updateMovie(movieId, { title: newTitle });
  return movieId;
}
```

## 共生性的三个属性

### 1. 强度（Strength）

![1745476869659](../../assets/2020/05-30-什么是耦合(共生)，什么是内聚/1745476869659.svg)

静态共生性通常可由 IDE 和静态分析工具检测；动态共生性需要在运行时识别，处理成本更高。
### 2. 局部性（Locality）

共生关系越靠近同一模块边界，越容易被局部修改和测试覆盖。
- **同一模块内**：可以有较强的共生性
- **跨模块**：应该使用较弱的共生性

### 3. 程度（Degree）

共生关系影响的模块和类越少，变更风险越容易控制。
- **影响几个类**：可接受
- **影响几十个类**：需要重构

## 实践指南

### Page-Jones 的指导原则

1. 通过模块化拆分降低整体共生性。
2. 减少跨越模块边界的共生性。
3. 将完成同一职责所需的共生关系保留在模块内部。

### Jim Weirich 的建议

1. 将强共生性改为较弱的共生性。
2. 随着模块距离增加，使用较弱的共生性。

### 实际操作建议

#### 设计阶段

1. **模块边界设计**
   - 明确模块职责边界
   - 最小化跨模块依赖
   - 优先使用名称共生性

2. **接口设计**
   - 避免位置耦合，使用命名参数
   - 减少算法耦合，抽象通用接口
   - 控制类型耦合的传播范围

#### 开发阶段

1. **代码组织**
   - 相关功能聚集在同一模块
   - 避免循环依赖
   - 清晰的模块导入/导出

2. **重构策略**
   - 识别高耦合点
   - 逐步降低共生性强度
   - 提高模块内聚性

#### 维护阶段

1. **监控指标**
   - 模块间依赖数量
   - 共生性强度分布
   - 变更影响范围

2. **持续改进**
   - 定期重构高耦合模块
   - 优化模块边界划分
   - 提升代码可读性

## 在前端项目中的应用

### 组件设计

```javascript
// 低内聚：功能混杂
class UserComponent {
  renderProfile() { /* 渲染用户信息 */ }
  validateForm() { /* 表单验证 */ }
  sendEmail() { /* 发送邮件 */ }
  calculateAge() { /* 计算年龄 */ }
}

// 高内聚：单一职责
class UserProfile {
  renderProfile() { /* 只负责渲染 */ }
}

class UserValidator {
  validateForm() { /* 只负责验证 */ }
}

class EmailService {
  sendEmail() { /* 只负责邮件 */ }
}
```

### 状态管理

```javascript
// 强耦合：直接修改全局状态
function updateUser(userId, data) {
  window.globalState.users[userId] = data; // 位置耦合
  window.globalState.lastUpdate = Date.now(); // 值耦合
}

// 弱耦合：通过接口操作
function updateUser(userId, data) {
  userStore.update(userId, data); // 名称耦合
  eventBus.emit('user:updated', { userId, data }); // 名称耦合
}
```

## 结论

模块之间需要通过接口、类型和数据协议协作，因此耦合无法被完全消除。应优先识别跨模块的动态共生、位置共生和算法共生，并把影响范围限制在明确的模块边界内。模块内部可以保留服务同一职责所需的关联；跨模块接口则应减少隐含顺序、时间和共享值约束。

---

**相关阅读**：
- [系统可维护性思考](./06-01-系统可维护到底指的是什么.md)
- [复杂性管理](../2016/05-11-解决复杂问题.md)
- [微前端架构思考](../2017/02-19-微前端思考.md)

**参考资料**：
- [Structured Design - Edward Yourdon & Larry Constantine](https://ia801702.us.archive.org/17/items/Structured_Design_Edward_Yourdon_Larry_Constantine/Structured_Design_Edward_Yourdon_Larry_Constantine.pdf)
- [Connascence.io](https://connascence.io/)
- [软件架构：架构模式、特征及实践指南](https://book.douban.com/subject/35487561/)
