前置知识: 软件工程与测试

软件度量

6 min中级

代码行度量、功能点分析、圈复杂度与软件质量指标。

1. 从”体重秤”说起

1.1 为什么需要度量

想象一个人想减肥。他能”感觉”自己胖了,但没有体重秤就不知道具体数字、无法追踪变化、无法判断方法是否有效。

软件度量(Software Metrics)就是软件的”体重秤”:用量化数据衡量软件的质量、规模、复杂度,让”软件好不好”从”感觉”变成”可测量的数字”。

1.2 度量的四个目的

目的说明
评估质量量化软件质量属性
预测工作量估算项目规模和成本
监控进度跟踪开发进展
改进过程识别改进机会

1.3 度量分类

分类说明示例
产品度量软件产品属性代码行数、缺陷密度
过程度量开发过程属性开发周期、评审次数
项目度量项目管理属性成本、进度、人力

2. 规模度量:软件”有多大”

2.1 代码行(LOC)

指标说明
SLOC源代码行(不含空行和注释)
KLOC千行代码
LOC/人月人均生产率

局限性(为什么要谨慎用 LOC):

  • 语言差异大:同一功能,Python 和 Java 行数差异巨大
  • 不反映复杂度:100 行简单代码 vs 100 行复杂逻辑,难度完全不同
  • 不鼓励简洁代码:用 LOC 考核开发者,会诱导写”又长又啰嗦”的代码

2.2 功能点分析(FPA)

功能点(Function Point)衡量软件的”功能规模”,与实现语言无关——它数的是”软件提供给用户的功能数量”,而不是”代码行数”。

五种功能类型:

功能类型简单平均复杂权重说明
外部输入(EI)346用户输入
外部输出(EO)457用户输出
外部查询(EQ)346查询操作
内部逻辑文件(ILF)71015系统维护的数据
外部接口文件(EIF)5710系统引用的数据

为什么功能点比 LOC 好:它衡量”功能价值”而非”代码量”。同样 10 个功能点,用 Java 写可能是 1000 行、用 Python 写可能是 500 行——但功能点是相同的。这让跨语言、跨团队的规模比较成为可能。

3. 复杂度度量:代码”多难懂”

3.1 圈复杂度(Cyclomatic Complexity)

圈复杂度衡量程序的线性独立路径数——即”有多少条不同的执行路径”:

V(G)=E−N+2PV(G) = E - N + 2P

(EE 边数,NN 节点数,PP 连通分量数)

简化计算:V(G) = 决策点数 + 1(决策点 = if、while、case 等分支)

圈复杂度风险等级建议
1~10低简单,低风险
11~20中较复杂,中等风险
21~50高复杂,高风险
>50极高不可测试,必须重构

圈复杂度的意义:

  • 可测试性:复杂度 30 意味着至少 30 条路径,要写 30 个测试才能覆盖——测试成本极高
  • 可维护性:复杂度越高,改一个分支越容易漏掉其他分支的影响

常见工具:SonarQube、ESLint(JavaScript)等都能自动计算圈复杂度。

3.2 认知复杂度(Cognitive Complexity)

圈复杂度有个缺陷:它对”线性 if-else 链”和”嵌套 if”同样计分,但人类理解它们的难度完全不同。认知复杂度改进了这一点:

  • 不对线性逻辑(if-else 链)增加复杂度
  • 嵌套增加额外复杂度(嵌套越深越难懂)
  • 跳转(break、continue)增加复杂度

认知复杂度更符合”人类理解代码的难度”,是圈复杂度的现代替代。

4. 质量度量:软件”好不好”

4.1 缺陷度量

指标公式说明
缺陷密度缺陷数/KLOC每千行代码缺陷数
缺陷发现率缺陷数/测试时间单位时间发现的缺陷
缺陷修复率已修复/总缺陷修复进度
缺陷泄漏率生产缺陷/(测试缺陷+生产缺陷)测试有效性(越低越好)

缺陷泄漏率特别重要:它衡量”测试漏掉了多少缺陷”。泄漏率高说明测试体系需要改进。

4.2 代码质量指标

指标说明工具
重复率重复代码占比SonarQube
测试覆盖率被测试代码的占比JaCoCo、Istanbul
技术债务修复所有问题所需时间SonarQube
代码异味数代码坏味道数量SonarQube
依赖复杂度模块间耦合度Structure101

4.3 可维护性指数(Maintainability Index, MI)

MI=171−5.2ln⁡(avgHV)−0.23avgCC−16.2ln⁡(avgLOC)+50sin⁡(2.4avgCM)\text{MI} = 171 - 5.2 \ln(\text{avgHV}) - 0.23 \text{avgCC} - 16.2 \ln(\text{avgLOC}) + 50 \sin(\sqrt{2.4 \text{avgCM}})

(HV 为 Halstead 体积、CC 为圈复杂度、LOC 为代码行、CM 为注释比例)

范围评级
0~9低
10~19中
20~100高

5. 度量实践:度量仪表盘

5.1 建立仪表盘

维度指标目标
质量缺陷密度<5/KLOC
质量测试覆盖率>80%
效率开发速度故事点/Sprint
效率构建成功率>95%
可维护性圈复杂度<15
可维护性重复率<3%

度量仪表盘的原则:指标要”少而关键”(6-8 个),并且自动采集(CI 自动统计),不要手工填表。

5.2 Goodhart 法则:度量的陷阱

“当一个度量成为目标时,它就不再是一个好的度量。“(Goodhart’s Law)

经典例子:如果考核”代码行数”,开发者会写出又长又啰嗦的代码;如果考核”测试覆盖率”,开发者会写”没意义的断言”来凑覆盖率。

避免度量陷阱的方法:

  1. 度量是工具,不是目标:度量用于发现问题,不是用于考核个人
  2. 组合多个度量:单一指标容易被”优化作弊”,组合指标更可靠
  3. 定期审视度量的有效性:如果某个指标开始被”做假”,说明它失效了

6. 常见误区

误区一:LOC(代码行)是衡量开发效率的好指标。 → 恰恰相反,LOC 鼓励写烂代码。功能点(FPA)才衡量”功能价值”。

误区二:圈复杂度越低越好。 → 复杂度 1 的代码(没有分支)可能是”把复杂逻辑硬编码了”。关键不是”低”,而是”与逻辑复杂程度匹配”。

误区三:测试覆盖率 100% = 软件没 bug。 → 覆盖率只证明”代码被跑过”,不证明”测试是有效的”。100% 覆盖率+无效断言,依然会漏掉 bug。

误区四:度量是为了考核开发者。 → 度量是为了发现系统问题(哪个模块复杂、哪里质量差),不是为了”抓人”。

误区五:指标越多越好。 → 指标太多会淹没重点,团队不知道该优化哪个。少而关键(6-8 个)才有效。