目录
Chapter 6: Maintainability-Oriented Software Construction Approaches
6.0、Maintainability-Oriented Software Construction Approaches
第6章主要讲的是面向可维护的软件构造方法;
第一节介绍了什么是软件维护,以及可维护性的度量标准,和实现可维护性的设计原则;
第二节介绍了可维护性的设计模式;
第三节介绍除了OO设计模式以外的其他能够提升软件可维护性的构造技术:基于状态、表驱动、基于语法的构造技术。
考点:
可维护性的常见度量指标
聚合度和耦合度
SOLID
语法、正则表达式
6.1、Metrics and Construction Principles for Maintainability
6.1.1、Metrics of Maintainability
〇几个概念Concepts
软件维护:修复错误、改善性能(软件的大部分成本来自于维护阶段);
软件演化:对软件进行持续的更新;
软件熵(Entropy):当系统被修改时,它的混乱度(熵)会增加;
①Some Names Of Maintainability
可维护性的范畴
Ready for Change, Ready for Extension.
Maintainability(可维护性) | 软件系统或者组件可以轻松的修改以纠正错误,性能和属性或适应变化的环境 |
Extensibility(可扩展性) | 软件设计、实现将未来的发展考虑在内,并被视为系统扩展能力的系统测量和实现扩展所需的工作量 |
Flexibility(灵活性) | 软件根据用户需求,外部技术和社会环境等容易改变的能力 |
Adaptability(可适应性) | 交互式系统(自适应系统)能够根据获取的有关用户及其环境的信息,使其行为适应个人用户的能力 |
Manageability(可管理性) | 能够有效和轻松地监控和维护软件系统,以保持系统的正常运行,安全并平稳运行 |
②Some comman-used maintainbility metrics
一些评估可维护性的常用标准
Cyclomatic Complexity(圈复杂度) | 衡量代码的结构复杂性;分支(if-else,switch)越多,控制流程越复杂,越难维护,圈复杂度越高 |
Lines Of Code(代码行数) | 指示代码大致的行数,行数太多,表明该类或方法做了太多工作,应该将不同的工作分离到多个类中实现 |
Maintainability Index(MI,可维护性指数) | 计算0到100之间的索引值,表示维护代码的相对容易性,越高意味着更好的可维护性;基于Halsted Volume(HV,基于源代码中不同运算符和操作符数量的合成度量)、Cyclomatic Complexity(CC,圈复杂度)、Average number of lines of code per module(LOC,每个模块平均的代码行数)、Percentage of comment lines per module(COM,每个模块注释行数的百分比) |
Depth Of Inheritance(继承的层数) | 继承的层数越深,就越难理解特定方法和变量是在哪个父类被定义或重新定义的 |
Class Coupling(类之间的耦合度) | 通过参数、局部变量、返回类型、方法调用、泛型或模板实例化、基类、接口实现、在外部类型上定义的字段以及属性修饰来测量对唯一类的耦合。良好的软件设计决定了类型和方法应该具有高内聚性和低耦合性(可以理解为是否相互联系紧密,相互影响,如果一个类与其他类是基本相互独立的,不相互影响,有较少的依赖关系,则具有低耦合性)。 |
Unit test coverage(测试覆盖度) | 表示代码库的哪些部分被自动化单元测试覆盖 |
6.1.2、Modular Design and Modularity Principles
模块化编程是一种强调将程序功能分离为独立,可互换模块的设计技术,每种模块都包含执行所需功能一个方面所需的一切;
模块化意味着将系统划分为组件或模块,每个组件或模块都可以与系统的其他部分分开设计,实施,测试,推理和重复使用;
目标是实现高内聚和低耦合。
①Five Criteria for Evaluating Modularity
实现模块化的五个标准
Decomposability(可分解性) | 将问题分解为各个可独立解决的子问题 | 目标:使得模块之间的依赖关系显式化和最小化;例如:自上而下的结构设计 |
Composability(可组合性) | 可以容易的将模块组合起来形成新的系统 | 目标:使得模块可在不同环境下复用;例如:Math库,UNIX command & pipes |
Understandablity(可理解性) | 每个子模块都可被设计者容易的理解 | Unix shell像Program1 | Program2 | Program3 |
Continuity(可持续性) | 小的变化将只影响一小部分模块,而不会影响整个体系结构 | 例如:宏常量的使用;统一接入模式 |
Protection(出现异常之后的保护) | 运行时的不正常将局限于小范围模块内 | 在源处进行验证输入 |
②Five Rules of Modularity Design
五个模块化设计的原则
Direct Mapping(直接映射) | 模块的结构与需要解决的实际问题领域的结构保持一致(对持续性和可分解性有影响) |
Few Interfaces(尽可能少的接口) | 模块尽可能少的与其他模块通讯(对可持续性、保护性、可理解性、可组合性产生影响) |
Small Interfaces(尽可能小的接口) | 如果两个模块通讯,那么它们应交换尽可能少的信息(限制模块之间通讯的带宽,对可持续性和保护性产生影响)) |
Explicit Interfaces(显示接口) | 当A与B通讯时,应明显的发生在A与B的接口之间(不要直接使用类,通过接口调用,eg. superInterface a = new subclass(), 在每个subclass变量声明为superInterface类型对象;对可分解性、可组合性、可持续性、 可理解性产生影响) |
Information Hiding(信息隐藏) | 经常可能发生变化的设计决策应尽可能隐藏在抽象接口后面 |
③Coupling(耦合性) and Cohesion(内聚性)
耦合性 | 内聚性 |
模块之间依赖关系(关联程度)的度量。如果两个模块之间的变化可能需要另一个模块的变更,则两个模块之间存在依赖关系 | 衡量一个模块的功能或责任有多强烈程度的一个指标(模块的功能强度),即一个模块内部各个元素彼此结合的紧密程度的度量 |
块之间的耦合程度取决于:模块之间的接口数量(数量)和每个接口的复杂性(由通信类型决定)(质量) | 如果模块的所有元素都朝着相同的目标努力,则模块具有高度的凝聚力 |
最好的设计在模块内具有高内聚力(也称为强内聚力)和模块之间的低耦合(也称为弱耦合) |
图示高内聚性和低耦合性
6.1.3、OO design principles
①SOLID
The Single Responsibility Principle(SRP,单一责任原则) | The Open-Closed Principle(OCP,开放-封闭原则) | The Liskov Substitution Principle(LSP,Liskov替换原则) | The Interface Segregation Principle(ISP,接口聚合原则) | The Dependency Inversion Principle(DIP,依赖转置原则) |
一个类,一个责任,只执行一个主要任务;不应有多于一个的原因使得一个类发生变化;通过分解,将两个无关的责任分离开来,分别放置在两个类中,使得每个类只负责一个类型的任务 | 对扩展性的开放,对修改的封闭;使用抽象技术实现,构造一个抽象的父类,包含针对所有类型的子类都通用的代码(避免使用if & case语句,应该将具体针对的对象抽象出来),实现对修改的封闭,当出现新的子类型,只需要从抽象父类派生具体子类,实现对扩展的开放 | 子类型必须能够替换其基类型;派生类必须能够通过其基类的接口使用,客户端无需了解二者之间的差异(使用父接口而不是直接的子类) | 接口是属于客户端的而不是继承的,不能为了继承而使得接口臃肿;应该使得接口是聚合的,将功能复杂的接口分解成多个小接口,不同的接口分别向不同的客户端提供服务,客户端只访问自己需要的接口 | 抽象的模块不应依赖于具体的模块,具体应依赖于抽象;当定义一个对象时,不应该定义为具体的类型,而应该是接口类型 |
6.2、Design Patterns For Maintainability
简略介绍
Creational patterns | Factory Method Pattern | 创建对象而不是指定要创建的确切类(创建一个实例对象返回,new ConcreteTwo().makeObject(),遵循OCP) |
Abstract factory pattern | 将具有共同特征的对象工厂分组(提供接口以创建一组相关、相互依赖的对象,但不需要指明其具体类) | |
Builder pattern | 通过分离构造和表示来构造复杂的对象(创建一个对象,可能需要创建多个类型的实例对象,进行组合,则在这个模式中进行创建和返回) | |
Structural patterns | Bridge | 将抽象从其实现中分离出来,以使两者可独立地变化 |
Proxy | 为另一个对象提供一个占位符来控制访问,降低成本并降低复杂性(某个对象比较私密、重要,不希望被client直接访问到,故设置proxy,在二者之前建立防火墙) | |
Composite | 组成零个或多个相似的对象,以便它们可以作为一个对象进行操作(目的是在同类型的对象之间建立起树型层次结构,一个上层对象可包含多个下层对象) | |
Behavior Patterns | Mediator | 通过成为唯一具有其方法详细内容的类,允许类之间的松散耦合 |
Observer | 是一种发布、订阅模式,允许许多观察者对象查看事件 | |
Visitor | 通过将方法的层次结构移动到一个对象中将算法从对象结构中分离出来 | |
Chain of responsibility | 将命令委托给一系列处理对象 | |
Command | 创建封装动作和参数的对象 |
6.3、Maintainability-Oriented Construction Techniques
6.3.1、State-based construction
核心思想:将程序看作是一个有限状态自动机,侧重于对”状态”及状态转换的抽象和编程。
①State Pattern
依旧逃离不了无处不在的delegation:ADT的操作看作是状态转换,都委派给了外部的state对象 ;
不同的“状态”子类:delegate ADT在该状态Si下能够发生的所有行为,即从Si到所有可能其他状态的转换;
②Memento Pattern
记住对象的历史状态,以便于“回滚”;
Originator:需要“备忘”的类;
Caretaker:添加originator的备忘记录和恢复
Memento:备忘录,记录originator对象的历史状态
Demonstration:实例主函数,创建Caretaker和Originator对象,每次修改Originator类后,调用Caretaker进行备忘;
6.3.2、Grammer-based construction
理解语法生成和正则表达式操作符的思想;
能够读取语法或正则表达式,并确定它是否匹配字符序列;
能够编写语法或正则表达式来匹配一组字符序列并将其解析为一个数据结构;
语法生成
举例,可以自己生成自己
/**
* URl
* http://stanford.edu/
* http://google.com/
*/
url ::= 'http://' [a-z]+ '.' [a-z]+ '/'
/* 语法解析树表示, 具有多个非终端的文法 */
url ::= 'http://' hostname '/'
hostname ::= word '.' word
word ::= [a-z]+
/**
* http://didit.csail.mit.edu:4949/
*/
url ::= 'http://' hostname (':' port)? '/' /* 端口可有可无 */
hostname ::= word '.' hostname | word '.' word /* 可以有一个或多个'.' */
//hostname ::= (word '.')+ word
port ::= [0-9]+
word ::= [a-z]+
/**
* Markdown 和 HTML的斜体
* Markdown
* This is _italic_.
* HTML
* Here is an <i>italic</i> word.
*/
markdown ::= ( normal | italic ) * /* 由斜体和一般字体构成 */
italic ::= '_' normal '_'
normal ::= text
text ::= [^_]* /* '^'为取反符号,除了'_'不能取,其余任取 */
html ::= ( normal | italic ) * /* 由斜体和一般字体构成 */
italic ::= '<i>' html '</i>'
normal ::= text
text ::= [^<>]*
/**
* Think
* 使用上述匹配
* markdown不能嵌套
* a_b_c_d_e -> b, d为斜体
* html可以嵌套
* a<i>b<i>c<i>d<i>e -> b,c,d均为斜体
*/
a_b_c_d_e(b,d为斜体效果)
abcde(bcd均为斜体效果)
Regular Grammar(正则表达式)
不能自己产生自己(html ::= ( [ ^<>]* | ‘’ html ‘’ )* 错误)
在java中”\”要用”\”进行转义
Regular Expression, 正则表达式的使用