HIT软件构造课程复习笔记(五)

5.1 可维护性的度量与构造原则

软件维护和发展

软件维护:修复错误、改善性能
运维工程师:运维是软件开发中最困难的工作之一 ,其工作就是处理来自用户报告的故障/问题,大多数运维任务并非需要涉及代码修改。但是有时候不得不回到代码层面。测试所作的修改后还要进行回归测试和记录变化,除了修复问题,修改中还不能引入新的故障,最大的问题是修改后没有足够的文档记录和测试
软件演化:对软件进行持续的更新
软件的大部分成本来自于维护阶段,软件维护不仅仅是运维工程师的工作,而是从设计和开发阶段就开始了,在设计与开发阶段就要考虑将来的可维护性

可维护性的指标

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

圈复杂度:检测代码结构的复杂性,它是通过计算程序流中不同代码路径的数量创建的。拥有复杂控制流的程序将需要更多的测试来达到良好的代码覆盖率,并且将更不易维护

代码行数:该指标过高表明一个类型或方法尝试做太多的工作,应该被分割,它还可能表明类型或方法可能难以维护

可维护性指数MI:计算表示维护代码相对容易的0到100之间的索引值

继承的层次数:扩展到类层次结构的根的类定义的数量。层次结构越深,就越难以理解在何处定义或重写特定的方法和属性

类之间的耦合度:通过参数、局部变量、返回类型、方法调用、泛型或模板实例化、基类、接口实现、在外部类型上定义的属性与其装饰来度量对唯一类的耦合

单元测试的覆盖度:检测代码的哪个部分被自动检测覆盖到

模块化设计和模块化原则

模块化编程是一种设计技术,强调将程序的功能分离为独立的、可互换的模块,这样每个模块都包含了执行所需功能的一个方面所必需的一切。例如在结构化编程和面向对象编程中,将整个程序的代码分解为若干块。

高内聚/低耦合/分离关注点/信息隐藏

模块化标准:可分解性/可组合性/可理解性/可持续性/出现异常之后的保护
模块化规则:直接映射/信息隐藏/尽可能小而少的显式的接口

耦合:耦合是对模块之间依赖关系的度量。如果一个模块中的更改可能需要另一个模块中的更改,则两个模块之间存在依赖关系。模块之间接口的数量和每个接口的复杂性决定了两个模块间的耦合程度。

内聚:内聚性是对模块的功能或职责之间的关联程度的度量。如果一个模块的所有元素都朝着同一个目标工作,那么这个模块就具有高内聚性。一个好的模块设计应该是高内聚低耦合的

OO设计原则:SOLID

SRP单一责任原则:不应该有多于1个原因让你的ADT发生变化,否则就拆分开。如果一个类包含了多个责任,那么将引起不良后果:引入额外的包,占据资源或是导致频繁的重新配置、部署等。这是最简单但最难做好的原则

OCP开放/关闭原则:对扩展性的开放——模块的行为应是可扩展的,从而该模块可表现出新的行为以满足需求的变化;对修改的封闭——但模块自身的代码是不应被修改的,扩展模块行为的一般途径是修改模块的内部实现,如果一个模块不能被修改,那么它通常被认为是具有固定的行为

LSP灵活可替换原则:子类型必须能够替换其基类型,派生类必须能够通过其基类的接口使用,客户端无需了解二者之间的差异

ISP接口隔离原则:不能强迫客户端依赖于它们不需要的接口:只提供必需的接口。“胖”接口会使得结构不够聚合,可分解为多个小的接口,不同的接口向不同的客户端提供服务,客户端只访问自己所需要的端口

DIP依赖倒置原则:抽象的模块不应依赖于具体的模块,具体应依赖于抽象。换句话说,delegation的时候, 要通过interface建立联系,而非具体子类

GRASP:关于如何为“类”和“对象”指派“职责”的一系列原则

5.2 面向可维护性的设计模式

创建类模式

工厂方法模式:当client不知道要创建哪个具体类的实例,或者不想在client代码中指明要具体创建的实例时,用工厂方法。 定义一个用于创建对象的接口,让其子类来决定实例化哪一个类,从而使一个类的实例化延迟到其子类。这样满足了对扩展的开放,对修改已有代码的封闭

抽象工厂模式:提供接口以创建一组相关/相互依赖的对象, 但不需要指明其具体类。创建的不是一个完整产品,而是“产品族”(遵循固定搭配规则的多类产品的实例),得到的结果是:多个不同产品的 object,各产品创建过程对client可见,但“搭配”不能改变。本质上,Abstract Factory是把多类产品的factory method组合在一起,即“工厂的工厂”

结构性模式

代理模式:某个对象比较“敏感”/“私密”/“贵重”,不希望被client直接访问到,故设置proxy,在二者之间建立防火墙。为了便于改变,客户端访问抽象接口,创建代理并访问, 降低代价。代理模式的目的是隔离对复杂对象的访问,降低难度/代价,定位在“访问/使用行为” ;而类似的适配器模式的目的是消除不兼容,目的是B以客户端期望的统一的方式与A建立起联系。

行为类模式

观察者模式:“粉丝”对“偶像”感兴趣,希望随时得知偶像的一举一动;粉丝到偶像那里注册,偶像一旦有新闻发生,就推送给已注册的粉丝 (回调callback粉丝的特定功能)这要求从属的状态必须和主状态保持一致,因此我们定义以下四种类型的对象:
1.抽象主体:维护一个关注列表,主状态改变时通知其中的每一个
2.抽象观察者:定义更新关注列表的方法
3.实例主体:保存关注者的数据,更新状态时全体通知
4.实例观察者:通过获取更新信息更新关注人的状态
Java中提供了Observer接口和Observable抽象类

访问者模式:对特定类型的object的特定操作(visit),在运行时将二者动态绑定到一起,该操作可以灵活更改,无需更改被visit的类,本质上将数据和作用于数据上的某种/些特定操作分离开来。该模式为ADT预留一个将来可扩展功能的“接入点”,外部实现的功能代码可以在不改变ADT本身的情况下通过delegation接入ADT

相较于迭代器模式,访问者模式在特定ADT上执行某种特定操作,但该操作不在ADT内部实现,而是delegate到独立的visitor对象,客户端可灵活扩展/改变visitor的操作算法,而不影响ADT;迭代器模式以遍历的方式访问集合数据而无需暴露其内部表示,将“遍历”这项功能delegate到外部的iterator对象。
相较于策略模式,访问者模式强调是的外部定义某种对ADT的操作,该操作于ADT自身关系不大(只是访问ADT),故ADT内部只需要开放accept(visitor)即可,client通过它设定visitor操作并在外部调用。 而策略模式强调是对ADT内部某些要实现的功能的相应算法的灵活替换。这些算法是ADT功能的重要组成部分,只不过是delegate到外部strategy类而已。总之,visitor是站在外部client的角度,灵活增加对ADT的各种不同操作(哪怕ADT没实现该操作),strategy则是站在内部ADT的角度,灵活变化对其内部功能的不同配置。

设计模式的共性和差异

共性样式1:

在这里插入图片描述
共性样式2:
在这里插入图片描述

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

基于状态的构造技术

基于状态编程:

使用有限状态机来定义程序的行为、使用状态来控制程序的执行,根据当前状态,决定下一步要执行什么操作、执行操作之后要转移到什么新的状态。

基于自动机的编程:将程序看作是一个有限状态自动机,侧重于对“状态”及“状态转换” 的抽象和编程。程序的执行被分解为一组自动执行的步骤,各步骤之间的通讯通过“状态变量”进行,程序执行就可看作是各自动步骤的不断循环,使用枚举类型enum定义状态,使用二维数组定义状态转换表

状态模式:不要使用if/else 结构在ADT内部实现状态转换(考虑将来的扩展和修改)使用delegation,将状态转换的行为委派到独立的state对象去完成

备忘录模式:记住对象的历史状态,以便于“回滚”。该模式需要三个角色:发起者——需要备忘的类;管理员——添加发起者的备忘记录和恢复;备忘录——记录发起者的历史状态

基于语法的构造技术

有一类应用,从外部读取文本数据, 在应用中做进一步处理。输入文件有特定格式,程序需读取文件并从中抽取正确的内容;从网络上传输过来的消息,遵循特定的协议;用户在命令行输入的指令,遵循特定的格式;内存中存储的字符串,也有格式需要…

语法:使用grammar判断字符串是否合法,并解析成程序里使用的数据结构,通常是递归的数据结构
正则表达式:根据语法,开发一个它的解析器,用于后续的解析。用语法定义一个“字符串” ,语法解析树的叶子节点/终止节点通常为字符串字面值,无法再向下扩展。

语法一般由一组产生式节点分别定义非终止节点,遵循特定规则,利用操作符、终止节点和其他非终止节点,构造新的字符串。
产生式节点语法格式:
非终结符::=终结符、非终结符和操作符的表达式
语法的一个非终止节点被设为根节点
语法的三种基本操作:
连接:x ::= y z
重复:x ::= y*
选择:x ::= y | z
语法糖:
至多一次:x ::= y?
至少一次:x ::= y+
字符集:x ::= [a-c] x ::= [aeiou]
倒序字符集:x ::= [^a-c]
按照惯例,后缀操作符*、?和+具有最高的优先级,这意味着我们要优先应用它们;接下来是连接操作;| 的优先级是最低的,括号的使用可以改变优先级关系

语法的递归:hostname ::= word ‘.’ hostname | word ‘.’ word

解析树:将语法与字符串匹配可以生成一个解析树,该树显示字符串的部分与语法的部分如何对应。解析树的叶子用终止节点标记,表示已经解析的字符串的部分(无法向下扩展)如果我们把叶子连接在一起,我们就得到了原始的字符串

正则语法:简化之后可以表达为一个产生式而不包含任何非终止节点
正则表达式:正则表达式去掉了终止节点旁边的引号、终止节点和操作符之间的空格,因此它只由终端字符、用于分组的括号和操作符字符组成
正则特殊操作符:“.”为任意字符,“\d”为任意数字,“\s”为任意空白符,“\w”为任意字母数字(包括下划线)

Java中的正则表达式:
String.split, String.matches, java.util.regex.Pattern
Pattern是对regex正则表达式进行编译之后得到的结果,Matcher是利用Pattern对输入字符串进行解析

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值