在所有关于软件维护的故事中,功能的扩展是一个永恒的话题。正因为软件系统需要功能的扩展,需要新功能的加入,才使我们的编程需要那么多的设计。可以说,正是因为新功能的扩展,使得原有的系统质量下降;正是因为软件质量的下降,才使我们需要进行深入的分析与研究,制订设计原则,总结设计模式;正是因为要解决软件质量下降的问题,经过一番艰苦卓绝的摸索过程,我们才认识到系统重构才是解决该问题的最佳方案。
然而,事情总是这样的,每个系统当我们进行初次的设计时,设计思路、程序结构总是比较完美的。可是当初次设计结束后,我们在日后的维护中,开始往系统里添加新功能时,系统开始不完美了,甚至开始出现问题了,新增的功能总是或多或少有些水土不服。怎么办呢?要保证每次需求的变更时软件质量不会下降,必须记住这样一个原则:先重构再添加新功能。
添加新功能前先重构原有系统,其目的有两个:
1. 软件的设计总是与软件的复杂程度有关的,原有的设计是在原有需求不复杂的条件下做出的,但随着新功能的加入,软件复杂度在发生着变化,因此必须要调整原有的设计以适应新的需求;
2. 为了提高软件的可维护性与易变更性,添加新功能应遵循OCP原则。而要遵循OCP原则,我们应当在不添加新功能的前提下先进行重构,设计出可扩展点出来,然后再添加新功能。
是的,明白这两点非常重要。软件维护越来越困难,不是因为客户提出了需求变更,而是因为我们没有随着软件复杂度的增加改变我们的软件结构。软件需求起初比较简单,是几乎所有软件的共性。但随着软件复杂度的增加,我们却不敢有效地调整现有的软件结构,以适应新的需求,这就真正是我们的问题了。不敢调整,是害怕原有功能会出错,但不调整,则意味着我们软件设计的问题会越来越大,进而越来越难于维护。改与不改,我们面临着两难的抉择。
解决这个两难的难题其实不难,实际上就是一层窗户纸一桶就破了。试想,我们不敢修改原有代码的真正原因是因为害怕改出问题影响原有功能的正常运行。那么,我们找一个方法使我们在修改原有代码时不会出现问题,换句话说是出现问题以后会及时发现,则问题就可以解决,这个方法就是重构与测试。
客户判断一个功能是否正常运行的标准,就是当输入一个值后,能得到客户期望的结果,不管系统内部是怎样运行的。因此,建立这样一个测试用例,让软件系统在重构前后都能通过这些测试用例,就可以保证重构的正确性(关于如何建立,我们还会在后面仔细讨论)。重构以后,外部功能是一致的,但内部程序结构却变得更加易于添加新的功能,使新的功能与原有系统可以有机地融为一体,这才是我们的目的。说起来比较抽象,我们来举一个示例吧:
在许多系统中,只要有报表出现就有需求要实现Excel数据导出功能。在一个系统中,客户起初提出的需求是实现“全部导出”、“按选择导出”、“导出本页”。为此,我们设计了一个单选框,并在后台程序中编写了一个if语句,如果选择的是“全部导出”,则查询所有记录并导出;如果选择的是“按选择导出”,则从前端获得一个主键列表,即用户已选择的行,以此作为条件查询导出;如果选择的是“导出本页”,则查询本页数据并导出。
String exportTypeName = (String)params.get("exportType");
if("exportAll".equals(exportTypeName)) {
//全部导出的代码
} else if("exportChoosen".equals(exportTypeName)) {
//按选择导出的代码
} else if("exportOnePage".equals(exportTypeName)) {
//导出本页的代码
}
这样的设计没有问题,也是大多数人首先想到的设计。但是,多个不同的选择放在一个类中必将为功能的扩展带来麻烦。随后,客户提出了新的需求,按页导出,即根据客户的要求,从第几页到第几页进行导出。按照前面的设计,我们必然是在原有基础上再增加一个if语句,实现按页导出。
String exportTypeName = (String)params.get("exportType");
if("exportAll".equals(exportTypeName)) {
//全部导出的代码
} else if("exportChoosen".equals(exportTypeName)) {
//按选择导出的代码
} else if("exportOnePage".equals(exportTypeName)) {
//导出本页的代码
} else if("exportPageRange".equals(exportTypeName)) {
//按页导出的代码
}
但这样的设计违反了OCP原则,也是大多数系统代码质量下降的重要原因之一。在一些文章中,if语句被称为“罪恶之源”,因为大量使用if语句将会大大降低系统的可读性、可维护性与易变更性,使系统难于维护。因为不断添加的if语句很快会使代码由数百行膨胀到几千行,还会大量掺杂各种重复代码与糟糕设计。其根本原因就在于,它让一个类承载了过多的职责,降低了功能内聚而提高了功能耦合。它不仅加大了我们修改代码的难度,也将加大我们测试代码的成本,因为任何一项修改都必须要对所有功能进行测试。
因此,我们需要调整我们的代码结构,改变我们的设计(到这里也许你开始理解我所说的改变代码结构以适应新的需求的含义了吧)。我们说扩展新功能的设计应当符合OCP原则。怎样的设计才是符合OCP原则的呢?首先可以想到的是,让“按页导出”这个功能的代码放到另一个类中,而不写在原有类中。比如,我们可以创建一个新类ExportPageRange,通过接口Exporter接入到原类ExportBus,让ExportBus调用其相应的方法:
但是,这样的设计我们依然需要修改原类ExportBus,在if语句中调用接口:
String exportTypeName = (String)params.get("exportType");
If ("exportAll".equals(exportTypeName)) {
//全部导出的代码
} else if ("exportChoosen".equals(exportTypeName)) {
//按选择导出的代码
} else if ("exportOnePage".equals(exportTypeName)) {
//导出本页的代码
} else if ("exportPageRange".equals(exportTypeName)) {
//按页导出的代码
Exporter exporter = new ExportPageRange();
exporter.doExport(resultset);
return exporter.getFileInfo();
}
加粗部分是我们不得不在原类中添加的代码。如果不使用这个if语句而让Exporter接口的实现类与判断条件建立一种联系,则问题可以得到解决。要实现这种联系有很多方法,其中一个方法就是建立配置文件,让配置文件中的名称与实现类关联起来就可以了,为此我们需要这样设计:
然后进行这样的配置:
<bean id="exportBus" class="com...reporter.bus.impl.ExportBusImpl">
<description>导出数据BUS</description>
<property name="exportTypes">
<map>
<entry key="exportAll"><!-- 全部导出 -->
<bean class="com...reporter.export.ExportAll"/>
</entry>
<entry key="exportOnePage"><!-- 导出本页 -->
<bean class="com...reporter.export.ExportOnePage"/>
</entry>
<entry key="exportChosen"><!-- 按选择导出 -->
<bean class="com...reporter.export.ExportChosen"/>
</entry>
<entry key="exportPageRange"><!-- 按页导出 -->
<bean class="com...reporter.export.ExportPageRange"/>
</entry>
</map>
</property>
</bean>
这样,配置文件中的entrykey就与导出程序的实现类建立了联系,因此在ExportBus中原来的那个if语句就演变成了这样:
String exportTypeName = (String)params.get("exportType");
Exporter exporter = exportTypes.get(exportTypeName);
exporter.doExport(resultset);
return exporter.getFileInfo();
加粗的部分实质性替代了原来那个if语句。这样的设计,让各个不同类型的导出程序得到有效解耦,然后通过接口与配置文件实现动态地装配。这就是一种典型的可扩展点设计,当我们还有新的导出类型的功能需要扩展的时候,不需要修改原有的任何代码,而只需添加一个Exporter接口新的实现类,再进行相应的配置,功能就可以实现。这样的设计是可以满足OCP原则的,而在系统中实现这种可扩展性设计的功能点,我们就称之为“可扩展点”。
以上的设计是我们最终应当实现的设计,是结果。但要达到这样的设计,即分析整个设计的过程,我们真的没有修改原程序吗?不,我们修改了。那么这怎么叫符合OCP原则呢?问题十分犀利哈。转了那么大一圈,现在才是我要真正提出我的观点的时候了。我认为,要改善遗留系统的可维护性,要遵守OCP原则,并不是意味着实现新需求时不能修改原有代码。要遵从“两顶帽子”的设计原则,先重构原有的代码,使其具有可扩展功能,然后再添加新程序,使其满足OCP原则,这才是可扩展设计的关键之所在。
具体来说,就是第一步修改代码,第二步添加功能。第一步,修改原有代码,在保证原有功能不变的前提下,设计出可扩展点,使其在以后添加新功能时不必修改原有代码。在本例中就是将原有的三种导出方式从原有代码中抽取出来,形成Exporter接口与ExportAll、ExportOnePage、ExportChosen三个实现类,以及它们的配置文件。这样,Exporter接口就是数据导出方式的可扩展点。这时,由于没有添加任何新功能,我们可以编写测试代码进行测试,或者手工测试。
第二步,就是添加新功能。由于可扩展点已经做出来了,剩下的工作其实就很简单了:编写实现类ExportPageRange,然后配置到系统中,整个设计符合OCP原则。功能扩展就应当这样做,才能使我们的软件在维护中始终能保持高质量的代码。
(续)
相关文档
遗留系统:IT攻城狮永远的痛
需求变更是罪恶之源吗?
系统重构是个什么玩意儿
我们应当改变我们的设计习惯
小步快跑是这样玩的(上)
小步快跑是这样玩的(下)
代码复用应该这样做(1)
代码复用应该这样做(2)
代码复用应该这样做(3)
做好代码复用不简单
软件可以这样功能扩展
过程扩展与放置钩子
特别说明:希望网友们在转载本文时,应当注明作者或出处,以示对作者的尊重,谢谢!
- 大小: 6.3 KB
- 大小: 12.5 KB
分享到:
相关推荐
这是一套arduino 9合一扩展板的使用说明,文档内将扩展板常用功能简单的描述了引脚,并用实例代码做了演示
最新单片机仿真 独立式键盘的按键功能扩展:以一当四最新单片机仿真 独立式键盘的按键功能扩展:以一当四最新单片机仿真 独立式键盘的按键功能扩展:以一当四最新单片机仿真 独立式键盘的按键功能扩展:以一当四最新...
无线网卡 上网拨号软件 扩展短信功能
你是否对目前所使用的软件系统的功能还不满足...以下介绍的这57款工具软件可以充分扩展或者改善您正在使用的Windows、Office、浏览器、E-mail客户端管理软件和媒体播放器等其他软件功能,让这些系统软件功能更加强大。
IMAGINE开发者工具包™ 包括一套“C / C + +语言的API,可为有经验的C / C + +的”程序员使用的。用于修改IMAGINE的商业版本,或者开发全新的应用程序,扩展软件的功能。
计算机网络设计软件系统的可扩展性主要指软件规模的可扩展性和软件功能的可扩展性,相对于计算机网络设计软件系统规模的可扩展性来说, 人们更加注重软件功能的可扩展性。根据计算机网络设计软件系统的内容,其可...
基于微服务架构扩展软件功能的方法最终版.pdf
电子技术应用交流,原厂器件资料,电子设计软件,电子技 术电器的功能扩展
本实验为多功能数字钟的设计与仿真调试。...此数字钟电路由振荡器、分频器、校时电路、显示译码电路和功能扩展电路组成。本是实验采用Proteus软件进行仿真。仿真结果显示此电路能完成上述要求,可实现上述一系列功能。
#资源达人分享计划#
文章在国内外关于井下风网解算软件的研究基础上,提出了通风计算软件网络扩展模式,从当前需求、软件结构、软件功能几个方面阐述了网络扩展模式的可行性,并重点针对扩展应用范围和远程监控系统联机两个方面实现了扩展...
openwrt插件扩展大全ipk3100个对应功能中文 解释
子程序 取消行列色, , 公开, 方圆软件工作室-林炳崇(近在眼前)出品 .子程序 置背景交替色, , 公开, 方圆软件工作室-林炳崇(近在眼前)出品 .参数 窗口, 窗口, , 超级列表框所在窗口名称 .参数 超级列表框, ...
A900GOT操作手册 扩展功能篇.pdf )
三维CAD软件中二维工程图功能的扩展.pdf
基于RTC的即时通信软件的安全认证及功能扩展,徐亮,李宁,当前即时通信软件的开发主要是从协议的底层来进行研究,主要利用的是几大开源协议栈,但是要想在协议栈之上做功能方面的扩展就是
可设置六键鼠标各键功能,可更改为连续点击各按键,可设置延时,扩展鼠标各键功能,实现鼠标利用最大化,文件小巧方便存取,工作中提高效率,无广告,不收费。
本文来自于网络,网站的可扩展性架构设计,能够在对现有系统影响最小的情况下,系统功能可以可持续扩展及提升的能力。在此,对容易混为一谈的“扩展性”和“伸缩性”的概念进行详细说明:扩展性表现为:基础设施不...
根据TI提供的CCS文档整理而成,包括了软件的常见使用问题及扩展脚本等功能
意法半导体STM32扩展软件简化物联网终端安全功能部署