哈工大软件构造第10章总结

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

10.1 软件维护和演化

软件工程中的软件维护是软件产品交付后的修改,以纠正故障、提高性能或其他属性。

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

  • 模块化
  • OO设计原则
  • OO设计模式
  • 基于状态的构造技术
  • 表驱动的构造技术
  • 基于语法的构造技术

10.2 维修性指标

名词:

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

常见问题:

  • 设计结构是否简单
  • 模块之间是否松散耦合
  • 模块内部是否高度聚合
  • 是否用委托代替继承
  • 是否存在许多循环
  • 是否存在重复代码

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

  • 圈复杂度:具有复杂控制流的程序将需要更多的测试来实现良好的代码覆盖率,并且维护性较差
  • 代码行数(直观但不准确)
  • 可维护性指数:计算一个介于0和100之间的索引值,该值表示维护代码的相对容易程度,越高越好维护。
  • 继承的层次数
  • 类之间的耦合度
  • 单元测试的覆盖度:指示自动单元测试覆盖了代码库的哪些部分

代码异味:代码中出现的不好的现象

10.3 模块化设计和模块化原则

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

模块化编程的特征(好处):高内聚、低耦合、分离关注点(将功能分配给将类似功能分组在一起的模块)、信息隐藏

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

10.3.1 评估模块化的五个标准

  • 可分解性
  • 可组合性(较大组件由较小组件组成)
  • 可理解性
  • 可持续性:发生变化时受影响范围最小
  • 出现异常之后的保护:出现异常后受影响范围最小
10.3.1.1 可分解性

将问题分解为各个可独立解决的子问题

目标:使模块之间的依赖关系显式化和最小化

10.3.1.2 可组合性

可容易的将模块组合起来形成新的系统

目标:使模块可在不同的环境下复用

10.3.1.3 可理解性

每个子模块都可被系统设计者容易的理解

10.3.1.4 可持续性

规格说明小的变化将只影响一小部分模块,而不会影响整个体系结构

10.3.1.5 出现异常之后的保护

运行时的不正常将局限于小范围模块内

10.3.2 模块化设计的五条规则

直接映射

直接映射:模块的结构与现实世界中问题领域的结构保持一致

直接映射会对持续性和可分解性产生影响

尽可能少的接口

模块应尽可能少的与其他模块通讯

对以下评价标准产生影响:可持续性、保护性、可理解性、可组合性

尽可能小的接口

如果两个模块通讯,那么它们应交换尽可能少的信息

对“可持续性”和“保护性”产生影响

显式接口

当A与B通讯时,应明显的发生在A与B的接口之间

受影响的评价标准:可分解性、可组合性、可持续性、 可理解性

信息隐藏

经常可能发生变化的设计决策应尽可能隐藏在抽象接口后面

每个模块的设计者必须选择模块属性的子集(暴露在外的部分)作为模块的官方信息,以供客户端模块使用。

10.3.3 低耦合和高内聚

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

模块之间的耦合程度取决于:模块之间的接口数量(数量)和每个接口的复杂性

eg:

当耦合度较高时,内聚度往往较低,反之亦然。最好的设计在模块内具有高内聚性(也称强内聚性),模块之间具有低耦合性(也称弱耦合性)。

10.4 设计原则:SOLID

10.4.1 单一责任原则(SRP)

一个类只专注于一件事,责任是产生变化的原因。

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

  • 引入额外的包,占据资源
  • 导致频繁的重新配置、部署等

eg:(一个反例)

不要刻意将功能分离,当可以预见到变化(扩展)时,再采取SRP原则。

10.4.2 (面向变化的)开/关原则(OCP)

模块的行为应是可扩展的,从而该模块可表现出新的行为以满足需求的变化。
但模块自身代码不应该被修改,扩展模块行为的一般途径是修改模块的内部实现,如果一个模块不能被修改,那它常备认为具有固定的行为。
解决方案:抽象技术,类的行为用继承和委托机制。

eg:(一个反例)

10.4.3 Liskov替换原则(LSP)

见第9章

10.4.4 接口隔离原则(ISP)

只提供客户必须的接口,避免接口污染、胖接口。
将胖接口分解为多个小接口,不同用户使用不同的接口,只能访问所需要的端口。

10.4.5 依赖转置原则(DIP)

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

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

委托的时候要通过interface建立联系。

核心是使用接口来缓冲,即将接口作为参数传入类中,作为临时变量或者成员变量。将接口的实现类和调用者隔离。

总结:让类保持责任单一、接口稳定。

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

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

分离(Separation):Keep It Simple, Stupid (KISS)

  • SRP:按责任将大类拆分为多个小类,每个类完成单一职责,规避变化,提高复用度;
  • ISP:将接口拆分为多个小接口,规避不必要的耦合。

10.5 语法驱动的构造

有一类应用,从外部读取文本数据,在应用中做进一步处理。具体而言,字节或字符序列可能是输入文件、网络信息、命令行指令、内存中的数据等。

对于这类序列,语法的概念是一个很好的设计选择:使用语法判断字符串是否合法,并解析成程序里使用的数据结构。

10.5.1 语法成分

一个语法定义了一类“字符串”,语法由一组产生式节点描述, 其中每个产生式定义一个非终结符。我们可以遵循特定规则,利用操作符、终止节点和其他非终止节点,构造新的字符串

终止符(叶节点,无法再往下扩展)、非终止符(内节点)

10.5.2 语法中的运算符

连接符、重复符、选择符

eg:

其他运算符:

“?”(选择)、“+”(至少一个)、“[]”(表示包含方括号中列出的任何字符的长度为1的字符串)、“[^]”(与前面取反)

eg:

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

10.5.3 语法中的递归

10.5.4 分析树

将语法与字符串匹配可以生成一个解析树,该树显示字符串的各个部分如何与语法的各个部分相对应。解析树的叶子用终端标记,表示已解析的字符串部分,他们没有子节点,不能再扩大规模了。

如果我们将叶子连接在一起,我们将得到原始字符串。

eg:

可以发现,hostname是二叉树,表示使用了递归;如果不使用递归 ,则如下图:

eg:

10.5.5 markdown和HTML

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

10.5.6 正则语法和正则表达式

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

url、markdown是正则的,但是html不是正则的。

终端和运算符的简化表达式可以写成更紧凑的形式,称为正则表达式。正则表达式去掉了引号以及空格,因此它只包含端子字符、分组括号和运算符字符,从而表达更简洁,但也更难懂。正则表达式也简称为regex。

正则表达式中的符号:

eg:

上下文无关文法左侧只含有一个非终结符,它定义的语法范畴(或语法单位)是完全独立于这种范畴可能出现的环境,无需考虑上下文

10.5.8 在Java中使用正则表达式

在本课程里,只需要能够熟练掌握正则表达式regex这种“基本语法”,并熟练使用JDK提供的regex parser进行数据处理即可.

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

Java.util.regex包中有三个类:

  • Pattern对象是对regex正则表达式进行编译之后得到的结果
  • Matcher对象:利用Pattern对输入字符串进行解析
  • PatternSyntaxException指示正则表达式模式中的语法错误

eg:

字符类:

预定义的字符类:

有三种匹配模式:

  • Greedy :匹配器被强制要求第一次尝试匹配时读入整个输入串,如果
    第一次尝试匹配失败,则从后往前逐个字符地回退并尝试再次匹配,直
    到匹配成功或没有字符可回退。
  • Reluctant:从输入串的首(字符)位置开始,在一次尝试匹配查找中只勉
    强地读一个字符,直到尝试完整个字符串。
  • Possessive: 直接匹配整个字符串,如果完全匹配就匹配成功,否则匹配
    失败。效果相当于equals()。

边界匹配器:

模式匹配在java.lang.String中的使用:

  • public boolean matches(String regex):告诉此字符串是否与给定的正则表达式匹配。调用了Pattern.matches(regex, str)
  • public String [] split(String regex,int limit):围绕给定正则表达式的匹配项拆分此字符串。调用了Pattern.compile(regex).split(str, n)
  • public String [] split(String regex):围绕给定正则表达式的匹配项拆分此字符串。
  • public String replace(CharSequence target,CharSequence replacement)
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值