第六章代码的可维护性——可维护性的度量和构造原则
1.软件的维护与演化
我们一直说软件维护,那么什么是软件维护呢?其实就是修改错误、改善性能的过程。运维是软件开发中最困难的工作之一,他需要处理各种来自用户报告的问题与故障。
软件维护主要针对一下几种(数据来源未知2333):
- 纠错性25%
- 适应性21%
- 完善性50%
- 预防性4%
“变化”在软件生命周期中是不可避免的!那么如何在最初的设计中充分考虑到未来的变化,避免因为频繁的变化导致软件复杂度增加和质量的下降呢?这就是我们这章要说的事情——提高软件的适应性,延续软件生命。注意软件维护不仅仅是运维工程师的工作,而是从设计和开发阶段就开始了。所以在设计开发的过程中就要考虑到将来的可维护性,使设计方案容易改变。
这张将会着重讲解几个基于可维护性建设的例子:
- 模块化
- OO设计原则
- OO设计模式
- 基于状态的构造技术
- 表驱动的构造技术
- 基于语法的构造技术
2.可维护性的度量
首先来说几个常用的可维护性度量指标:
- 圈复杂度(Cyclomatic Complexity):度量代码的结构复杂度。
- 代码行数:……
- 可维护性指数(Maintainnbility Index)MI:计算0到100之间的索引值。表示维护代码的相对容易性,高价值意味着更好地可维护性。
- 继承的层次数(Depth of Inheritance):就是继承深度,英文更通俗些。
- 类之间的耦合度(Class Coupling):通过参数,局部变量,返回类型,方法调用,泛型或模板实例化,基类,接口实现,在外部类型上定义的字段和属性修饰来测量耦合到唯一类。
- 单元测试覆盖率(Unit test coverage):只是代码库的那些部分被自动化单元测试覆盖。
3. 模块化设计和模块化原则
其设计目的只有两点:
- 模块内高内聚
- 模块间低耦合
至于如何评估模块化也有五个标准:
- 可分解性(Decomposability)
- 可组合性(Composability)
- 可理解性(Understandability)
- 可持续性(Continulity)
- 出现异常之后的保护(protection)
模块化设计的五个规则:
- 直接映射
- 尽可能少的接口
- 尽可能小的接口
- 显示接口
- 信息隐藏
然后我们来说一下耦合与内聚的概念(Coupling and Cohension)
耦合是模块之间的依赖关系的度量。
对“依赖关系”解释:如果两个模块之间的变化可能需要另一个模块的变更,这两个模块之间存在依赖关系。
模块之间的耦合主要取决于:
- 模块之间接口的数量(质量)
- 每个接口的复杂度
而聚合是指衡量一个模块的功能或责任的强烈程度的一个指标。如果一个模块的一切的结构都朝着相同的目标努力,那么他就具有很高的聚合度。
模块化设计所追求的目标就是高内聚低耦合!
4.OO设计原则:SOLID
不不不!这可并不是固态原则……他是五个类设计原则的缩写:
- (SRP)单一责任原则The Single Responsibility Principle
- (OCP)开放-封闭原则The Open-Closed Principe
- (LSP)Liskov替换原则The Liskov Substitution Principe
- (ISP)接口聚合原则The Interface Segregation Principe
- (DIP)依赖转置原则The Dependency Inversion Principe
SOLID原则!剩下的将主要围绕这五个原则分别说明:
A. 单一责任原则(SRP):
原则内容很简单,总结就是做好自己的事,不要插手别人的事。
什么意思?假设一个软件有一段代码,如果有维护软件的需求需要改动这段代码,那么也就可以理解为这段代码对这次改动负有责任。形象点说,如果软件如果出现了问题,通过改动这段代码修复了,那么这段代码就是要对这个问题负责。所以代码变化的根本原因是责任,代码负责的功能出现了问题才会变化。
所以为了减少代码变化,最好让一个类有一个责任而不是多个,也就是说做好自己的事。
反过来说如果一个类承担了多个责任,那么会引入额外的包,占据资源。同时会导致频繁的重新配置部署等。
2. 开放-封闭原则(OCP)
开放:指的是对拓展性开放,模块的行为应该是可拓展的,从而该模块可表现出新的行为以满足需求的变化。
封闭:指的是对修改封闭,模块自身的代码是不应被修改的,拓展模块的行为的一般途径是通过修改模块内部实现实现的。如果一个模块不能被修改,那么它通常被认为是具有固定的行为。
其核心思想就是抽象技术。
但运用的时候也会有一些情况,我们来看一下的栗子:
问题:
修改后:
3. Liskov替换原则(LSP)
其原则主要是:子类型必须能够替换其基类型。而且派生类必须能够通过其基类的接口使用,客户端无需了解二者之间的差异。
这个原则已经在第五章中的复用性结构中详细说明了,不了解的可以看这里。
4. 接口聚合原则(ISP)
其原则内容是:客户端不应依赖于他们不需要的方法。也就是说要吝啬你的接口方法,除去无用的。
如果接口过于庞大可以分解为多个小接口,不同的接口向不同的客户端提供服务,客户端只需要访问自己所需要的接口。
5.依赖转置原则(DIP)
其原则内容是:抽象的模块不应依赖于具体的模块,而是具体的模块应该依赖于抽象。
觉得课程中的栗子不是很恰当,所以就不截图了,这篇博客讲解的很详细,感兴趣的大家可以看一下。
5. OO设计原则GRASP
General Responsibility Assignment Software Patterns(Principes)通用责任分配软件模式
其是关于如何为“类”和“对象”指派“职责”的一系列原则。
对象的职责与对象的义务是有关联的。
大栗子:
责任是使用方法来实现的,makepayment代表sale对象有责任创建payment对象。
GRASP通常由以下几部分组成:
- 控制器(Controller)
- 信息专家(Information expert)
- 创建者(Creators)
- 低耦合(Low coupling)
- 高内聚(High cohension)
- 间接(Indirection)
- 多态(polymorphism)
- 受保护的变体(Protected variations)
- 纯制造(Pure fabrication)