【软件构造】第六章第一节 可维护性的度量与构造原则

第六章第一节 可维护性的度量与构造原则

本章面向另一个质量指标:可维护性——软件发生变化时,是否可以以很小的代价适应变化? 
本节是宏观介绍:(1)什么是软件维护;(2)可维护性如何度量;(3)实现高可维护性的设计原则——很抽象。

Outline

  • 软件的维护和演化
  • 可维护性的常见度量指标
  • 聚合度与耦合度
  • 面向对象五大原则SOLID
    • 单一职责原则SRP(Single Responsibility Principle)
    • 开放封闭原则OCP(Open-Close Principle)
    • 里式替换原则LSP(the Liskov Substitution Principle LSP)
    • 依赖倒置原则DIP(the Dependency Inversion Principle DIP)
    • 接口分离原则ISP(the Interface Segregation Principle ISP)

Notes

## 软件的维护和演化

  • 定义:软件可维护性是指软件产品被修改的能力,修改包括纠正、改进或软件对环境、需求和功能规格说明变化的适应。简而言之,软件维护:修复错误、改善性能。
  • 类型:纠错性(25%)、适应性(25%)、完善性(50%)、预防性(4%)
  • 演化:软件演化是一个程序不断调节以满足新的软件需求过程。
  • 演化的规律:软件质量下降,延续软件生命
  • 软件维护和演化的目标:提高软件的适应性,延续软件生命 。
  • 意义:软件维护不仅仅是运维工程师的工作,而是从设计和开发阶段就开始了 。在设计与开发阶段就要考虑将来的可维护性 ,设计方案需要“easy to change”
  • 基于可维护性建设的例子:
    • 模块化
    • OO设计原则
    • OO设计模式
    • 基于状态的构造技术
    • 表驱动的构造技术
    • 基于语法的构造技术

## 可维护性的常见度量指标

  • 可维护性:可轻松修改软件系统或组件,以纠正故障,提高性能或其他属性,或适应变化的环境。
  • 除此之外,可维护性还有其他许多别名:可扩展性(Extensibility)、灵活性(Flexibility)、可适应性(Adaptability)、可管理性(Manageability)、支持性(Supportability)。总之,有好的可维护性就意味着容易改变,容易扩展。
  • 软件可维护性的五个子特性:
    • 易分析性。软件产品诊断软件中的缺陷或失效原因或识别待修改部分的能力。
    • 易改变性。软件产品使指定的修改可以被实现的能力,实现包括编码、设计和文档的更改。如果软件由最终用户修改,那么易改变性可能会影响易操作性。
    • 稳定性。软件产品避免由于软件修改而造成意外结果的能力。
    • 易测试性。软件产品使已修改软件能被确认的能力。
    • 维护性的依从性。软件产品遵循与维护性相关的标准或约定的能力。
  • 一些常用的可维护性度量标准:
    • 圈复杂度(CyclomaticComplexity):度量代码的结构复杂度。
    • 代码行数(Lines of Code):指示代码中的大致行数。
    • Halstead Volume:基于源代码中(不同)运算符和操作数的数量的合成度量。
    • 可维护性指数(MI):计算介于0和100之间的索引值,表示维护代码的相对容易性。 高价值意味着更好的可维护性。
    • 继承的层次数:表示扩展到类层次结构的根的类定义的数量。 等级越深,就越难理解特定方法和字段在何处被定义或重新定义。
    • 类之间的耦合度:通过参数,局部变量,返回类型,方法调用,泛型或模板实例化,基类,接口实现,在外部类型上定义的字段和属性修饰来测量耦合到唯一类。
    • 单元测试覆盖率:指示代码库的哪些部分被自动化单元测试覆盖。

## 模块化设计规范:聚合度与耦合度

  • 模块化编程的含义:模块化编程是一种设计技术,它强调将程序的功能分解为独立的可互换模块,以便每个模块都包含执行所需功能的一个方面。
  • 设计规范:高内聚低耦合
  • 评估模块化的五个标准:
    • 可分解性:将问题分解为各个可独立解决的子问题
    • 可组合性:可容易的将模块组合起来形成新的系统
    • 可理解性:每个子模块都可被系统设计者容易的理解
    • 可持续性:小的变化将只影响一小部分模块,而不会影响整个体系结构
    • 出现异常之后的保护:运行时的不正常将局限于小范围模块内
  • 模块化设计的五条原则:
    • 直接映射:模块的结构与现实世界中问题领域的结构保持一致
    • 尽可能少的接口:模块应尽可能少的与其他模块通讯
    • 尽可能小的接口:如果两个模块通讯,那么它们应交换尽可能少的信息
    • 显式接口:当A与B通讯时,应明显的发生在A与B的接口之间
    • 信息隐藏:经常可能发生变化的设计决策应尽可能隐藏在抽象接口后面

【内聚性】

  • 又称块内联系。指模块的功能强度的度量,即一个模块内部各个元素彼此结合的紧密程度的度量。若一个模块内各元素(语名之间、程序段之间)联系的越紧密,则它的内聚性就越高。
  • 所谓高内聚是指一个软件模块是由相关性很强的代码组成,只负责一项任务,也就是常说的单一责任原则。

【耦合性】

  • 也称块间联系。指软件系统结构中各模块间相互联系紧密程度的一种度量。模块之间联系越紧密,其耦合性就越强,模块的独立性则越差。模块间耦合高低取决于模块间接口的复杂性、调用的方式及传递的信息。
  • 对于低耦合,粗浅的理解是:一个完整的系统,模块与模块之间,尽可能的使其独立存在。也就是说,让每个模块,尽可能的独立完成某个特定的子功能。模块与模块之间的接口,尽量的少而简单。如果某两个模块间的关系比较复杂的话,最好首先考虑进一步的模块划分。这样有利于修改和组合。

更多请参考 王永迪的专栏 浅谈高内聚低耦合

## SOLID原则

  更多请参考 我理解的SOLID 浅谈SOLID原则的具体使用

  S.O.L.I.D是面向对象设计和编程(OOD&OOP)中几个重要编码原则(Programming Priciple)的首字母缩写。

SRP The Single Responsibility Principle 单一责任原则
OCP The Open Closed Principle 开放封闭原则
LSP The Liskov Substitution Principle 里氏替换原则
ISP The Interface Segregation Principle 接口分离原则
DIP The Dependency Inversion Principle 依赖倒置原则

【SRP 单一责任原则】

  • 含义:需要修改某个类的时候原因有且只有一个。换句话说就是让一个类只做一种类型责任,当这个类需要承当其他类型的责任的时候,就需要分解这个类。
  • 如果一个类包含了多个责任,那么将引起不良后果:引入额外的包,占据资源;导致频繁的重新配置、部署等。
  • SRP是最简单的原则,却是最难做好的原则。
  • SRP的一个反例:

 【OCP 开放封闭原则】

  • 软件实体应该是可扩展,而不可修改的。也就是说,对扩展是开放的,而对修改是封闭的。这个原则是诸多面向对象编程原则中最抽象、最难理解的一个。

    • 模块的行为应是可扩展的,从而该模块可表现出新的行为以满足需求的变化。
    • 模块自身的代码是不应被修改的
    • 扩展模块行为的一般途径是修改模块的内部实现
    • 如果一个模块不能被修改,那么它通常被认为是具有固定的行为。
  • 关键解决方案:抽象技术。 使用继承和组合来改变类的行为。
  • OCP的一个反例:

  • OCP的一个例子:
 1 // Open-Close Principle - Bad example
 2 class GraphicEditor {
 3 public void drawShape(Shape s) {
 4     if (s.m_type==1)
 5         drawRectangle(s);
 6     else if (s.m_type==2)
 7         drawCircle(s);
 8     }
 9     public void drawCircle(Circle r)
10         {....}
11     public void drawRectangle(Rectangle r)
12         {....}
13 }
14
15 class Shape { int m_type; }
16 class Rectangle extends Shape { Rectangle() { super.m_type=1; } }
17 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 } }   

【LSP 里氏替换原则】

  • Liskov‘s 替换原则意思是:"子类型必须能够替换它们的基类型。"或者换个说法:"使用基类引用的地方必须能使用继承类的对象而不必知道它。" 这个原则正是保证继承能够被正确使用的前提。通常我们都说,“优先使用组合(委托)而不是继承”或者说“只有在确定是 is-a 的关系时才能使用继承”,因为继承经常导致”紧耦合“的设计。
  • 【软件构造】第五章第二节 设计可复用的软件 中有所描述

 【ISP 接口分离原则】

  • 含义:客户端不应依赖于它们不需要的方法。换句话说,使用多个专门的接口比使用单一的总接口总要好。
  • 客户模块不应该依赖大的接口,应该裁减为小的接口给客户模块使用,以减少依赖性。如Java中一个类实现多个接口,不同的接口给不用的客户模块使用,而不是提供给客户模块一个大的接口。
  • “胖”接口具有很多缺点。
    • 胖接口可分解为多个小的接口;
    • 不同的接口向不同的客户端提供服务;
    • 客户端只访问自己所需要的端口。
  • 下图展示出了这种思想:
  • ISP的一个反例

【DIP 依赖转置原则】

  • 定义:

    • 高层模块不应该依赖于低层模块,二者都应该依赖于抽象
    • 抽象不应该依赖于细节,细节应该依赖于抽象
  • 这个设计原则的亮点在于任何被DI框架注入的类很容易用mock对象进行测试和维护,因为对象创建代码集中在框架中,客户端代码也不混乱。有很多方式可以实现依赖倒置,比如像AspectJ等的AOP(Aspect Oriented programming)框架使用的字节码技术,或Spring框架使用的代理等。
    • 高层模块不要依赖低层模块;
    • 高层和低层模块都要依赖于抽象;
    • 抽象不要依赖于具体实现;
    • 具体实现要依赖于抽象;
    • 抽象和接口使模块之间的依赖分离。
  • 一个具体的例子:

进行抽象改进后:

【SOLID 总结】

  1. 一个对象只承担一种责任,所有服务接口只通过它来执行这种任务。
  2. 程序实体,比如类和对象,向扩展行为开放,向修改行为关闭。
  3. 子类应该可以用来替代它所继承的类。
  4. 一个类对另一个类的依赖应该限制在最小化的接口上。
  5. 依赖抽象层(接口),而不是具体类。

原文地址:https://www.cnblogs.com/hithongming/p/9190702.html

时间: 2024-08-04 10:28:58

【软件构造】第六章第一节 可维护性的度量与构造原则的相关文章

软件构造 第五章第一节 可复用性的度量、形态和外部观察

第五章第一节  可复用性的度量.形态和外部观察 面向复用编程(programming for reuse):开发出可复用的软件 基于复用编程(programming with reuse):利用已有的可复用软件搭建应用系统 代码复用的类型: 白盒复用:源代码可见,可修改和扩展 含义:复制已有代码到正在开发的系统,进行修改 优点:可订制化程度高 缺点:对其修改增加了软件的复杂度,且需要对其内部充分的了解 黑盒服用:源代码不可见,不能修改 含义:只能通过过API接口来使用,无法修改代码 优点:清晰.

软件构造 第六章第三节 面向可维护的构造技术

第六章第三节 面向可维护的构造技术 基于状态的构造技术 状态模式(State Pattern) 备忘录模式(Memento Pattern) Grammar-based construction 使用grammar判断字符串是否合法,并解析成程序里使用的数据结构 . 正则表达式 通常是递归的数据结构 . terminals 终止节点.叶节点 nonterminal 非终止节点(遵循特定规则,利用操作符.终止节点和其他非终止节点,构造新的字符串) 三个基本语法的操作符: 连接,不是通过一个符号,而

【软件构造】第七章第一节 健壮性和正确性的区别

第七章第一节  健壮性和正确性的区别 第七章:进入软件构造最关键的质量特性 --健壮性和正确性. 本节在1-2节的基础上,重申了Robustness and Correctness的重要性,澄清了二者之 间的差异,并指明了在软件构造中处理二 者的典型技术(防御式编程.异常处理. 测试.调试等) Outline 健壮性(Robustness)和正确性(correctness) 如何测量健壮性和正确性 Notes ## 健壮性(Robustness)和正确性(correctness) [健壮性] 定

软件构造 第二章 第一节 软件生命周期和版本控制

软件构造第二章 第一节 软件生命周期和版本控制 基本内容 Software Development Lifecycle (SDLC) Traditional software process models (waterfall, incremental, V- model, prototyping, spiral) Agile development and eXtreme Programming (XP) Collaborative software development Software

【软件构造】第六章第二节 可维护的设计模式

第六章第二节 可维护的设计模式 Outline 创造性模式:Creational patterns 工厂模式(Factory Pattern) 抽象工厂模式(Abstract Factory Pattern) 建造者模式(Builder Pattern) 结构化模式:Structural patterns 桥接模式(Bridge Pattern) 代理模式(Proxy Pattern) 组合模式(Composite Pattern) 行为化模式:Behavioral patterns 中介者模式

【计算机网络】第一章第一节 计算机网络的基本概念

第一章第一节 计算机网络的基本概念 第一章概述了计算机网络和因特网,这一章从整体上粗线条地勾画出计算机网络的概貌和全课程的框架 Outline 计算机网络的具体构成描述 协议 Notes ## 计算机网路的具体构成描述  [计算机网络的定义] 计算机网络是 通信技术 与 计算机技术 紧密结合的产物 计算机网络就是 互连的.自治的 计算机集合 自治:无主从关系 互连:互联互通 计算机网络就是一种特殊的通信网络(信源和信宿都是计算机.传输数字化信息) [具体构成] 主机:处在因特网的边缘部分的设备都

【软件构造】第二章第二节 软件构造的过程、系统和工具

第二章第二节 软件构造的过程.系统和工具 Outline 广义的软件构造过程 编程 静态代码分析 动态代码分析 调试与测试 重构 狭义的软件构造过程 构造系统:经典BUILD场景 构造系统的组件 构造过程和构造描述 Java编译工具 子目标和结构变体 构造工具 Notes ## 广义的软件构造过程 [编程(Coding)] 开发语言:如Java.C.Python 使用IDE(集成开发工具)的优势(组成) 方便编写代码和管理文件(有代码编辑器,代码重构工具.文件和库(Library)管理工具) 能

软件构造 第三章第二节 软件规约

第三章第二节 软件spec 客户端无需阅读调用函数的代码,只需理解spec即可. 精确的规约,有助于区分责任,给"供需双方"确定了责任,在调用的时候双方都要遵守. @param @return @throws 例子: Behavioral equivalence (行为等价性) 根据规约判断是否行为等价 与实现无关! 如果两个函数符合这个规约,故它们等价. Specification Structure 前置条件(precondition):对客户端的约束,在使用方法时必须满足的条件.

软件构造 第七章第二节 错误与异常处理

第七章第二节 错误与异常处理 内部错误:程序员通常无能为力,一旦发生,想办法让程序优雅的结束 异常:你自己程序代码发生的,可以捕获处理 [Error] Error类描述很少发生的Java运行时系统内部的系统错误和资源耗尽情况(例如,VirtualMachineError,LinkageError). 对于内部错误:程序员通常无能为力,一旦发生,想办法让程序优雅的结束 Error的类型: 用户输入错误 例如:用户要求连接到语法错误的URL,网络层会投诉. 设备错误 硬件并不总是做你想做的. 输出器