可维护性的度量和设计原则(1)

可维护性的度量和设计原则(1)

软件维护和演化

软件维护

  • 软件维护指在软件发布后对软件中的错误进行修改以提升性能或者其他特性的活动。
  • 软件维护是软件产品最困难的方面,因为它与其他所有的软件开发阶段都相关。
  • 修复代码后
    • 测试修改是否正常:使用特殊构造的测试用例
    • 检查回归性错误:使用存储的测试数据,并将特殊构造的测试用例添加到存储的测试数据中,以便将来进行回归测试
    • 记录所有更改
  • 软件维护的种类
    • 纠正性维护(Corrective maintenance):纠正软件产品中的问题
    • 适应性维护(Adaptive maintenance):对软件产品进行修改,以使软件产品能够适应新的变化的环境
    • 完善性维护(Perfective maintenance):增强软件产品以提高性能或增加可维护性
    • 预防性维护(Preventive maintenance):修改软件产品,以便在软件产品成为有效故障之前检测并纠正软件产品中的潜在故障。

软件演化

  • 软件演化是在软件维护中使用的一个概念,指最初开发软件的过程,然后由于各种原因反复更新软件。
  • 典型系统的90%以上的成本出现在维护阶段,并且任何成功的软件都将不可避免地得到维护。
  • Lehman’s Laws:一个软件是为了解决现实世界中的问题而产生的,它如何表现应该和它运行的环境紧密相关,因此这样的软件需要适应不同的需求和环境。

可维护性的指标

可维护性的几个名字

  • Maintainability:软件易于被修改以纠正错误、提升性能或者其他特性、适应变化的环境的能力。
  • Extensibility:一个软件易于增加新的功能的能力。
  • Flexibility:软件对于用户的需求、外部技术和社会环境而易于改变的能力。
  • Adaptability:一个交互系统能够根据用户或环境提供的信息改变它的行为的能力。
  • Manageability:可以高效,轻松地监控和维护软件系统,以保持系统的正常、安全地运行。
  • Supportability:在软件部署后还有多大的受支持程度。

关于可维护性的问题

  • 结构和设计简单性:改变事物有多容易
  • 紧密或松散耦合的事物(即关注点分离)
  • 包装/模块中的所有要素是否具有凝聚力,其责任是否明确且密切相关?
  • 它是否具有过于深入的继承层次结构,还是有利于组合而不是继承?
  • 方法定义中有多少独立的执行路径(即圈复杂度)?
  • 存在多少代码重复?

一些可维护性的指标

  • Cyclomatic Complexity:度量软件的结构复杂性
    • 计算程序流中独立路径的个数
    • 程序流越复杂,需要的测试就更多,可维护性也更低
    • 计算公式:CC = E-N+2, CC=P+1, CC=number of areas
      可维护性的度量和设计原则(1)
  • Lines of Code
    • 很高的代码行数预示着可能有些类或者方法尝试实现太多的功能,而这些功能应该被分开
    • 也可能意味着类或者方法难以维护
  • Halstead Volume:基于源代码中运算符和操作数的个数
  • Maintainability Index(MI):计算一个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)
      1715ln(HV)0.23CC16.2ln(LOC)+50.0sin(2.46×COM)171-5\ln (HV)-0.23CC-16.2\ln (LOC)+50.0\sin (\sqrt {2.46\times COM})
  • Depth of Inheritance:继承关系约深,系统就会越难理解
  • Class Coupling:通过参数,局部变量,返回类型,方法调用,通用或模板实例化,基类,接口实现,在外部类型上定义的字段以及属性修饰来测量与唯一类的耦合
    • 良好的软件设计要求类型和方法应具有高内聚力和低耦合性
    • 高耦合表明设计难以重用和维护,因为它与其他类型有很多相互依赖性
  • Unit test coverage :指自动单元测试都涵盖了代码库的哪些部分

模块化设计和模块化原则

模块化编程

  • 是一种软件设计方法,讲程序的功能分散到独立的、可交互的模块中,这样每个模块都实现整体功能的仅一个方面
  • 模块化是高层的功能分解技术
    • 结构化编程中是如何根据控制流分解代码
    • OOP中是指对象的使用方式
  • 设计的目标是将系统划分为模块,并以下列方式在组件之间分配责任:
    • 模块内的高内聚
    • 模块之间的低耦合
  • 模块化降低了程序员在任何时候必须处理的总复杂性,假设:
    • 将功能分配给远离的模块,将相似的功能组合在一起(关注点分离)
    • 模块之间有小而简单,定义良好的接口(信息隐藏)
  • 内聚和耦合的原理可能是评估设计可维护性的最重要的设计原则

评估模块性的五个标准

  • Decomposability (可分解性)
    • 将问题分解为各个可独立解决的子问题
    • 目标:使模块间的依赖关系显式化和最小化
    • 自顶向下的函数式设计:
      可维护性的度量和设计原则(1)
  • Composability (可组合性)
    • 可以容易的将模块组合起来形成新的系统
    • 目标:使模块可以在不同的环境下复用可维护性的度量和设计原则(1)
  • Understandability (可理解性)
    • 每个子模块都可以被系统设计者容易地理解
    • 例子:序列依赖(AA\rarrBB\rarrCC
      可维护性的度量和设计原则(1)
  • Continuity (可持续性)
    • specification中的一个小的拜年话之影响一小部分模块而不会影响整个系统架构
    • 例子:为模块提供的服务通过统一的标识提供
      可维护性的度量和设计原则(1)
  • Protection (保护性)
    • 运行时异常情况限制在小范围的模块内
    • 例子:在源处验证输入
      可维护性的度量和设计原则(1)

模块设计的五个规则

  • Direct Mapping(直接映射)
    • 模块的结构与现实世界问题领域的结构一致
  • Few Interfaces(尽可能少的接口)
    • 模块应该尽可能少地与其他模块通信
  • Small Interfaces(尽可能小的接口)
    • 如果两个模块通信,那么它们应该交换尽可能少的信息
  • Explicit Interfaces(显式接口)
    • 当A与B通信时,应明显地发生在A与B的接口之间
  • Information Hiding(信息隐藏)
    • 经常发生变化的设计决策应该尽可能隐藏在抽象接口后面

内聚和耦合

  • 耦合(Coupling)
    • 耦合是模块之间依赖性的度量。 如果一个模块中的更改可能需要更改另一个模块,则两个模块之间存在依赖关系。
    • 模块之间的耦合程度由以下因素确定:
      • 接口数量
      • 接口复杂度
      • 通讯复杂度
  • 内聚(Cohesion)
    • 内聚是衡量模块的功能或职责的相关程度
    • 如果模块的所有元素都朝着相同的目标努力,则模块具有高内聚