哈尔滨工业大学软件构造课程笔记第五章第一节
5.1可维护性的度量与构造原则
1.软件维护和软件演化
什么是软件维护?
软件工程中的软件维护是指在软件产品交付后对其进行修改,以纠正错误,提高性能或其他属性。
运维工程师
运维是软件开发中最困难的工作之一,涉及到其他所有环节
处理来自用户报告的故障/问题
运维工程师必须有高超的调试技巧
修复代码之后
更多步骤:
测试所做的修改
回归测试
记录变化
除了修复问题,修改中不能引入新的故障
最大的问题:修改后没有足够的文档记录和测试
软件维护的类型
25% 纠错性:对交付后执行的软件产品进行反应性修改,以纠正发现的问题
21% 适应性:在交付后对软件产品进行的修改,以使软件产品在变更或变化的环境中保持可用
50% 完善性:改进交付后的软件产品,以提高性能或可维护性
4% 预防性:在交付后对软件产品进行修改,以便在软件产品中潜在的故障成为有效故障之前检测并纠正它们
软件演化
软件进化是软件维护中的一个术语,指的是最初开发软件,然后由于各种原因对其进行反复更新的过程。
软件的大部分成本来自于维护阶段
雷曼关于软件进化的八条定律▪反馈系统
▪持续改变
▪持续增长
▪质量下降
▪增加复杂性
▪自我调节
-保持组织的稳定性
-熟悉性守恒
软件维护不仅仅是运维工程师的工作,而是从设计和开发阶段就开始了
在设计与开发阶段就要考虑将来的可维护性
设计方案的“easy to change”
这就是所谓的“可维护性”、“可扩展性”
软件构建的“灵活性”。
面向可维护性的构造的例子
▪模块化设计和实现
-低耦合和高内聚
▪OO设计原则
-坚实,把握
▪OO设计模式
-工厂方法模式,生成器模式
-桥梁模式,代理模式
-记忆模式,状态模式
▪基于状态的构造技术
▪表驱动的构造技术
▪基于语法的构造技术
2.指标的可维护性
可维护性-修改软件系统或组件以纠正错误、改进性能或其他属性或适应更改的环境的容易程度
可扩展性-软件设计/实现需要考虑未来的增长和被视为一个系统测量扩展系统的能力和努力实现扩展所需的水平。
灵活性——软件能力变化很容易响应用户需求,外部技术和社会环境等。
可适应性——交互式系统的能力
(适应性系统)能够根据所获得的有关用户及其环境的信息来调整其行为以适应个别用户的系统。
可管理性——有效且易于软件系统可以监测和维护,以保持系统执行,安全、平稳运行。
支持性——软件可以有效地保持运行部署后,基于资源,包括质量文档、诊断信息和知识渊博的和可用的技术人员。
一些常用的可维护性度量:
圈复杂度——措施代码的结构复杂性。
-它是通过计算程序流中不同代码路径的数量来创建的。独立路径的数量
-一个具有复杂控制流的程序将需要更多的测试,以实现良好的代码覆盖率,并将较难维护。
- CC= E-N+2, CC=P+1, CC=面积数
代码行数:表示近似在代码的行数。
-一个非常高的计数可能表明一个类型或方法试图做太多的工作,应该被分割。
-它还可能表明类型或方法可能难以维护。
可维护性指数-计算索引0到100之间的值,表示相对易于维护的代码。
高价值意味着更好的可维护性。计算依据:
-霍尔斯特德体积(HV)
-圈复杂度(CC)
-每个模块的平均代码行数(LOC)
-每个模块的注释行百分比(COM)
继承的层次数:表示数量的类定义,扩展类层次结构的根。层次结构越深,就越难理解在哪里定义或/和重新定义了特定的方法和字段。
类之间的耦合度——措施通过独特的类的耦合参数、局部变量、返回类型,方法调用,泛型或模板实例化,基类,接口实现,装饰字段上定义外部类型和属性。
3.模块化设计和模块化原则
模块化编程
▪模块编程是一种设计技术,强调将一个程序的功能分离成独立的、可互换的模块,这样每个模块都包含所有必要的东西,只执行所需功能的一个方面。
▪在结构化编程和面向对象编程中,对整个程序的代码进行高级分解。
▪设计目标是将系统划分为模块,并按照以下方式分配职责:
——高内聚模块内高内聚
——模块之间松散耦合低耦合
▪模块化降低了程序员必须在任何时间处理的总复杂性,假设:
——功能模块分配给了类似的功能组织在一起的(关注点分离)分离关注点
-模块之间有小的、简单的、定义良好的接口
(信息隐藏)信息隐藏
▪内聚和耦合的原则可能是最重要的设计原则
(1)五个评价模块化的标准
五个评价模块化的标准
可分解性:是否将较大的组件分解为较小的组件?
可组合性:较大的组件是由较小的组件组成的吗?
可理解性:组件是可单独理解的吗?
可持续性:发生变化时受影响范围最小
出现异常之后的保护:出现异常后受影响范围最小
1.可分解性:将问题分解为各个可独立解决的子问题
目标:使模块之间的依赖关系显式化和最小化
2.可组合性:可容易的将模块组合起来形成新的系统
目标:使模块可在不同的环境下复用
3.可理解性:每个子模块都可被系统设计者容易的理解
4.可持续性:规格说明小的变化将只影响一小部分模块,而不会影响整个体系结构
5.出现异常之后的保护:运行时的不正常将局限于小范围模块内
(2)模块化设计的五条原则
模块化设计的五条原则
直接映射
尽可能少的接口
尽可能小的接口
显式接口
信息隐藏
- 直接映射:模块的结构与现实世界中问题领域的结构保持一致
对以下评价标准产生影响:持续性,可分解性 - 尽可能少的接口:模块应尽可能少的与其他模块通讯
对以下评价标准产生影响:可持续性、保护性、可理解性、可组合性 - 尽可能小的接口:如果两个模块通讯,那么它们应交换尽可能少的信息
限制模块之间通讯的“带宽”
对“可持续性”和“保护性”产生影响 - 显式接口:当A与B通讯时,应明显的发生在A与B的接口之间
受影响的评价标准:可分解性、可组合性、可持续性、 可理解性 - 信息隐藏:经常可能发生变化的设计决策应尽可能隐藏在抽象接口后面
每个模块的设计者必须选择模块属性的子集作为关于模块的官方信息,以供客户端模块使用。
(3)耦合性和内聚性
耦合性
▪耦合是衡量模块之间的依赖性。如果一个模块的更改可能需要另一个模块的更改,则两个模块之间存在依赖关系。
▪模块之间的耦合程度由以下因素决定:
-模块间接口数量(数量)
-每个接口的复杂性(由通信类型决定)(质量)
内聚性
▪衔接是对模块的功能或职责之间的紧密联系的衡量。
▪模块具有高凝聚力,如果它的所有元素都朝着同一个目标努力。
耦合性和内聚性
最好的设计在模块内部具有高内聚(也称为强内聚),在模块之间具有低耦合(也称为弱耦合)。
4.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 接口聚合原则
(1)单一责任原则(SRP)
ADT中不应该有多于1个原因让其发生变化,否则就拆分开
责任:变化的原因
一个类,一个责任
如果一个类包含了多个责任,那么将引起不良后果:
– 引入额外的包,占据资源
– 导致频繁的重新配置、部署等
最简单的原则,却是最难做好的原则
(2)(面向变化的)开放/封闭原则 (OCP)
对扩展性的开放:模块的行为应是可扩展的,从而该模块可表现出新的行为以满足需求的变化
对修改的封闭:但模块自身的代码是不应被修改的
扩展模块行为的一般途径是修改模块的内部实现
如果一个模块不能被修改,那么它通常被认为是具有固定的行为
关键的解决方案:抽象技术
(3)Liskov替换原则(LSP)
子类型必须能够替换其基类型
派生类必须能够通过其基类的接口使用,客户端无需了解二者之间的差异
(4)接口隔离原则(ISP)
不能强迫客户端依赖于它们不需要的接口:只提供必需的接口
▪不要强迫类实现它们不能实现的方法(Swing/Java)
▪不要用很多方法污染界面
▪避免“肥胖”的界面
胖”接口具有很多缺点:不够聚合
胖接口可分解为多个小的接口
不同的接口向不同的客户端提供服务
客户端只访问自己所需要的端口
(5) 依赖转置原则(DIP)
高层模块不应该依赖于低层模块,二者都应该依赖于抽象
抽象不应该依赖于实现细节,实现细节应该依赖于抽象
小结:OO设计的两大武器
▪ 抽象(abstraction):模块之间通过抽象隔离开来,将稳定部分和容易
变化部分分开
– LSP:对外界看来,父类和子类是“一样”的;
– DIP:对接口编程,而不是对实现编程,通过抽象接口隔离变化;
– OCP:当需要变化时,通过扩展隐藏在接口之后的子类加以完成,而不要修
改接口本身。
▪ 分离(Separation): Keep It Simple, Stupid (KISS)
– SRP:按责任将大类拆分为多个小类,每个类完成单一职责,规避变化,提
高复用度;
– ISP:将接口拆分为多个小接口,规避不必要的耦合。
▪ 归纳起来:让类保持责任单一、接口稳定。
5.OO设计原则:GRASP
GRASP模式
▪一般职责分配软件模式(原理),简称把握,包括为OOP中的类和对象分配职责的指导方针。▪掌握模式是一个学习的辅助工具,帮助一个人理解基本的对象设计,并应用设计推理在一个系统的,合理的,可解释的方式。
▪理解和使用设计原则的方法是基于将职责分配到班级的模式。
什么是责任
▪物品的责任:与物品的义务有关
▪知道:
-了解私有封装数据
-了解相关物品
-了解它可以推导或计算的东西
▪做:
-做一些事情本身,如创建一个对象或做一个计算
-在其他对象中启动动作
-控制和协调其他对象的活动。
GRASP是由什么组成的?
▪控制器
▪信息专家
▪创造者
▪低耦合
▪高内聚
▪间接
▪多态性
▪保护变化
▪纯制造
6. 总结
▪软件维护和改进
▪可维护性指标
▪模块设计和模块化原则
▪OO设计原则:扎实
▪OO设计原则:把握