10 面向可维护性的构造技术

本章面向另一个质量指标:可维护性——软件发
生变化时,是否可以以很小的代价适应变化?
(1)什么是软件维护
(2)可维护性如何度量
(3)实现高可维护性的设计原则
(4)基于语法的构造技术

(由于时间有限,非考点部分省略)

1 一些常用的可维护性指标

A.圈、环复杂度-度量代码的结构复杂度。
–它是通过计算程序流中不同代码路径的数量创建的。
–具有复杂控制流的程序将需要更多的测试来实现良好的代码覆盖率,并且维护性较差
–CC=E-N+2,CC=P+1,CC=区域数
B. 代码行数-表示代码中的大致行数。
–非常高的计数可能表示某个类型或方法试图做太多的工作,应该进行拆分。
–这也可能表明类型或方法可能难以维护。

C.可维护性指数MI- 计算一个介于0和100之间的索引值,该值表示维护代码的相对容易程度。

D.继承层次度 -指示扩展到类层次结构根的类定义数。

层次结构越深,就越难理解特定方法和字段的定义或/或重新定义位置。

E.类耦合度-通过参数、局部变量、返回类型、方法调用、泛型或模板实例化、基类、接口实现、外部类型上定义的字段和属性修饰来测量到唯一类的耦合
–良好的软件设计要求类型和方法应具有高内聚性和低耦合性
–高耦合表示由于与其他类型的许多相互依赖性,难以重用和维护的设计

F单元测试覆盖率-指示自动单元测试覆盖了代码库的哪些部分。

2 耦合和内聚

设计的目标是将系统划分为模块,并以以下方式在组件之间分配责任:
–模块内的高内聚性
–模块之间的低耦合
模块化降低了程序员在任何时候都必须处理的总复杂性,前提是:
–将功能分配给模块,将相似的功能分组在一起(分离关注点)
–模块之间有小型、简单、定义良好的接口(信息隐藏)

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

1.耦合

耦合是模块之间依赖性的度量。如果一个模块的更改可能需要另一个模块的更改,则两个模块之间存在依赖关系。
▪ 模块之间的耦合程度由以下因素决定:模块间的接口数目,每个接口的复杂度

2.聚合

内聚性是衡量模块的功能或职责之间的关联程度。
如果模块的所有元素都朝着相同的目标努力,则模块具有很高的内聚性。

3 OO设计原则:SOLID

▪ (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个的原因使得一个类发生变化;一个类,一个责任
在这里插入图片描述

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

1.对扩展性的开放

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

2.对修改的封闭

模块自身的代码是不应被修改的

扩展模块行为的正常方法是对该模块进行更改

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

关键的解决方案:抽象技术

软件实体(类、模块、函数等)应对于扩展开发,但对于修改封闭,即使用继承和组合/委派更改类的行为

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rQrQbNrb-1655135846155)(img/image-20220608202019661.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4mmLdbDu-1655135846156)(img/image-20220608202635796.png)]

3 LSP Liskov替换原则

使用指向基类的指针或引用的函数必须能够在不知情的情况下使用派生类的对象”,即子类在替代其基类时应表现良好

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

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

4 ISP 接口隔离原则

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

不要强迫类实现它们无法实现的方法
不要用很多方法污染接口
避免胖接口

“胖”接口具有很多缺点:不够聚合

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

在这里插入图片描述

即改为:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-B7RRQbxg-1655135846159)(img/image-20220608203410241.png)]

5 DIP 依赖转置原则

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cUyuyRui-1655135846159)(img/image-20220608204823800.png)]

高层模块不应该依赖于低层模块,二者都应该依赖于抽象

抽象不应该依赖于实现细节,实现细节应该依赖于抽象

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

小结:OO设计的两大武器
▪ 抽象(abstraction):模块之间通过抽象隔离开来,将稳定部分和容易变化部分分开
– LSP:对外界看来,父类和子类是“一样”的
– DIP:对接口编程,而不是对实现编程,通过抽象接口隔离变化
– OCP:当需要变化时,通过扩展隐藏在接口之后的子类加以完成,而不要修改接口本身
▪ 分离(Separation): Keep It Simple, Stupid (KISS)
– SRP:按责任将大类拆分为多个小类,每个类完成单一职责,规避变化,提
高复用度;
– ISP:将接口拆分为多个小接口,规避不必要的耦合。
▪ 归纳起来:让类保持责任单一、接口稳定。

5 语法驱动的构造

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Q0cYCqjC-1655135846163)(img/image-20220608210539456.png)]

▪ 对于这些类型的序列,语法的概念是一个很好的设计选择:
–它不仅可以帮助区分合法序列和非法序列,还可以将序列解析为程序可以使用的数据结构
–语法生成的数据结构通常是递归数据类型
▪ 正则表达式
–它是一种广泛使用的工具,用于许多需要反汇编字符串、从中提取信息或转换字符串的字符串处理任务。
-根据语法,开发一个它的解析器,用于后续的解析

(1) 语法成分

为了描述符号串,无论它们是字节、字符还是从固定集合中提取的其他类型的符号,我们使用一种称为语法的紧凑表示法。
▪ 用语法定义一组字符串。
–例如,URL语法将指定HTTP协议中合法URL的字符串集。
▪ 语法中的文字字符串称为终止节点、叶节点 、终止符
–它们被称为终止节点,因为它们是表示字符串结构的解析树的叶子。
–他们没有孩子,无法再往下扩展。
–我们通常用引号写终止节点,如“http”或“:”。

语法由一组产生式节点描述, 其中每个产生式节点定义一个非终止节点

B->A|a

A->b

上面两条为产生式,A,B为非终结符

–非终结符就像一个代表一组字符串的变量,而产生式则是该变量在其他变量(非终结符)、运算符和常量(终结符)方面的定义。遵循特定规则,利用操作符、终止节点和其他非终止节点,构造新的字符串
–非终结符是表示字符串的树的内部节点
▪ 语法中的结果具有以下形式
–nonterminal::=终端、非终端和运算符的表达式
▪ 语法的一个非终结符被指定为根。
–语法识别的字符串集与根非终结符匹配。
–此非终结符通常称为root或start。(根节点)

(2) 语法中的运算符

产生表达式中最重要的三个运算符是:
–串联,不是用符号表示,而是用空格表示:
x::=y z x匹配y后跟z
–重复,表示为*:
x::=y * x匹配零个或多个y
–联合,也称为选择,由|表示:
x::=y | z x与y或z匹配

其他运算符只是语法糖(即,它们是相当于三大运算的组合):

–可选**(0或1次出现),由?表示:
x::=y? x是y或是
空字符串**
–1次或多次出现:用+:
x::=y+
x是一个或多个y(相当于x::=y y*)
–字符类[…],表示包含方括号中列出的任何字符的长度为1的字符串:
x::=[a-c]相当于x::=‘a’|‘b’|‘c’
x::=[aeiou]相当于x::=‘a’|‘e’|‘i’|‘o’|‘u’
–反转字符类[ ^…],表示包含括号中未列出的任何字符的长度为1的字符串:
x::=[ ^a-c ]相当于x::=‘d’|‘e’|‘f’|。。。(所有其他字符)

在这里插入图片描述

==优先级: ? +优先级最高,连接次之,| 最低==*

(3)语法中的递归

回到我们刚才的例子,主机名可以有两个以上的组件,并且可以有一个可选的端口号,那么可以这样表示(递归形式)

在这里插入图片描述

当然hostname也可以定义为非递归形式 hostname ::= (word ‘.’)+ word

(4) 语法分析树

将语法与字符串匹配可以生成一个解析树,该树显示字符串的各个部分如何对应于语法的各个部分
–解析树的叶子用终结节点标记,表示已解析的字符串部分
–他们没有孩子,不能再扩大。
–如果我们将叶子连接在一起,我们将返回原始字符串

在这里插入图片描述
在这里插入图片描述

要走得更远,我们还需要做更多的事情:

–推广http以支持URL可以拥有的其他协议,如ftp、https等…
–将末尾的/泛化为斜线分隔的路径,例如http://didit.csail.mit.edu:4949/homework/lab1/
–允许主机名具有完整的合法字符集,而不仅仅是a-z,例如http://ou812.com/

(5) 正则语法和正则表达式

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

正则语法有一个特殊的特性:通过将每个非终结符(根语法除外)替换为其右侧,可以将其简化为根的单个产生式,仅在右侧使用终结符和运算符
在这里插入图片描述

终结节点和运算符的简化表达式可以写成更紧凑的形式,称为正则表达式
▪ 正则表达式去掉了终结节点周围的引号以及终结节点和运算符之间的空格,因此它只包含终结字符、分组括号和运算符字符。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-E5SOXdED-1655135846168)(img/image-20220608220922268.png)]

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

regex中的一些特殊运算符

. 任何单个字符
▪ \d任意数字,与[0-9]相同
▪ \s任何空格字符,包括空格、制表符、换行符
▪ \w任何单词字符,包括下划线,与[a-z A-Z _ 0-9]一致

转义运算符或特殊字符,使其与字面上的匹配

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-g1SzchCw-1655135846169)(img/image-20220608221437966.png)]

(6) 在Java中使用正则表达式

regex在编程中被广泛使用。
▪ 在Java中,可以使用正则表达式来操纵字符串(请参见String.split、String.matches、Java.util.regex.Pattern)。

java.util.regex包主要由三个类组成:
Pattern 对象是正则表达式的编译表示。Pattern类不提供公共构造函数。要创建模式,必须首先调用其公共静态编译方法之一,然后该方法将返回Pattern对象。这些方法接受正则表达式作为第一个参数。
Matcher对象是解释模式并对输入字符串执行匹配操作的引擎。与Pattern类一样,Matcherdefines没有定义公共构造函数。通过调用Pattern对象上的Matcher方法,可以获得Matcher对象。
PatternSyntaxException对象是未经检查的异常,表示正则表达式模式中存在语法错误。

在这里插入图片描述
在这里插入图片描述

强制将元字符视为普通字符的两种方法:
•在元字符前面加一个反斜杠\

•将其括在\Q(引号开头)和\E(引号结尾)中。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值