第三章 单一职责在模块划分中的实践
单一职责原则(Single Responsibility Principle, SRP)在模块划分中并非简单的“一个模块只做一件事”,而是要求模块所承担的职责在变化维度上保持一致。当两个职责因不同的原因、在不同的时间、以不同的频率变化时,它们应当被分配到不同的模块中。本章从职责内聚、边界粒度与反模式三个维度,给出可操作的模块边界设计方法。
3.1 职责内聚原则
职责内聚衡量的是一个模块内部所包含的职责是否围绕同一变化原因组织。内聚度越高,模块的独立性与可维护性越强。根据内聚的强度,可将模块分为七个层次:
| 内聚层次 | 类型 | 变化特征 | 模块稳定性 |
|---|---|---|---|
| 最高 | 功能内聚 | 所有元素共同完成单一功能,变化原因唯一 | 极高 |
| ↑ | 顺序内聚 | 元素按数据流顺序连接,变化传递链清晰 | 高 |
| ↑ | 通信内聚 | 元素操作同一数据集,数据集变化波及全部 | 中高 |
| ↑ | 过程内聚 | 元素按执行顺序组合,顺序变化影响整体 | 中 |
| ↑ | 时间内聚 | 元素因执行时间相近而组合,时间约束变化导致重构 | 中低 |
| ↑ | 逻辑内聚 | 元素因逻辑类型相似而组合,新增类型需修改模块 | 低 |
| 最低 | 偶然内聚 | 元素无实质关联,任意需求变化都可能影响模块 | 极低 |
判断要点: 若模块内两个子功能在需求变更时修改的原因相同、时间相同、频率相近,则它们应保留在同一模块内;否则应拆分。
实践中常用“变化原因核对法”评估内聚度:列出模块近三个迭代周期的变更记录,统计每次变更所影响的函数与数据结构。若变更往往只波及模块内的一部分元素,说明内聚不足,边界需要重新划分。
内聚度速查: 当模块超过 60% 的变更仅修改其中不到 30% 的代码区域时,即可判定为“职责分散”,应考虑拆分或重组。
3.2 边界粒度判断
边界粒度是模块在职责内聚基础上的“粗细”选择。粒度过粗会导致模块内部耦合膨胀、变更影响范围失控;粒度过细则引发模块间通信网络复杂、部署与编排成本上升。合理的边界粒度是在内聚度与通信代价之间取得平衡。
粒度评估需同时考察以下五个维度:
- 职责变化频率: 同一模块内各职责的变化频率应相近,差值超过 5 倍时应考虑拆分。
- 数据依赖深度: 模块所需的外部数据源数量超过 3 个时,边界可能过粗。
- 接口扇出系数: 模块对外暴露的接口数量与服务调用方的比值,扇出 > 1:8 时通信成本过高。
- 重构波及范围: 单次修改影响的模块数量超过 3 个时,边界划分可能不合理。
- 部署独立性: 模块能否在不依赖其他模块的情况下独立测试与部署。
| 粒度特征 | 过粗表现 | 过细表现 | 合理边界特征 |
|---|---|---|---|
| 内部职责数 | 职责超过 5 个且变化原因不同 | 每个模块仅包含 1 个操作 | 2–4 个相关职责 |
| 模块间调用深度 | 调用链超过 4 层 | 调用链仅 1 层但扇出极大 | 2–3 层,扇出可控 |
| 变更影响模块数 | 单次变更涉及 5+ 模块 | 单次变更涉及 1 个模块但需同步更新 10+ 下游 | 变更影响 2–3 个模块 |
| 测试复杂度 | 单模块测试用例超过 200 个 | 每个模块测试少于 5 个 | 30–80 个用例 / 模块 |
常见误区: 不要将“模块越小越好”等同于“粒度越细越好”。粒度过细会导致模块间数据一致性协议复杂化,分布式事务比例上升,整体稳定性反而下降。边界粒度的最终衡量标准是“单模块变更时,其余模块不受牵连”。
3.3 反模式拆分案例
以下三种反模式在实际架构评审中频繁出现,是模块边界设计的主要陷阱。
反模式一:按数据表拆分模块
将数据库中的每一张表对应为一个独立模块,认为“表即边界”。后果:业务操作需跨 5–8 个模块完成一次写入,分布式事务占比超过 40%,数据一致性难以保证。
改进方向: 按业务聚合根组织模块,将同一事务边界内的表归入同一模块。
将数据库中的每一张表对应为一个独立模块,认为“表即边界”。后果:业务操作需跨 5–8 个模块完成一次写入,分布式事务占比超过 40%,数据一致性难以保证。
改进方向: 按业务聚合根组织模块,将同一事务边界内的表归入同一模块。
反模式二:按技术层拆分职责
将 Controller、Service、DAO 分别设为独立模块,认为“分层即职责分离”。后果:任何功能变更都必须同时修改三个模块,模块间依赖关系形成全连通图,编译与部署顺序耦合严重。
改进方向: 以业务功能为维度,将跨层的代码按“功能垂直切片”重组,每一切片内部包含完整的控制、服务与数据访问逻辑。
将 Controller、Service、DAO 分别设为独立模块,认为“分层即职责分离”。后果:任何功能变更都必须同时修改三个模块,模块间依赖关系形成全连通图,编译与部署顺序耦合严重。
改进方向: 以业务功能为维度,将跨层的代码按“功能垂直切片”重组,每一切片内部包含完整的控制、服务与数据访问逻辑。
反模式三:按功能碎片拆分
将原本完整的业务逻辑拆分为“查询模块”“计算模块”“校验模块”“组装模块”等细粒度碎片。后果:一次请求需要串行经过 6–10 个模块,每个模块仅做极小的操作,模块间通信开销超过实际计算开销。
改进方向: 合并高频协作的碎片,以“业务步骤”为粒度重新划分,确保一次请求在 2–3 个模块内完成。
将原本完整的业务逻辑拆分为“查询模块”“计算模块”“校验模块”“组装模块”等细粒度碎片。后果:一次请求需要串行经过 6–10 个模块,每个模块仅做极小的操作,模块间通信开销超过实际计算开销。
改进方向: 合并高频协作的碎片,以“业务步骤”为粒度重新划分,确保一次请求在 2–3 个模块内完成。
反模式鉴别清单:
▸ 模块数量超过团队人数的 2 倍 → 可能粒度过细
▸ 超过 30% 的模块仅包含 1 个公开接口 → 碎片化风险
▸ 任意功能变更需同步修改 4 个以上模块 → 边界错位
▸ 模块间环形依赖数量超过 3 个 → 职责内聚不足
▸ 模块数量超过团队人数的 2 倍 → 可能粒度过细
▸ 超过 30% 的模块仅包含 1 个公开接口 → 碎片化风险
▸ 任意功能变更需同步修改 4 个以上模块 → 边界错位
▸ 模块间环形依赖数量超过 3 个 → 职责内聚不足
正确运用单一职责原则进行模块划分,应当从“变化原因”出发,以内聚度为内核、粒度平衡为边界、反模式为警示,在每一次架构演进中持续校准模块边界。
上图揭示了单一职责原则在模块划分中的三层实践路径:以 SRP 为起点,通过职责内聚评估夯实模块内部质量,借助五维度粒度判断优化模块外部协作,同时以三种反模式作为边界退化的早期预警信号。三者形成闭环,持续指导架构演进中的边界调整决策。