软件构造课笔记:第10章——面向可维护性的构造技术

面向可维护性的构造技术

软件维护和进化

什么是软件维护?
软件工程中的软件维护是对一种软件产品,交付后可以纠正故障,进行改进性能或其他属性。
软件维护:修复错误、改善性能

运维工程师
维护是软件生产中最困难的方面之一,因为维护包含了所有其他阶段的各个方面运维是软件开发中最困难的工作之一,涉及到其他所有环节
用户报告故障,并由维护工程师处理。处理来自用户报告的故障/问题
维修工程师必须具备高超的调试技能

修复代码后
更多步骤:–测试修改是否正确:使用专门构建的测试
测试所做的修改– 检查回归错误:使用存储的测试数据,并添加专门的测试用例
为未来的回归测试存储测试数据–记录所有更改
记录变化
如何最大限度地减少回归错误
回归测试
除了修复问题,修改中不能引入新的故障– 查阅详细的文档并使用构建的测试用例。
通常发生的情况:没有足够的文档/测试用例
问题:没有足够的文档记录和测试用例
最大的– 操作工程师必须从源代码本身推断出所有
避免引入回归错误所需的信息。

软件维护类型
▪ 纠正性维护
25%
纠错性– 软件产品的反应式修改交付后执行以纠正发现的问题
▪ 适应性维护
21%
适应性– 之后执行的软件产品的修改
交付以保持软件产品在中可用于改变或变化的环境;
▪ 完善的维护
50%
完善性– 软件产品交付后的增强功能提高性能或可维护性;
▪ 预防性维护
4%
预防性– 软件产品交付到后的修改检测并更正软件产品中的潜在故障
 

软件进化
▪ 软件进化是软件维护中使用的术语,参考最初开发软件的过程,然后由于各种原因反复更新它。
软件演化是软件维护的重要部分:对软件进行持续的更新
▪ 典型系统90%以上的成本产生于维护阶段,任何成功的软件都将不可避免

面向维修性的施工技术
1.模块化
2.OO设计原则
3.OO设计模式
4.基于状态的构造技术
5.表驱动的构造技术
6.基于语法的构造技术

可维护性指标


1.可维护性:“软件系统的易用性或者可以修改组件以纠正故障、改进性能或其他属性,或适应变化环境
2. 可扩展性:可扩展性—软件设计/实施需要考虑未来的增长,并被视为一项系统性措施扩展系统的能力以及所需的努力水平实现扩展。
3.灵活性:软件在中易于更改的能力对用户需求、外部技术和社会需求的响应环境等。
4.适应性:可适应性—交互式系统的可用性(自适应系统)能够使其行为适应个人用户基于获取的有关其用户及其环境的信息
5.可管理性:软件的效率和容易程度可以对系统进行监控和维护以保持系统性能、安全且运行平稳。
6.可支持性:支持性—软件的保存效率在部署后运行,基于包括质量在内的资源文档、诊断信息和知识渊博的可用的技术人员

一些常用的可维护性指标

1.循环复杂性:测量代码的结构复杂性。
2.代码行数:表示代码中的行数近似值。

可维护性指数(MI)介于0和100之间,表示相对易于维护代码。

值越高表示可维护性越好。计算依据为:– Halstead 体积 (HV)– 循环复杂度 (CC)– 每个模块的平均代码行数 (LOC)– 每个模块的注释行百分比 (COM)。

一些常用的可维护性指标
1.继承的层数-表示扩展到类层次结构根部的类定义的数量。层次结构越深,就越难理解特定方法和字段的定义和/或重新定义的位置。
2.类之间的耦合度-通过参数、局部变量、返回类型、方法调用、通用或模板实例、基类、接口实现、在外部类型上定义的字段和属性修饰来测量与唯一类的耦合。 – 良好的软件设计要求类型和方法应具有高内聚性和低耦合性。 – 高耦合表示设计难以重用和维护,因为它与其他类型之间存在许多相互依赖关系。
3.单元测试覆盖度,单元测试的覆盖度-表示代码库的哪一部分被自动化单元测试覆盖。

模块化设计与模块化原则

模块化编程是一种设计技术,它强调将程序的功能分离为独立的、可互换的模块,使得每个模块都包含执行所需功能的一个方面所需的一切。

设计的目标是将系统划分为模块,并在组件之间分配职责,方式如下:– 模块内高内聚,模块间松耦合
▪ 模块化降低了程序员在任何时候必须处理的总复杂性,前提是:– 将功能分配给模块,将相似的功能分组在一起(关注点分离)
分离关注点– 模块之间存在小而简单、定义良好的接口(信息隐藏)
▪ 内聚和耦合原则可能是评估设计可维护性的最重要的设计原则。

评估的五个标准模块性

模块化设计的五条规则

1.直接映射:模块的结 构与现实世界中问题领域的结构保持一致
2.尽可能少的接口:模块应尽可能少的与其他模块通讯
3.尽可能小的接口:如果两个模块通讯,那么它们应交换尽可能 少的信息
4.显式接口:当A与B通讯时,应明显的发生在A与B的接口之间
5.信息隐藏:经常可能发生变化的设计决策应 尽可能隐藏在抽象接口后面

耦合与内聚

耦合是衡量模块之间依赖性的指标。如果其中一个模块发生更改,则两个模块之间存在依赖关系需要对另一个进行更改。

模块之间的耦合程度由以下因素决定:-模块之间的接口数量(数量),以及-每个接口的复杂性(由通信类型决定)质量

一个精心设计的web应用程序围绕以下内容模块化:
–指定数据和语义的HTML文件
–指定HTML数据的外观和格式的CSS规则
–定义p的行为/交互的JavaScript

内聚性是衡量一个模块的功能或职责之间有多紧密联系的指标。
如果一个模块的所有元素都朝着同一目标努力,那么它就具有很高的内聚性。

最好的设计在模块内具有高内聚性(也称为强内聚性),在模块之间具有低耦合性(也称为弱耦合)。

OO设计原则:SOLID

SOLID:五类设计原则

单一责任原则

ADT 中不应该有多于 1 个原因让其发生变化,否则就拆分开。

如果一个类包含了多个责任,那么将引起不良后果:
– 引入额外的包,占据资源
– 导致频繁的重新配置、部署等

(面向变化的)开放/封闭原则

模块的 行为应是可扩展的,从而该模块可表现出新的行为以满足需求的变化

但模块自身的代码是不应被修改的,扩展模块行为的一般途径是修改模块的内部实现。如果一个模块不能被修改,那么它通常被认为是具有固定的行为

Liskov替换原则

子类型必须能够替换其基类型

派生类必须 能够通过其基类的接口使用,客户端无需了解二者之间的差异

接口隔离原则

不能强迫客户端依赖于它们不需要的接口:只提供必需的接口

避免接口污染,避免胖接口

客户端不应依赖于它们不需要的方法

1.胖接口可分解为多个小的接口
2.不同的接口向不同的客户端提供服务
3.客户端只访问自己所需要的端口

依赖转置原则

高层模块不应该依赖于低层模 块,二者都应该依赖于抽象。
抽象不应该依赖于实现细节,实现细节应该依赖于抽象
使用接口和抽象类

OO设计的两大武器

抽象(abstraction):模块之间通过抽象隔离开来,将稳定部分和容易 变化部分分开

– LSP:对外界看来,父类和子类是“一样”的;
– DIP:对接口编程,而不是对实现编程,通过抽象接口隔离变化;
– OCP:当需要变化时,通过扩展隐藏在接口之后的子类加以完成,而不要修 改接口本身。 

分离(Separation):Keep It Simple, Stupid (KISS)
– SRP:按责任将大类拆分为多个小类,每个类完成单一职责,规避变化,提 高复用度;
– ISP:将接口拆分为多个小接口,规避不必要的耦合。

归纳起来:让类保持责任单一、接口稳定。

语法驱动的构造

String/Stream based I/O

1.输入文件有特定格式,程序需读取文件并从中抽取正确的内容
2.从网络上传输过来的消息,遵循特定的协议
3.用户在命令行输入的指令,遵循特定的格式
4.内存中存储的字符串,也有格式需要

语法的组成部分

为了描述一系列符号,无论它们是字节、字符还是从固定集合中提取的其他类型的符号,我们使用一种称为语法的紧凑表示。语法定义了一组字符串。用语法定义一类“字符串”。语法中的文字字符串称为终端终止节点、叶节点、终止符
-它们之所以被称为终端,是因为它们是表示字符串结构的解析树的叶子。语法解析树的叶子节点
-他们没有孩子,不能再扩大了。无法再 往下扩展
-我们通常用引号写终端,如“http”或“:”。通常表示为用 引号引起的字符串

遵循特定规则,利用 操作符、终止节点和其他非终止节点,构造新的字符串

语法中的运算符

生成表达式中最重要的三个运算符是:
-级联,不是用符号表示,而是用空格表示:
-重复批准,以*表示:
-联合,也称为交替,代表为

语法中的递归

语法中的递归是指一种语言规则,其中某个语法结构可以包含自身的实例。递归在形式语言、编程语言以及自然语言处理中都非常常见。递归语法允许生成无限多的句子或结构,从而使语言能够表达复杂的概念和关系。

在形式语言和计算机科学中,递归语法通常用于定义上下文无关文法(Context-Free Grammar,CFG)。在CFG中,一个非终结符可以通过一个或多个产生式规则生成自身。这种规则称为递归规则。在自然语言中,递归使得句子结构可以嵌套,从而形成复杂的句子

解析树

将语法与字符串匹配可以生成,显示了字符串的各个部分如何与语法的各个部分相对应。–解析树的叶子用终端标记,表示已解析的字符串的部分。–他们没有孩子,不能再扩大了如果我们把叶子连接在一起,我们就得到了原来的字符串。

Markdown和HTML

markdown和html是一种可以使 用普通文本编辑器编写的标记语言,通过简单的标记语法,它可以使普 通文本内容具有一定的格式

正则语法和正则语法表达

正则语法:简化之后 可以表达为一个产生式而不包含任何非终止节点

终结符和操作符的简化表达式可以写成更紧凑的形式,称为正则表达式达式。
正则表
▪ 正则表达式去掉了终结符周围的引号以及终结符和操作符之间的空格,因此它只由终结符、用于分组的括号和操作符组成。
去除引号和空格,从而表达更简洁

▪ 正则表达式也简称为 regex。
– 正则表达式的可读性远低于原始语法,因为它缺少记录每个子表达式含义的非终结符名称。但正则表达式实现速度很快,并且许多编程语言中都有支持正则表达式的库。

上下文无关文法

上下文无关文法左侧只含有一个 非终结符,它定义的语法范畴(或语法单位)是完全独立于这种范畴 可能出现的环境,无需考虑上下文。HTML语法是无关上下文的,但不是规则的。程序设计语言的语法多数都是 CFG但不全是

解析器

输入一段文本,与特定的语法规则建立匹配,输出结果

parser :将文本转化为 parse tree

解析器通常会生成一个解析树,该树显示语法生成是如何扩展为与字符序列匹配的句子的。解析器:解析树
-解析树的根是语法的起始非终结符。
-解析树的每个节点都扩展为语法的一个生成。

解析的最后一步是用这个解析树做一些有用的事情进行下一步的处理
表示语言表达式的递归抽象数据类型称为抽象语法树(AST)。

1959年6月,Backus Normal Form (BNF)首次提出,以递归形式描述 语言的各种成分,凡遵守其规则的程序就可保证语法上的正确性。

Grammar定义语法规则(BNF格式的文本),Parser generator根据 语法规则产生一个parser,用户利用parser来解析文本,看其是否符 合语法定义并对其做各种处理(例如转成parse tree)

在Java中使用正则表达式

正则表达式在编程中被广泛使用。
▪ 在Java中,可以使用正则表达式来操作字符串(请参阅String.split、String.matches、java.util.regex.Pattern
▪ 它们是作为现代脚本的一流功能而内置的Python、Ruby和JavaScript等语言,并且您可以使用
它们在许多文本编辑器中用于查找和替换。

Pattern 对象 是对 regex 正则表达式进行编译之后得到的解析
Matcher 对象 :利用 Pattern对输入字符串进行解析

▪Greedy :匹配器被强制要求第一次尝试匹配时读入整个输入串,如果 第一次尝试匹配失败,则从后往前逐个字符地回退并尝试再次匹配,直 到匹配成功或没有字符可回退。

▪Reluctant:从输入串的首(字符)位置开始,在一次尝试匹配查找中只勉 强地读一个字符,直到尝试完整个字符串。

▪Possessive: 直接匹配整个字符串,如果完全匹配就匹配成功,否则匹配 失败。效果相当于equals()。

安全无漏洞
-语法和正则表达式是字符串和流的声明性规范,库和工具可以直接使用它们。
-这些规范通常比解析手工编写的代码更简单、更直接,而且不太可能出现错误。


易于理解
-语法以比手工编写的解析代码更容易理解的形式捕获序列的形状。
遗憾的是,正则表达式通常不容易理解,因为它们是本可以更容易理解的正则语法的一行简化形式。

准备更改
-语法可以很容易地编辑,但不幸的是,正则表达式更难更改,因为复杂的正则表达式是神秘的,很难理解。

  • 21
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值