什么是软件维护
所谓软件维护就是修改已经发布的软件,来更正其中的错误或者改善它的性能
软件开发周期图:
ISO/IEC 14764:2006 Software Life Cycle Processes
可维护性如何度量
什么是可维护性?
- 可维护性(Maintainability): 一个软件可以很容易地修改错误,提升性能,或者适应环境的改变
- 可扩展性(Extensibility): 设计软件时考虑到了未来的的扩展
- 灵活性(Flexibility): 根据用户需求的变化或者环境的变化,软件可以轻易地修改
- 可适应性(Adaptability): 交互系统的能力,软件可以根据用户的信息改变它的行为
- 可管理性(Manageability): 如何高效、方便地监控和维护软件系统,以保持系统的性能、安全性和平稳运行
- 支持性(Supportability): 软件在部署后保持运行的有效性,包括文档,诊断信息和技术人员
可维护性就是要考虑如下问题:
- 设计结构是否简单?
- 模块之间是否松散耦合(loosely coupled)?
- 模块内部是否高度聚合(closely related)?
- 是否使用了非常深的继承树(inheritance hierarchies)?是否用委托(composition)替代继承?
- 代码的圈/环复杂度(cycolmatic complexity)是否太高?
- 是否存在重复代码?
以下部分都是可维护性的度量指数
可维护性指数(Maintainability Index)
通过公式计算的一个 0 到 100 之间的值,它表示维护代码的相对难易程度
更高的值表示更好维护,基于以下指标计算:
- 代码容量 Halstead Volume (HV)
- 圈/环复杂度 Cyclomatic Complexity (CC)
- 代码行数 The average number of lines of code per module (LOC)
- 注释的占比 The percentage of comment lines per module (COM)
先介绍这几个概念:
圈/环复杂度(Cyclomatic Complexity)
该指数测量代码的结构复杂度
- 它计算程序流中不同代码路径的数量
- 具有复杂控制流的程序需要更多的测试来实现良好的代码覆盖率,并且维护性较差
计算方法:
如果在控制流图中增加了一条从终点到起点的路径,整个流图形成了一个闭环。圈复杂度其实就是在这个闭环中线性独立回路的个数
如图,线性独立回路有:
- e1→ e2 → e
- e1 → e3 → e
所以复杂度为 2
对于简单的图,我们还可以数一数,但是对于复杂的图,这种方法就不是明智的选择了
也可以使用计算公式:
V(G) = e – n + 2 * p
- e:控制流图中边的数量(对应代码中顺序结构的部分)
- n:代表在控制流图中的判定节点数量,包括起点和终点(对应代码中的分支语句)
- ps:所有终点只计算一次,即使有多个
return
或者throw
- ps:所有终点只计算一次,即使有多个
- p:独立组件的个数
代码行数(LOC)
- 代码行数太高,可能表示某个类或方法做了太多的工作,应该进行拆分
代码容量(HV)
代码容量关注的是代码的词汇数,有以下几个基本概念
参数 | 含义 |
---|---|
n1 | Number of unique operators,不同的操作元(运算子)的数量 |
n2 | Number of unique operands,不同的操作数(算子)的数量 |
N1 | Number of total occurrence of operators,为所有操作元(运算子)合计出现的次数 |
N2 | Number of total occurrence of operands,为所有操作数(算子)合计出现的次数 |
Vocabulary | n1 + n2,词汇数 |
length | N1 + N2,长度 |
Volume | length * Log2 Vocabulary,容量 |
继承(inheritance)的层次数
- 指示延伸到类层次结构根的类定义的数量。 层次结构越深,就越难理解特定方法在哪里定义或重新定义
类之间的耦合度(coupling)
- 通过参数、局部变量、返回类型、方法调用、泛型或模板实例化、基类、接口实现、在外部类型上定义的字段和属性修饰来衡量与唯一类的耦合程度
- 好的软件设计应该高内聚和低耦合
- 高耦合表示由于与其他类型的许多相互依赖而难以重用和维护的设计
单元测试的覆盖度(coverage)
- 指示代码库的哪一部分被单元测试覆盖
此外,还有很多其它的可维护性指标:
实现高可维护性的设计原则
软件设计的目标就是将系统划分成不同的模块,并在不同的模块之间分配规则:
- 模块内要高内聚
- 模块间要低耦合
模块化降低了程序员在任何时候必须处理的总复杂性
- 分离关注点(Separation of concerns),将功能分配给相似功能组合在一起的模块
- 信息隐藏(Information hiding),模块之间有小型、简单、定义良好的接口
评估模块化的五个标准·
- 可分解性(Decomposability)
- 较大的组件是否替换为了较小的组件?
- 可组合性(Composability)
- 较大的组件是由较小的组件组成的吗?
- 可理解性(Understandability)
- 组件分开了吗?
- 可持续性(Continuity)
- 组件发生变化后,影响范围足够小吗?
- 出现异常后的保护(Protection)
- 运行时异常的影响是否仅限于少数相关的组件?
可分解性(Decomposability)
可分解性就是将问题分解为各个可独立解决的子问题,使模块之间的依赖关系显式化和最小化
比如,自顶向下(top-down)的结构设计
可组合性(Composability)
可组合性就是模块可以很容易的组合起来形成新的系统,使模块可在不同的环境下复用
比如,数学计算库,UNIX
命令、管道
可理解性(Understandability)
可理解性就是每个子模块都很容易理解
比如:Unix
的shell
命令Program1 | Program2 | Program3
可持续性(Continuity)
规约的变化只会影响一小部分模块而不会影响整个体系结构
异常保护(Protection)
运行时出现的不正常情况只会局限于小范围的模块内
模块设计的五条原则
- 直接映射(Direct Mapping)
- 尽可能少的接口(Few Interfaces)
- 尽可能小的接口(Small Interfaces)
- 显式接口(Explicit Interfaces)
- 信息隐藏(Information Hiding)
直接映射(Direct Mapping)
即模块的结构与现实世界中问题领域的结构保持一致
对以下评价标准产生影响:
- Continuity
- 更容易评估变化的影响
- Decomposability
- 将现实问题模型中的分解作为软件模块的分解
尽可能少的接口(Few Interfaces)
模块应尽可能少的与其它模块通讯
对以下评价标准产生影响:
- Continuity
- Protection
- Understandability
- Composability
尽可能小的接口(Small Interfaces)
如果两个模块通讯,那么它们应交换尽可能少的信息
对以下评价标准产生影响:
- Continuity
- Protection
显式接口(Explicit Interfaces)
两个模块的通讯应该很明显
反例:
对以下评价标准产生影响:
- Decomposability
- Composability
- Continuity
- Understandability
信息隐藏(Information Hiding)
经常可能发生变化的设计决策应尽可能隐藏在抽象接口后面