架构思维之复用

2021-04-22 09:16:44 2639

复用是架构思维中非常重要的思维之一,是面向对象架构设计的核心思想,业务能力组件化,组件能力服务化,共享平台,中台建设,公共服务下沉等都是为了提高系统的复用而设计。

好的系统设计具备可扩展性(Extensibility)、灵活性(Flexibility)和可插入性(Pluggability),一个复用较好的系统,就是一个易维护的系统。但实际上,可维护性和可复用性是两个纬度。

一、可维护性

可维护性指系统的被修改能力和可修复能力,修改能力指改进、系统环境、需求、系统容量等变化的适应;可修复能力指系统发生故障后,能够排除(或抑制)故障予以修复,并能按照原有设计流程正常运行状态的可能性。可维护性架构有如下特征:

(1)易分析性

系统发生故障或缺陷时,能很快定位或分析出原因。

(2)易改变性

面对新需求系统拥有快速被实现的能力,实现体现在代码实现、设计实现和说明文档的更新。

(3)易测试性

系统新的变更可以被验证的能力。

(4)稳定性

兼容新的变更不影响原有系统的运行的能力。

(5)依从性

架构设计遵循和保持相关约定的能力。

对于系统可维护性的度量可以从以下几个方面进行判断:

a.可理解性。指通过分析源码和解读相关设计文档来了解系统架构、功能和运行逻辑的难易程度。一个可理解性高的系统一般应具备以下的特征:模块化(系统各个模块结构良好、功能完整),程序代码清晰,编程风格具有一致性(代码风格及设计风格的一致性),完整性(对输人数据进行完整性检查),使用有意义的数据名和函数名等。

b.可靠性。可靠性表明一个系统在给定的一段时间内正确执行的概率。衡量可靠性的方法主要有两类:第一类是根据程序错误的统计数字来进行可靠性预测。比如用一些可靠性模型,根据程序测试中发现并排除的错误数来预测平均失效间隔时间(Mean Time To Failure,MTTF)。第二类是当系统的可靠性与复杂性有关时,可以根据程序的复杂性来预测软件的可靠性。

c.可测试性。可测试性表明能够用测试的方法来验证程序正确性的难易程度。系统的可测试性取决于系统的可理解性、复杂性、设计合理的测试用例的难易程度等方面的内容。

d.可修改性。可修改性描述了程序能够被正确修改的难易程度。一个可修改的程序应当是可理解的、通用的、简单的、灵活的。通用性是指程序适用于各种功能变化而无需修改。灵活性是指能够容易地对程序进行修改。

e.可移植性。可移植性表明程序从一个运行环境移植到另一个新的运行环境的可能性的大小。一个可移植性好的系统应具有结构良好、灵活、不依赖于某一具体计算机或操作系统的特性。

系统的可维护性不但和架构师采用的分析设计方法和技术熟练程度有关,还和项目管理技术有密切的联系,除了和设计开发方法有关的因素之外,还有一些因素影响系统的可维护性的原因:

f.开发人员是否有统一的约束规范

g.从需求——业务分析——架构设计——编码实现——测试发布——线上运营是否采用了统一的文档结构和文档形成机制。

h.是否应用了可维护性的语音和框架

i.是否有完整的代码说明文档

j.是否有持续更新保存规范化的测试用例信息

二、可复用性

复用性是面向对象技术最重要的特征之一,但是事与愿违复用性的好处,我们在架构设计时并不能真正实现,因为复用需要付出一定的代价,并不像我们使用面向对象开发工具一样,很容易利用起复用性特性完成相关工作。相反,为了打到系统的复用性,我们需要取舍一些设计。另外好多设计者对于复用的范围很有局限,常常会考虑代码的复用而忽略了其他更高层次的复用,当然代码的复用是最基本的,代码的复用有自身的好处,但是为了让我们的系统复用,希望设计者能从更高的层次来看待复用。

1。复用性的好处

有较高的生产效率。

有较高的系统质量。

恰当运用复用可以改善系统的可维护性。

2。复用分类

我们可以将系统复用分为代码复用、算法的复用、数据结构的复用、测试信息的复用、设计的复用、分析的复用,接下来我们就详细讲解如何做这些复用。

(1)代码复用

代码复用是最常见的复用,包括目标代码和源代码的复用,指的是在同一个应用的多个模块中,或者是在多个应用下代码的复用。理想状况下,代码复用可以共享通用类、函数集合来实现;即便是在最差的情况下,代码复用也可以通过拷贝和修改源代码来实现。

代码复用的一个关键因素是你要能够获取到代码。如果必要的话,你自己可以修改这段代码,当然也可以找别人帮你修改。这一点好坏并存,通过审读代码,你可以自己决定—— 哪怕这个决定很难做出—— 你是否要复用这段代码。同时,把代码开放给你,那么代码原作者也许会失去撰写说明文档的动力,这也就增加了你理解它所花费的时间,减小了你可能获得的收益。

代码复用的最大好处在于它可以减少你的代码量,也就潜在地减小了开放和维护成本。坏处则在于你自己应用的能力范围就被约束住了,而且也增加了应用和被复用代码之间的耦合。要大规模地实现源程序的复用,只有依靠含有大量可复用构件的构件库,如“对象链接与嵌入”技术,既支持在源程序级上定义构件以构造新的系统,又使这些构件在目标代码级上仍然是一些独立的可复用构件,能够在运行时被灵活地重新组合为各种应用系统。

代码复用是最基础最基本的复用,那我们就来看看做到代码复用应该注意哪些内容:

a.面向接口编程

"面向接口编程"是面向对象设计(OOD)的第一个基本原则。面向接口编程就是先把客户的业务逻辑线提取出来,作为接口,业务具体实现通过该接口的实现类来完成。当客户需求变化时,只需编写该业务逻辑的新的实现类,通过更改配置文件(例如Spring框架)中该接口的实现类就可以完成需求,不需要改写现有代码,减少对系统的影响。

b.使用对象组合而不是继承

"优先使用组合而不是继承"是面向对象设计(OOD)的第二个基本原则。继承是在程序开发的过程中重构得到的,而不是程序设计之初就使用继承,很多开发者滥用继承,结果可能造成后期的代码解决不了需求的变化。因此,优先使用组合而不是继承,是面向对象开发的一个重要经验。

继承:继承的起源,来自于多个类中相同特征和行为的抽象。子类可以通过继承父类,那么可以调用父类中定义的方法和属性,从而达到代码重用的目的。另外,子类除了重用父类的代码以外,还可以扩展自身的属性和方法,来描述子类特有的特征和行为。

对象组合:对象组合要求被组合的对象具有良好的接口,并且通过从其他对象得到的引用在运行时运态定义。对象组合是类继承之外的另一种复用选择,可以将对象组合到其他对象中,以构建更加复杂的功能。由于对象的内部细节对其他对象不可见,它们看上去为“黑箱”,这种类型的复用称为黑箱复用(black-box reuse)。对象只以"黑箱"的形式出现。

下面分析继承和组合的优缺点:

(a)继承的优缺点

优点: 

继承简单粗爆,直观,关系在编译时静态定义。

被复用的实现易于修改,sub可以覆盖super的实现。

缺点:

无法在运行时变更从super继承来的实现(也不一定是缺点)

sub的部分实现通常定义在super中。

sub直接面对super的实现细节,因此破坏了封装。

super实现的任何变更都会强制子类也进行变更,因为它们的实现联系在了一起。

如果在新的问题场景下继承来的实现已过时或不适用,所以必须重写super或继承来的实现。

由于在类继承中,实现的依存关系,对子类进行复用可能会有问题。有一个解决办法是,只从协议或抽象基类继承(子类型化),国为它们只对很少的实现,而协议则没有实现。

(b)组合的优缺点优点:

不会破坏封装,因为只通过接口来访问对象;

减少实现的依存关系,因为实面是通过接口来定义的;

可以在运行时将任意对象替换为其他同类型的对象;

可以保持类的封装以专注于单一任务;

类和他的层次结构能保持简洁,不至于过度膨胀而无法管理;

缺点:

涉及对象多;

系统的行为将依赖于不同对象间的关系,而不是定义于单个类中;

现成的组件总是不太够用,从而导致我们要不停的定义新对象。

c.将可变的部分和不可变的部分分离

"将可变的部分和不可变的部分分离"是面向对象设计(OOD)的第三个基本原则。如果使用继承的复用技术,我们可以在抽象基类中定义好不可变的部分,而由其子类去具体实现可变的部分,不可变的部分不需要重复定义,而且便于维护。如果使用对象组合的复用技术,我们可以定义好不可变的部分,而可变的部分可以由不同的组件实现,根据需要,在运行时动态配置。这样,我们就有更多的时间关注可变的部分。对于对象组合技术而言,每个组件只完成相对较小的功能,相互之间耦合比较松散,复用率较高,通过组合,就能获得新的功能。

d.控制方法的长度

通常,我们的方法应该只有尽量少的几行,太长的方法会难以理解,而且,如果方法太长,则应该重新设计。对此,可以总结为以下原则:

三十秒原则:如果另一个程序员无法在三十秒之内了解你的函数做了什么(What),如何做(How)以及为什么要这样做(Why),那就说明你的代码是难以维护的,必须得到提高; 

一屏原则:如果一个函数的代码长度超过一个屏幕,那么或许这个函数太长了,应该拆分成更小的子函数;一行代码尽量简短,并且保证一行代码只做一件事。

e.消除case / if语句

要尽量避免在代码中出现判断语句,使用过多的switch/case 或者 if else 语句,代码的可读性很差同时也违背了面向对象的原则。

f.减少参数个数

有大量参数需要传递的方法,通常很难阅读。我们可以将所有参数封装到一个对象中来完成对象的传递,这也有利于错误跟踪。太多层的对象包装对系统效率有影响,但是它带来的好处相比,我们宁愿做包装。毕竟"封装"也是OO的基本特性之一,而且,"每个对象完成尽量少(而且简单)的功能",也是OO的一个基本原则。

g.类层次的最高层应该是抽象类。

在许多情况下,提供一个抽象基类有利做特性化扩展。由于在抽象基类中,大部分的功能和行为已经定义好,使我们更容易理解接口设计者的意图是什么。由于JAVA不允许"多继承",从一个抽象基类继承,就无法再从其它基类继承了。所以,提供一个抽象接口(interface)是个好主意,一个类可以实现多个接口,从而模拟实现了"多继承",为类的设计提供了更大的灵活性。

h.尽量减少对变量的直接访问

对数据的封装原则应该规范化,不要把一个类的属性暴露给其它类,而是应该通过访问方法去保护他们,这有利于避免产生波纹效应。如果某个属性的名字改变,你只需要修改它的访问方法,而不是修改所有相关的代码。

i.子类应该特性化,完成特殊功能

如果一个子类只是使一个组件变成组件管理器,而不是实现接口功能,或者,重载某个功能,那么,就应该使用一个外部的容器类,而不是创建一个子类

j.拆分过大的类

如果一个类有太多的方法(超过50个),那么它可能要做的工作太多,我们应该试着将它的功能拆分到不同的类中。

k.作用截然不同的对象应该拆分

对同样的数据,有不同的视图。某些属性描述的是数据结构怎样生成,而某些属性描述的是数据结构本身。最好将这两个视图拆分到不同的类中,从类名上就可以区分出不同视图的作用。类的域、方法也应该有同样的考虑!

为了提高代码的复用性,还有好多的手段,以上都是多年的编码经验,希望能帮助大家。

(2)算法的复用

各种算法如排序算法都已经得到了大量的研究,几乎不需要我们重写自己的算法,各种语言通常也实现了这些常用算法,因此直接复用即可。

(3)数据结构的复用

与算法一样,类似数组、队列、栈、列表等得到了透彻的研究,只需要直接复用。

(4)测试信息的复用

测试信息的复用主要包括测试用例的复用和测试过程的复用。前者是把一个软件的测试用例应用于新的软件测试中,或者在软件作出修改时使用在新一轮的测试中。后者是在测试过程中通过软件工具自动记录测试的过程信息,包括测试员的每一个操作、输人参数、测试用例及运行环境等信息,并将这些过程信息应用于新的软件测试或新一轮的软件测试中。测试信息的复用级别不易同分析、设计、编程的复用级别进行准确地比较,因为被复用的不是同一事物的不同抽象层次,而是另一种信息,但从这些信息的形态来看,大体处于与程序代码相当的级别。

a.测试用例设计方法

测试用例设计可以分为白盒测试用例设计法和黑盒测试用i设计法。

白盒测试用设计法


1.webp.jpg


黑盒测试用例设计法


2.webp.jpg


(5)设计复用

设计复用指的是使用对以前创建的设计用例、标准文档、领域模型、过程指导和其它,来帮助你开始一个新工程。设计复用分为几个层次,从完整的拿来即用这种100%的完全复用,到仅以工件作模型之用,研究分析之以获取灵感。举例来说,编码和用户界面设计的标准文档对不同的项目来说,就是有价值的设计,像其中的建模符号定义和方法论总览这种文档,是可以直接复用的。我曾经为已有的通用数据接口以面向对象的方式做了包装,让这些类使用起来更直观。

设计复用提升了项目间的一致性,减少了各个项目的管理成本。用户界面标准对绝大多数平台来说都是很常用的;编码标准对主要语言来说都是不可或缺的;而标准面向对象方法论和模型记号这样的东西已经使用好多年了。主要的坏处在于很多核心程序员总觉得这种复用过了头,给彼此带来了强加的标准和过程约束。因此设计复用的底线就是当你觉得它是一种重要、可行和醒目的技术时,再去使用它。这种复用有如下三种途径。

从现有系统的设计结果中提取一些可复用的设计组件,并将这些组件应用于新系统的设计中。

将一个现有系统的全部设计文档在新的系统上重新实现,也就是将一个设计运用于多个具体的实现中。

和任何应用无关,独立设计开发可复用的设计组件。

(6)分析复用

分析复用是比设计复用更高级别的复用,可复用的分析组件是针对业务领域中某些设计或问题抽象出的组件,受设计技术及实现条件的影响很少,所以可复用的机会更大,如领域模型已经显出巨大的可复用潜能,因为它们反映出大规模的业务行为内聚的特征,在许多应用中都是一致的。你在领域开发中创造的每样东西都是可以复用的。与后期的复用相比,领域组件在前期业务行为和组织的架构设计中显出更大的作用。复用的途径也有三种。

从已有系统的分析结果中提取可复用的组件用于新系统的架构设计。

用一份完整的分析文档作为输入,产生针对不同系统和其他实现条件的多项设计。

和任何应用无关,独立设计开发可复用的分析组件。

三、可复用和可维护性的关系

可复用和可维护性的关系如下。

1.适当地应用复用,同时提高了可维护性,就是在保持甚至提高系统的可维护性的同时,实现系统复用。

2.适当提高系统的可复用性,同时提高了系统的可扩展性。系统的可扩展性由“开-闭”原则、里氏代换原则、依赖倒转原则和组合/聚合复用原则保证。

3.适当提高系统的可复用性,同时提高了系统的灵活性。系统的灵活性由“开-闭”原则、迪米特法则、接口隔离原则保证。

4.适当提高系统的可复用性,同时提高了系统的可插入性。系统的可插入性由“开-闭”原则、里氏代换原则、组合/聚合复用原则和依赖倒转原则保证。

复用的成功之路

那么我们如何做到正真的面向对象设计的复用呢?上面所讲的内容都是工具,如果你认真的看完了,你已经拥有了复用的工具,复用是否成功就要看我们如何利用工具帮助我们在系统的生命周期中来实现。不要为了复用而设计系统,那么你会很累。给你提几个意见:

a.多次验证并被应用

你可以尝试可复用的设计,但是直到你的设计被复用多次,你才可以谈成功的设计。可复用性是旁观者来下结论的,而不是设计者自己。

b.可复用性必须有完备的文档

文档必须标识出,什么时候不要复用它,这样开发者才能理解合适场景的上下文。

c.复用是一种态度

当你设计新的系统架构的时候,第一件事应该是决定你的架构还会以怎样的方式在别的场合被复用。也许有些人已经设计了你需要的东西。另一方面,你得主动分享你的工作成果,这样大家才可以复用它。一个好领队会在团队中持续地寻找复用点和提升、奖励复用的机会。一个不错的方式就是在从这样两个方面去寻找可复用的机会:模型评审过程中,寻找继承和模式复用的机会;代码走读过程中,寻找组件和代码复用的机会。



提交成功!非常感谢您的反馈,我们会继续努力做到更好!

这条文档是否有帮助解决问题?

非常抱歉未能帮助到您。为了给您提供更好的服务,我们很需要您进一步的反馈信息:

在文档使用中是否遇到以下问题: