可维护性的度量和构造原则
本章面向另一个质量指标:可维护性——软件发生变化时,是否可以以很小的代价适应变化?
本节是宏观介绍:(1)什么是软件维护;(2)可维护性如何度量;(3)实现高可维护性的设计原则——很抽象。
目录
- 软件维护和演变
- 可维护性度量
- 模块化设计和模块化原则
- OO设计原则:SOLID
- OO设计原则:GRASP
软件维护和演变
软件工程中的软件维护是交付后修改软件产品以纠正故障,提高性能或其他属性,简而言之,软件维护:修复错误、改善性能。
负责软件维护的工程师我们通常称为运维工程师,处理来自用户报告的故障/问题 。
软件维护的类型:
- 纠错性(25%)
- 适应性(21%)
- 完善性(50%)
- 预防性(4%)
Lehman关于软件演化总结出的规律:软件质量下降,复杂度增加。
“变化”在软件生命周期中是不可避免的! 如何在最初的设计中充分考虑到未来的变化, 避免因为频繁变化导致软件复杂度的增加和质量的下降?
软件维护和演化的目标:提高软件的适应性,延续软件生命 。
软件维护不仅仅是运维工程师的工作,而是从设计和开发阶段就开始了 。在设计与开发阶段就要考虑将来的可维护性 ,设计方案需要“easy to change”。
基于可维护性建设的例子:
- 模块化
- OO设计原则
- OO设计模式
- 基于状态的构造技术
- 表驱动的构造技术
- 基于语法的构造技术
这些会在接下来章节具体讲述。
可维护性的度量
可维护性:可轻松修改软件系统或组件,以纠正故障,提高性能或其他属性,或适应变化的环境。
除此之外,可维护性还有其他许多别名:可扩展性、灵活性、可适应性、可管理性、支持性。总之,有好的可维护性就意味着容易改变,容易扩展。
一些常用的可维护性度量标准:
- 圈复杂度(CyclomaticComplexity):度量代码的结构复杂度。
- 代码行数(Lines of Code):指示代码中的大致行数。
- Halstead Volume:基于源代码中(不同)运算符和操作数的数量的合成度量。
- 可维护性指数(MI):计算介于0和100之间的索引值,表示维护代码的相对容易性。 高价值意味着更好的可维护性。
- 继承的层次数:表示扩展到类层次结构的根的类定义的数量。 等级越深,就越难理解特定方法和字段在何处被定义或重新定义。
- 类之间的耦合度:通过参数,局部变量,返回类型,方法调用,泛型或模板实例化,基类,接口实现,在外部类型上定义的字段和属性修饰来测量耦合到唯一类。
- 单元测试覆盖率:指示代码库的哪些部分被自动化单元测试覆盖。
模块化设计和模块化原则
模块化编程是一种设计技术,它强调将程序的功能分解为独立的可互换模块,以便每个模块都包含执行所需功能的一个方面。
设计的目标是将系统划分为模块并在组件之间分配责任,方式如下:
- 模块内高内聚
- 模块间低耦合
模块化降低了程序员在任何时候都必须处理的总体复杂性,因为模块化实现了分离关注点和信息隐藏。
内聚和耦合的原则可能是评估设计可维护性的最重要的设计原则。
评估模块化的五个标准
- 可分解性
- 可组合性
- 可理解性
- 可持续性
- 出现异常之后的保护
模块化设计的五条原则
- 直接映射
- 尽可能少的接口
- 尽可能小的接口
- 显式接口
- 信息隐藏
耦合和聚合
耦合是模块之间依赖关系的度量。 如果两个模块之间的变化可能需要另一个模块的变更,则两个模块之间存在依赖关系。
模块之间的耦合程度取决于:
- 模块之间的接口数量(数量)
- 每个接口的复杂性(由通信类型决定)(质量)
精心设计的Web应用程序模块化:
- 指定数据和语义的HTML文件
- 指定HTML数据外观和格式的CSS规则
- 定义页面行为/交互性的JavaScript
聚合是衡量一个模块的功能或责任有多强烈程度的一个指标。如果一个模块的所有元素都朝着相同的目标努力,则它具有很高的聚合度。
最好的设计在模块内具有高内聚力(也称为强内聚力)和模块之间的低耦合(也称为弱耦合)。
OO设计原则:SOLID
SOLID:5类设计原则
- (SRP) The Single Responsibility Principle 单一责任原则
- (OCP) The Open-Closed Principle 开放-封闭原则
- (LSP) The Liskov Substitution Principle Liskov替换原则
- (DIP) The Dependency Inversion Principle 依赖转置原则
- (ISP) The Interface Segregation Principle 接口聚合原则
单一责任原则
SRP:不应有多于1个的原因使得一个类发生变化;一个类,一个责任。
如果一个类包含了多个责任,那么将引起不良后果:引入额外的包,占据资源;导致频繁的重新配置、部署等。
SRP是最简单的原则,却是最难做好的原则。
SRP的一个反例:
开放-封闭原则
开发指的是对扩展性的开放,模块的行为应是可扩展的,从而该模块可表现出新的行为以满足需求的变化。
封闭指的是对修改的封闭,模块自身的代码是不应被修改的,扩展模块行为的一般途径是修改模块的内部实现,如果一个模块不能被修改,那么它通常被认为是具有固定的行为。
关键的解决方案:抽象技术。
使用继承和组合来改变类的行为。
OCP的一个反例:
再来看一个例子:
// Open-Close Principle - Bad example
class GraphicEditor {
public void drawShape(Shape s) {
if (s.m_type==1)
drawRectangle(s);
else if (s.m_type==2)
drawCircle(s);
}
public void drawCircle(Circle r)
{....}
public void drawRectangle(Rectangle r)
{....}
}
class Shape { int m_type; }
class Rectangle extends Shape { Rectangle() { super.m_type=1; } }
class Circle extends Shape { Circle() { super.m_type=2; } }
上面代码中有几个问题:
- 不可能在不修改
GraphEditor
的情况下添加新的Shape
GraphEditor
和Shape
之间的紧密耦合- 不调用
GraphEditor
就很难测试特定的Shape
改进之后代码如下:
// Open-Close Principle - Good example
class GraphicEditor {
public void drawShape(Shape s) {
s.draw();
}
}
class Shape { abstract void draw(); }
class Rectangle extends Shape { public void draw() { // draw the rectangle } }
Liskov替换原则
LSP:子类型必须能够替换其基类型。
派生类必须能够通过其基类的接口使用,客户端无需了解二者之间的差异。
这条原则在第五章第二节(5.2)中已详细讲述过,这儿就不再赘述了。
依赖转置原则
高级模块不应该依赖于低级模块。 两者都应该取决于抽象。抽象的模块不应依赖于具体的模块,具体应依赖于抽象。
例子: the “Copy” program
通常我们会这么实现这个程序:
void Copy(OutputStream dev) {
int c;
while ((c = ReadKeyboard()) != EOF)
if (dev == printer)
writeToPrinter(c);
else
writeToDisk(c);
}
改用抽象技术后:
interface Reader { public int read(); }
interface Writer { public int write(c); }
class Copy {
void Copy(Reader r, Writer w) {
int c;
while (c=r.read() != EOF)
w.write(c);
}
}
也许这个例子你还没有清楚的了解抽象之后有什么好处,再看下面一个例子:
改进之后,就能够将finder
变成任意的EmployeeFinder
,例如XmEmployeeFinder
, DBEmployeeFinder
, FlatFileEmployeeFinder
, MockEmployeeFinder
….
而在之前的代码中,就无法这么方便的改变类的属性了。
接口聚合原则
接口聚合原则:客户端不应依赖于它们不需要的方法。
“胖”接口具有很多缺点。胖接口可分解为多个小的接口;不同的接口向不同的客户端提供服务;客户端只访问自己所需要的端口。下图展示出了这种思想。
ISP的一个反例:
OO设计原则:GRASP
GRASP是关于如何为“类”和“对象”指派“职责”的一系列原则。
对象的责任:与对象的义务相关
了解:
- 了解私有封装数据
- 了解相关对象
- 了解它可以派生或计算的事物
执行:
- 自己做某件事,例如创建对象或执行计算
- 在其他对象中启动动作
- 控制和协调其他对象中的活动。
看下面一个例子:
责任是使用方法实现的:makePayment
意味着Sale
对象有责任创建Payment
对象。
GRASP由以下部分组成:
- 控制器(Controller)
- 信息专家(Information expert )
- 创建者(Creator)
- 低耦合(Low coupling )
- 高内聚(High cohesion )
- 间接(Indirection)
- 多态(Polymorphism)
- 受保护的变体(Protected variations )
- 纯制造(Pure fabrication)
这一部分主要是自学,可以在以下链接继续学习。
CMU course