4. 耦合度评估方法:如何量化评估耦合度、常用评估指标(CC、CB等)

说实话,很多团队在谈「低耦合」时,全凭感觉。

「我觉得这个模块耦合太高了」——这种话我听过太多次。但到底多高?能不能量化?没有数据,你连重构的优先级都排不出来。

这一章,我们就来聊聊怎么给耦合度「打分」。我会把我在几个大型项目中用过的评估方法,掰开揉碎了讲给你听。

4.1 为什么需要量化评估?

先讲个我自己的教训。

几年前我接手一个电商后台系统。代码写得「看起来」挺干净,每个类都挺小。但每次改一个支付相关的逻辑,订单模块、库存模块、物流模块全都要回归测试。为什么?因为耦合藏在细节里——一个接口改了签名,十几个地方跟着报错。

后来我做了个简单的耦合度扫描,发现有个模块的传出耦合(Ce)高达 23。什么意思?它直接依赖了 23 个外部类。改它等于改半个系统。

所以,量化评估不是炫技。它是帮你找到「最痛的点」的工具。

4.2 核心指标:CC 和 CB

业界最常用的两个指标,一个是 CC(Coupling Between Objects),一个是 CB(Coupling Between Modules)。名字有点像,但关注点不同。

4.2.1 CC:类级别的耦合

CC 衡量的是「一个类直接依赖了多少个其他类」。说白了,就是数一数你的 import 语句里,有多少是外部类的。

CC 的计算方式:

CC = 该类中引用的外部类的数量(不包括标准库和框架类)

举个例子:

// 这个类的 CC 是多少?
public class OrderService {
    private PaymentGateway paymentGateway;  // 外部类
    private InventoryClient inventoryClient; // 外部类
    private NotificationService notifService; // 外部类
    private OrderRepository orderRepo;       // 外部类

    public void createOrder(Order order) {
        // ...
    }
}

数一下:PaymentGatewayInventoryClientNotificationServiceOrderRepository。一共 4 个外部依赖。所以 CC = 4。

经验值:

  • CC ≤ 5:还算健康
  • CC 在 6~10:需要关注,考虑是否能用接口隔离
  • CC > 10:危险信号。我见过 CC=18 的类,改一行代码要测两天
我的小技巧:

写代码时,我会刻意控制每个类的 CC 不超过 7。如果超过,我会问自己:这个类是不是承担了太多职责?是不是该拆了?

4.2.2 CB:模块级别的耦合

CB 是 CC 的「升级版」。它不关心单个类,而是看模块之间的依赖关系。

一个模块可能包含多个类。CB 统计的是:模块 A 中,有多少个类直接依赖了模块 B 中的类。

CB 的计算方式:

CB(A→B) = 模块 A 中直接引用模块 B 中类的数量

举个例子:

模块 依赖的模块 CB 值
订单模块 支付模块 5
订单模块 库存模块 3
订单模块 用户模块 2
支付模块 订单模块 0

你看,订单模块对支付模块的 CB 是 5,说明有 5 个类直接用了支付模块的东西。反过来,支付模块对订单模块的 CB 是 0——这是好事,说明依赖是单向的。

注意:

如果出现双向依赖(A→B 且 B→A 的 CB 都大于 0),这就是典型的循环依赖。我曾经在一个项目中,因为循环依赖导致编译顺序都乱了,最后不得不重构了整整一周。

4.3 其他常用指标

除了 CC 和 CB,还有几个指标也值得了解。我不建议你全用,挑一两个适合你项目的就行。

4.3.1 Ce(传出耦合)和 Ca(传入耦合)

  • Ce(Efferent Coupling):一个类依赖的外部类的数量。其实就是 CC。
  • Ca(Afferent Coupling):有多少个外部类依赖这个类。这个指标能告诉你「这个类是不是太核心了」。

Ca 高不一定是坏事。比如工具类 StringUtils,Ca 可能上千,但它稳定,改了也不会出问题。但如果是业务类 Ca 很高,比如 UserService 被 50 个类依赖,那你改它的时候就要格外小心。

4.3.2 I(不稳定性)

I = Ce / (Ce + Ca)。

这个值越接近 1,说明这个类越「不稳定」——它依赖别人多,别人依赖它少。改它风险小。反过来,I 接近 0 的类,比如基础库,改起来要命。

4.3.3 扇入和扇出

这是从硬件设计借来的概念:

  • 扇出(Fan-out):一个模块直接调用的下级模块数量。高扇出意味着控制力弱。
  • 扇入(Fan-in):有多少上级模块调用它。高扇入说明复用性好。

我个人习惯:扇出控制在 5 以内,扇入越高越好。

4.4 如何自动化评估?

手动数?别傻了。项目大了根本数不过来。

我常用的工具:

  • JDepend:Java 项目的经典工具,能自动计算 CC、Ce、Ca、I 等指标
  • SonarQube:自带耦合度检测,还能看代码异味
  • Structure101:可视化依赖图,一眼看出循环依赖
  • 自己写脚本:有时候工具不够灵活,我会写个简单的 Python 脚本,解析 import 语句来统计
建议:

把耦合度检查集成到 CI 流水线里。比如设定 CC 超过 10 就报警,或者 CB 出现循环依赖就阻断构建。这样能防止「代码慢慢变烂」。

4.5 一张图看懂耦合度评估体系

下面这张 SVG 图,是我自己画的。它把本章的核心知识点串了起来:

耦合度评估方法体系 量化评估耦合度 类级别:CC 模块级别:CB Ce(传出耦合) Ca(传入耦合) I(不稳定性) 扇入/扇出 JDepend SonarQube Structure101 自定义脚本 集成到 CI 流水线,持续监控

4.6 避坑指南

最后,分享几个我踩过的坑:

  • 别只看数字:CC=8 不一定比 CC=6 差。如果那 8 个依赖都是稳定的标准库,问题不大。关键是看依赖的是「稳定接口」还是「易变实现」。
  • 小心间接耦合:A 依赖 B,B 依赖 C。虽然 A 的 CC 里没有 C,但改 C 可能影响 A。这种「传递耦合」很难量化,但危害不小。
  • 别追求极端低耦合:耦合度为零的系统,要么是玩具,要么是每个模块都在重复造轮子。适当的耦合是必要的。
总结一句话:

耦合度评估不是目的,而是手段。它的价值在于帮你找到「最需要重构的地方」,而不是让你陷入数字游戏。


公众号:蓝海资料掘金营,微信deep3321