资讯专栏INFORMATION COLUMN

第6章:可维护性软件构建方法 6.1可维护性的度量和构造原则

chanjarster / 2893人阅读

摘要:设计方案的容易改变这就是所谓的软件构建的可维护性,可扩展性和灵活性。这也可能表明类型或方法可能难以维护。基于源代码中不同运算符和操作数的数量的合成度量。对修改的封闭这种模块的源代码是不可侵犯的。

大纲

软件维护和演变
可维护性度量
模块化设计和模块化原则
OO设计原则:SOLID
OO设计原则:GRASP
总结

软件维护和演变

什么是软件维护?

软件工程中的软件维护是交付后修改软件产品以纠正故障,提高性能或其他属性。软件维护:修复错误,改善性能
在“ISO / IEC 14764:2006软件工程 - 软件生命周期过程 - 维护”

运维工程师

维护是软件生产中最困难的方面之一,因为维护包含了所有其他阶段的各个方面
用户报告故障并由维护工程师处理。

维护工程师必须具备出色的调试技能

故障可能存在于产品中的任何地方,故障的原因可能在于现在不存在的规格或设计文档(错误/问题本地化)。

需要高超的诊断技能,测试技能和文档技能(测试,修复和记录更改)。

软件维护的类型

纠正性维修25%纠错性

对交付后执行的软件产品进行无功修改,以纠正发现的问题;

适应性维护21%适应性

修改交付后执行的软件产品,以保持软件产品在变化或变化的环境中可用;

完善性维护50%完善性

交付后增强软件产品以提高性能或可维护性;

预防性维护4%预防性

交付后对软件产品进行修改,以便在软件产品发生有效故障前检测并纠正软件产品中的潜在故障。

雷曼关于软件演化的规律(Lehman’s Laws on Software Evolution)

反馈系统
持续变化
持续增长
质量下降
增加复杂性
自我调节

保持组织稳定

保持熟悉

软件维护和进化的目标

软件维护和演化的目标:为了提高软件的适应性和适应性,并保持其活力,即“长期软件(低熵软件)”
提高软件的适应性,延续软件生命
Linux内核发展的一个例子:可维护性指数

维护不仅仅是op工程师的任务......

维护不仅仅是维护和操作工程师的任务,也是软件设计人员和开发人员的潜在任务。 软件维护不仅仅是运维工程师的工作,而是从设计和开发阶段就开始了
对他们来说,在设计和施工阶段必须考虑软件的未来潜在变化/扩展;在设计与开发阶段就要考虑将来的可维护性
因此,灵活和可扩展的设计/结构被全面考虑,换句话说,“易于改变/扩展”。 设计方案的“容易改变”
这就是所谓的软件构建的“可维护性”,“可扩展性”和“灵活性”。

可维护性建设的例子

模块化设计与实现模块化

低耦合和高内聚力

OO设计原则OO设计原则

SOLID,GRASP

OO设计模式OO设计模式

工厂方法模式,Builder模式

桥模式,代理模式

纪念模式,状态模式

基于状态的构造技术(自动机编程)
表驱动的构造技术
基于语法的构造技术(Grammar-based construction)

可维护性度量

许多可维护性的名字

可维护性

“软件系统或组件可轻松修改以纠正故障,改善性能或其他属性,或适应变化的环境”。

可扩展性

软件设计/实现将未来的增长考虑在内,并被视为扩展系统能力的系统性测量以及实现扩展所需的工作量。

灵活性

软件根据用户需求,外部技术和社会环境等容易改变的能力。

可适应性

一种交互式系统(自适应系统)的能力,可以根据所获得的有关其用户及其环境的信息,使其行为适应个人用户。

可管理性

如何有效和轻松地监控和维护软件系统,以保持系统的正常运行,安全并平稳运行。

可支持性

基于包含质量文档,诊断信息以及知识丰富且可用的技术人员的资源,可以有效地使软件在部署后保持运行。

有关可维护性的问题

Code review的时候经常问的关于可维护性的问题:

结构和设计简单:改变事物有多容易?

事情紧密或松散(即关注点分离)?

包/模块中的所有元素是否具有凝聚力,其职责是否清晰且密切相关?

它是否具有过深的继承层次结构,还是赞成继承的组合?

方法定义中存在多少个独立的执行路径(即,cycolmatic复杂度)?

存在多少代码重复?

一些常用的可维护性度量标准

圈复杂度 - 测量代码的结构复杂度。

它是通过计算程序流程中不同代码路径的数量而创建的。

具有复杂控制流程的程序将需要更多的测试来实现良好的代码覆盖率,并且将不易维护。

CC = E-N + 2,CC = P + 1,CC =区域的数量

Lines of Code(代码行数) - 表示代码中的近似行数。

非常高的数值可能表明某种类型或方法试图做太多工作,应该分手。

这也可能表明类型或方法可能难以维护。

对于给定的问题,令:
η_1=不同运算符的数目
η_2=不同的操作数的数量
Ν_1=运算符总数
Ν_2=操作数总数
从这些数字中可以计算出几项度量:
程序词汇表:η=η_1+η_2
程序长度:N=Ν_1+Ν_2
计算的程序长度:N ̂=η_1 log_2⁡〖η_1 〗+η_2 log_2⁡〖η_2 〗
体积:V=N×log_2⁡η
难度:D=η_1/2×Ν_2/η_2
代价:E=D×V
难度测量与程序编写或理解的难度有关
代价度量使用以下关系转化为实际编码时间,
编程所需的时间:T=E/18 秒
Halstead提供的错误(B)是对执行错误数量的估计。
提供的错误数量:B = E^(2/3)/3000或更接近的B = V/3000也被接受。
Halstead Volume:基于源代码中(不同)运算符和操作数的数量的合成度量。

一些常用的可维护性度量标准

可维护性指数(MI)- 计算介于0和100之间的索引值,表示维护代码的相对容易性。 高价值意味着更好的可维护性。 它的计算基于:

Halstead体积(HV)

圈复杂性(CC)

每个模块的平均代码行数(LOC)

每个模块注释行的百分比(COM)。

一些常用的可维护性度量标准

继承的层次数

表示扩展到类层次结构的根的类定义的数量。 等级越深,就越难理解特定方法和字段在何处被定义或重新定义。

类之间的耦合度

通过参数,局部变量,返回类型,方法调用,泛型或模板实例化,基类,接口实现,在外部类型上定义的字段和属性修饰来测量耦合到唯一类。

良好的软件设计决定了类型和方法应该具有高内聚性和低耦合性。

高耦合表示一种设计难以重用和维护,因为它与其他类型之间存在许多相互依赖关系。

单元测试覆盖率

指示代码库的哪些部分被自动化单元测试覆盖。 (将在第7章中研究)

模块化设计和模块化原则

模块化编程

模块化编程是一种强调将程序功能分离为独立,可互换模块的设计技术,每种模块都包含执行所需功能一个方面所需的一切。
将整个程序的代码高度分解为结构化编程和OOP。

模块化编程设计的目标是将系统划分为模块,并通过以下方式在组件之间分配责任:

模块内高凝聚力(高内聚)

模块之间的耦合松耦合(低耦合)

模块化降低了程序员在任何时候都必须处理的总体复杂性,假设:

将功能分配给将相似功能组合在一起的模块(分离关注点)

模块之间有小而简单的定义明确的接口(信息隐藏)

内聚和耦合的原则可能是评估设计可维护性的最重要的设计原则。

(1)评估模块性的五个标准

Decomposability(可分解性)

较大的组件是否分解为较小的组件?

Composability(可组合性)

较大的组件是否由较小的组件组成?

Understandability(可理解性)

组件是否可多带带理解

Continuity(可持续性)

规范的小改动是否会影响本地化和有限数量的组件?

Protection(出现异常之后的保护)

运行时异常的影响是否局限于少数相关组件?

(2)模块化设计的五条原则

Direct Mapping (直接映射)
Few Interfaces (尽可能少的接口)
Small Interfaces (尽可能小的接口)
Explicit Interfaces (显式接口)
Information Hiding (信息隐藏)

(3)耦合和内聚

耦合

耦合是模块之间依赖关系的度量。 如果两个模块之间的变化可能需要另一个模块的变更,则两个模块之间存在依赖关系。

模块之间的耦合度取决于:

模块之间的接口数量(数量)和

每个接口的复杂性(由通信类型决定)(质量)

HTML,CSS和JavaScript之间的耦合

一个精心设计的网络应用程序模块化:

指定数据和语义的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 接口聚合原则

(1)单一责任原则(SRP)

“类改变不应该有一个以上的原因”,即一个类应该集中精力做一件事,只能一件事。

责任:“变更的理由”(责任:变化的原因)
SRP:

类改变不应该有一个以上的原因。 (不应有多于1个的原因使得一个类发生变化)

一个类,一个责任。(一个类,一个责任)

如果一个类包含了多个责任,那么将引起不良后果:

引入额外的包,占据资源

导致频繁的重新配置,部署等

SRP是原则中最简单的一种,也是最难做到的一种。(最简单的原则,却是最难做好的原则)

(2)开放/封闭原则(OCP)

类应扩展(对扩展性的开放)

这意味着模块的行为可以扩展。 我们可以根据应用程序的需求变化,或者为了满足新应用程序的需求,使模块以新的和不同的方式运行。 (模块的行为应是可扩展的,从而该模块可表现出新的行为以满足需求的变化)

但关闭修改。(对修改的封闭)

这种模块的源代码是不可侵犯的。 没有人可以对其进行源代码更改。(但模块自身的代码是不应被修改的)

扩展模块行为的正常方法是对该模块进行更改。(扩展模块行为的一般途径是修改模块的内部实现)

无法更改的模块通常被认为具有固定的行为。(如果一个模块不能被修改,那么它通常被认为是具有固定的行为)

Key:abstraction(关键的解决方案:抽象技术)
“软件实体(类,模块,函数等)应该被打开以进行扩展,但是为了修改而关闭”,即,使用继承和组合来改变类的行为

开放封闭原则 - 几个问题...。
不可能在不修改GraphEditor的情况下添加新的Shape
重要的是要了解GraphEditor添加一个新的形状
GraphEditor和Shape之间的紧密耦合
不参与GraphEditor就很难测试特定的Shape
If-Else-/Case应该被避免

OCP表示单一选择
只要软件系统必须支持一组替代方案,系统中的一个且只有一个模块应该知道他们的详尽列表。
编辑器:一组命令(插入,删除等)
图形系统:图形类型集(矩形,圆形等)
编译器:语言结构集(指令,循环,表达式等)

(3)Liskov替换原则(LSP)

“使用指针或对基类的引用的函数必须能够使用派生类的对象而不知道它”,即,子类在使用它们的基类时应该表现得很好

LSP:子类型必须可替代其基本类型。(子类型必须能够替换其基类型)

派生类必须可以通过基类接口使用,而不需要客户端了解其差异。 (派生类必须能够通过其基类的接口使用,客户端无需了解二者之间的差异)

在第5-2节中已经讨论过可复用性。

(4)接口隔离原理(ISP)

“客户不应该被迫依赖他们不使用的接口”,即保持接口小。
不要强制类来实现它们不能实现的方法(Swing / Java)
不要用很多方法污染界面
避免“胖”的接口

客户不应该被迫依赖他们不使用的方法。(客户端不应依赖于它们不需要的方法)
接口属于客户端,而不属于层次结构。
这个原则处理“胖”接口的缺点。(“胖”接口具有很多缺点)
具有“胖”接口的类是接口不具有内聚性的类。(不够聚合)

该类的接口可以分解为多组成员函数。(胖接口可分解为多个小的接口)

每个小组服务一组不同的客户端(不同的接口向不同的客户端提供服务)。

因此有些客户使用一组成员函数,而其他客户使用其他组。(客户端只访问自己所需要的端口)

(5)依赖倒置原理(DIP)

高级模块不应该依赖于低级模块。 两者都应该取决于抽象。

抽象不应该依赖于细节(抽象的模块不应依赖于具体的模块)

细节应该取决于抽象(具体应依赖于抽象)

应该使用大量的接口和抽象!

为什么DIP?

优点:

将类契约正式化。

您根据前置和后置条件定义例程的服务。 这非常清楚会发生什么。

尝试设计测试

创建一个测试友好的设计

测试友好的模块可能会展现其他重要的设计特征。

例如:你会避免循环依赖。 如果您必须从UI多带带测试,业务逻辑将更好地与UI代码隔离

OO设计原则:GRASP

什么是GRASP模式

一般责任分配软件模式(原则),缩写为GRASP,包含为OOP中的类和对象分配责任的准则。
GRASP模式是帮助理解基本对象设计的学习辅助,并以有条理,合理,可解释的方式应用设计推理。
这种理解和使用设计原则的方法是基于对班级分配责任的模式。
GRASP是关于如何为“类”和“对象”指派“职责”的一系列原则

什么是责任

对象的责任:与对象的义务有关

了解:

了解私有封装数据

了解相关对象

了解它可以派生或计算的事物

这样做:

自己做一些事情,比如创建一个对象或者做一个计算

在其他对象中发起行动

控制和协调其他对象的活动。

总结

软件维护和演变
可维护性度量
模块化设计和模块化原则
OO设计原则:SOLID
OO设计原则:GRASP

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/71346.html

相关文章

  • 5:可复用性软件构建方法 5.1可复用性度量,形态外部观察

    摘要:大纲什么是软件复用如何衡量可复用性可复用组件的级别和形态源代码级别复用模块级别的复用类抽象类接口库级别的复用包系统级别的复用框架对可复用性的外部观察类型变化例行分组实施变更代表独立分解常见行为总结什么是软件复用软件复用软件复用是使用现有软件 大纲 什么是软件复用?如何衡量可复用性?可复用组件的级别和形态 源代码级别复用 模块级别的复用:类/抽象类/接口 库级别的复用:API /包 系...

    mengera88 评论0 收藏0
  • 2软件构建过程工具 2.2软件构建过程,系统工具

    摘要:建模语言建模语言是可用于表达信息或知识或系统的任何人造语言,该结构由一组一致的规则定义,目标是可视化,推理,验证和传达系统设计。将这些文件安排到不同的地方称为源代码树。源代码树的结构通常反映了软件的体系结构。 大纲 软件构建的一般过程: 编程/重构 审查和静态代码分析 调试(倾倒和记录)和测试 动态代码分析/分析 软件构建的狭义过程(Build): 构建系统:组件和过程 构建变体...

    godiscoder 评论0 收藏0
  • 软件评测师考试学习计划

    摘要:软件评测师教程阅读持续更新。。。。单元测试又称模块测试,是针对软件设计的最小单位程序模块进行正确性检验的测试工作其目的在于检查每个程序单元能否正确实现详细设计说明中的模块功能性能接口和设计约束等要求,发现各模块内部可能存在的各种错误。 软件评测师教程阅读持续更新。。。。 目录大纲阅读时间完成...

    beanlam 评论0 收藏0
  • 5:可重用性软件构建方法 5.3面向复用设计模式

    摘要:共性的步骤在抽象类内公共实现,差异化的步骤在各个子类中实现子类为每个步骤提供不同的实现。模板方法将算法的骨架定义为抽象类,允许其子类提供具体行为。迭代器依次访问对象的元素而不暴露其基础表示。 大纲 结构模式 Adapter允许具有不兼容接口的类通过将自己的接口包装到已有类的接口中来一起工作。 Decorator动态添加/覆盖对象的现有方法中的行为。 Facade为大量代码提供简化的界...

    superPershing 评论0 收藏0
  • 6可维护性软件构建方法 6.3可维护性构建技术

    摘要:遵循特定规则,利用操作符,终止节点和其他非终止节点,构造新的字符串非终结符是表示字符串的树的内部节点。语法中的生产具有这种形式非终结符终结,非终结符和运算符的表达式语法的非终结点之一被指定为根。 大纲 基于状态的构建 基于自动机的编程 设计模式:Memento提供了将对象恢复到之前状态的功能(撤消)。 设计模式:状态允许对象在其内部状态改变时改变其行为。 表驱动结构* 基于语法的构...

    young.li 评论0 收藏0

发表评论

0条评论

chanjarster

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<