第4章:模块内聚性评估方法

4.1 边界定义与评估目标

模块内聚性评估的核心是量化模块内部元素之间的关联强度,以及模块对外部依赖的合理程度。本章聚焦三种互补的评估方法:扇入扇出分析、依赖审计、变更影响面。它们从结构、依赖、动态变化三个维度揭示模块的内聚质量。

评估维度关注点典型指标
扇入扇出分析模块的调用入口数与出口数Fan-in > 3 且 Fan-out < 7 为健康
依赖审计模块实际依赖与架构层级的合规性逆向依赖数、循环依赖环
变更影响面模块修改后波及的编译/运行时范围影响模块数、重编时间增量

4.2 分层模型:三级评估体系

将三种方法融入三层评估结构,由浅入深排查内聚问题:

  • 第一层(结构层):基于静态代码扫描,计算每个模块的扇入(被调用次数)与扇出(对外调用次数)。扇入高表明模块被广泛复用,扇出高则暗示功能过于分散。
  • 第二层(依赖层):通过依赖审计工具(如 Gradle dependencies、JDepend)生成模块依赖图,标记所有跨越架构层级的逆向依赖与循环依赖。
  • 第三层(动态层):记录模块变更后,通过编译耗时、测试覆盖、Git 提交关联度等数据,量化变更影响面。
典型模式:
# 扇入/扇出检查示例(假设使用 ClassyShark 或自定义脚本)
模块A: Fan-in=5, Fan-out=2 → 高内聚
模块B: Fan-in=1, Fan-out=12 → 低内聚,需重构
模块C: Fan-in=8, Fan-out=1 → 数据聚合器,内聚中等

4.3 组件职责与数据流

每个评估方法对应一个专门的评估组件,数据流如下:

  1. 扇入扇出分析器:读取编译后类图,统计每个模块对外部包的所有引用,生成结构化指标。
  2. 依赖审计器:基于模块间引用关系,自动检测逆向依赖(如业务层依赖数据层)和循环依赖,输出违规清单。
  3. 变更影响面计算器:结合 Git 提交历史和构建缓存,计算每次模块修改后必须重新编译的模块集合。
模块内聚性评估三方法结构图 模块内聚性评估方法结构图 目标模块 扇入扇出分析 Fan-in / Fan-out 指标 依赖审计 逆向依赖 / 循环依赖 变更影响面 影响模块数 / 编译增量 三个评估方法从不同角度度量内聚性,联合使用可定位 90% 以上的模块边界问题 建议评估顺序:扇入扇出 → 依赖审计 → 变更影响面,逐步收敛诊断范围

4.4 数据流 / 控制流 / 信任链路

评估方法之间的数据流向保证了诊断的连贯性:

  • 数据流:静态代码分析结果 → 扇入扇出矩阵 → 输入依赖审计,过滤出高扇出模块;依赖审计报告 → 输入变更影响面计算器,确定高风险模块的波及范围。
  • 控制流:每个评估阶段可独立触发,但推荐流水线执行:扇入扇出 -> 依赖审计 -> 变更影响面,前一阶段的结果可作为后一阶段的参数。
  • 信任链路:工具生成的原始数据需经过人工复核(如排除框架自动生成的代码),评估结果应通过架构评审会确认,避免自动化误判。

4.5 选型权衡

评估方法优势局限适用场景
扇入扇出分析快速识别“上帝类”与“贫血模块”忽略动态调用与反射单体拆解、模块边界初判
依赖审计准确发现模块间违规依赖对多语言混合项目支持有限架构治理、合规检查
变更影响面直接反映重构成本与风险依赖历史数据积累模块重构决策、回滚策略
风险提示:变更影响面评估必须结合 编译缓存失效规则,否则可能因增量编译精准度不足而误判。建议在 CI 流程中使用 Gradle 的 --scan 参数记录模块级编译时间。

4.6 落地步骤

  1. 数据采集:在 CI 流水线中加入自定义 Gradle 插件,每 PR 自动计算各模块扇入扇出并输出 JSON 报告。
  2. 阈值设定:根据团队历史基线设定健康阈值(推荐 Fan-in ≥ 2 且 Fan-out ≤ 6)。
  3. 依赖审计规则配置:定义架构层级规则(如 :core:network 不能依赖 :feature:login),写入 .detekt 或自定义 checkstyle。
  4. 变更影响面建模:通过 Gradle Task 依赖图与 Git 文件差异,生成每次提交的影响模块列表。
  5. 自动化告警:当评估结果超出阈值时,在 MR 评论区或 Slack 发送告警,并附带重构建议。
// 示例:Gradle 插件中获取模块扇出
project.afterEvaluate {
    project.tasks.register("computeFanOut") {
        doLast {
            val deps = project.configurations.getByName("implementation").dependencies
            val fanOut = deps.size
            println("${project.name} fan-out: $fanOut")
            if (fanOut > 8) {
                throw GradleException("Fan-out exceeds limit in ${project.name}")
            }
        }
    }
}

4.7 验收标准

  • 所有模块的扇出 ≤ 8(可配置,首次设为 10 逐步收紧)。
  • 依赖审计报告中的逆向依赖与循环依赖数量归零。
  • 每次模块变更引起的重编译模块数不超过该模块直接依赖数量的 1.5 倍(排除公共基础模块)。
  • 评估结果可在 CI 日志中追溯,并提供可读的 HTML 或 Markdown 摘要。
最佳实践:在模块化初期优先使用扇入扇出分析快速劣化模块,中期加入依赖审计治理架构,最后引入变更影响面度量重构收益。三个方法形成闭环,持续保障模块内聚性。