阮 航,陈 恒,彭 鑫+,赵文耘
1.复旦大学 软件学院,上海 201203 2.复旦大学 上海数据科学重点实验室,上海 201203
面向设计的开源软件项目重构经验研究*
阮 航1,2,陈 恒1,2,彭 鑫1,2+,赵文耘1,2
1.复旦大学 软件学院,上海 201203 2.复旦大学 上海数据科学重点实验室,上海 201203
技术债;软件设计;重构;开源项目
在现实世界中,软件不是一成不变的。随着时间的推移,软件一直在不断演化。演化的原因可能包括增加新需求,修改原有功能,删除无用功能,修复软件漏洞或者改善软件性能等。代码量在软件演化过程中变得愈来愈庞大,代码结构也愈来愈复杂,偏离了最初的设计方向,软件的质量和可理解性也会降低。此时如果不及时对软件进行重构,会给后续开发带来许多困难和阻碍。
重构是在不改变软件外部行为的条件下,对软件内部结构的一种调整,提高软件的可理解性,降低其修改成本[1]。重构可以改善软件的设计,提高代码的可理解性,帮助开发人员定位bug位置,使程序员能够更加快速地开发软件[1]。在实际软件开发过程中,重构逐渐占有越来越重要的地位,特别是在敏捷开发和极限编程实践里。极限编程团队通过周期性的重构来扭转软件退化,并循环持续进行重构[2]。
项目开发之初会对高层架构进行设计,后续开发中根据设计的架构完成项目的编码工作。一份好的设计可以让开发者轻松地完成工作,相反一份不好的设计会使开发者在面对同样的工作时付出更多的劳动。在实际开发过程中,即使有良好的设计,但为了尽早交付软件产品,开发人员往往会牺牲代码质量,破坏原有的设计。这就是技术债,长远的债务需要在维护阶段花费更多的人力去补偿。
重构是否真的可以改善代码质量,重构在实际软件开发过程中的地位如何。针对这两个问题,本文选择两个开源项目进行经验研究,主要有两个关注点:(1)实际项目中重构出现的频率和类型,其中面向设计的重构在所有重构中是否是重要的一部分;(2)不好的设计是否真的对软件开发造成了困难和阻碍,这种情况在开源项目中是否真实存在,如果对其进行合理的重构,是否有利于后续的开发。
本文组织结构如下:第2章简要概述本文的相关工作;第3章通过一个使用策略模式进行重构的例子证明重构的重要性,继而介绍重构的相关概念;第4章描述研究的问题,介绍选取的研究对象以及选取原因,并说明数据收集方法;第5章根据收集到的数据回答研究问题并得出结论;第6章对研究中的发现和不足之处进行讨论;第7章总结全文。
下面从重构和技术债两部分介绍相关工作。重构部分介绍目前学术界对重构的相关研究;技术债部分描述了设计相关的技术债的研究工作,说明了不良设计问题对于软件开发和维护的阻碍。
众多学者对重构的时机和分类已经进行了研究。Fowler[1]的工作总结了22种代码坏味道帮助定位重构机会,总结了一系列重构类型,以及指导怎样合理进行重构。Gamma等人[3]分类整理了共23种设计模式,帮助开发者设计出更加灵活的、模块化的、可复用的和易理解的软件。Kerievsky[4]在前两者的基础上把重构与模式结合起来进行考虑,描述了模式导向的重构。
Mens等人[5]对目前为止软件重构技术进行了总结,对每个重构阶段的相关研究都进行了介绍,并分析了目前研究出的技术和工具。Mens等人[6]指出未来重构面临的问题是基本原理问题和实践问题。
代码坏味道会阻碍软件演化,开发历史中对代码坏味道的修改明显多于其他部分[7-8]。Munro[9]提出了一种通过使用度量值的集合自动监测代码坏味道的方法。
Mäntylä[10]发现处于领导级别的工程师更关注于较高层次的代码坏味道,而且在项目中经验丰富的人员能发现更多的代码坏味道;人工进行的代码坏味道甄别和现有的度量产生了冲突。Kataoka等人[11]设计了一款通过程序不变量来挑选出代码坏味道并进行重构的工具。
现有研究面向的重构操作大都是简单的原子性操作,当需要完成比较高级的复合型操作时,仍旧需要人工进行辅助。
技术债由Cunningham首次提出,指的是开发团队在设计或架构选型时从短期效应的角度选择了一个易于实现的方案,但从长远来看,这种方案会带来更消极的影响,亦即开发团队所欠的债务[12]。
在技术债相关信息不明确的时候,Guo等人[13]对技术债的问题严重性,技术债是如何影响软件项目和技术债影响软件项目的哪些方面进行了探索。Schmid[14]提出应该用一系列近似值来模拟各个方面,然后慎重决定技术债。Tom等人[15]对技术债提出了一个完整的理论框架体系。
Ernst等人[16]通过对现实生活中1 800多位工程师和架构师的调查和访问,分析技术债的相关问题。结论显示技术债是一个有意义且容易理解的抽象比喻,工程师们一致同意设计结构是造成软件技术债的主要原因。如何处理和管理技术债目前仍然存在诸多困难,但他们认为从原始设计追踪有助于解决这一问题。
本章通过一个计算顾客购物清单应支付金额的案例,介绍重构相关的基本知识,展示结构设计对于软件演化的影响,以及利用重构进行结构调整对于软件后续开发的益处。
案例的大致结构如图1所示,图中展示了一个在商场中付款时,用来计算顾客购买货物应支付金额的类Payment。
Fig.1 Payment class structure图1 Payment类结构
用户分为3种:(1)有等级并可以使用积分的用户;(2)有等级但不可以使用积分的用户;(3)无等级的用户。有等级的用户可以享受对应的等级打折优惠,并且可以使用积分来抵消掉一部分的消费金额。
这个计算支付金额的案例存在设计缺陷。设计最大的缺点是当对原有功能进行扩展时会遇到很多阻碍,比如需要根据顾客所选择的支付方式提供一些不同的优惠政策,这些优惠有着不同的计算方式。如果采取图1所示的设计方法,需要修改大量的分支代码。这样的设计会令后续的功能扩展更加困难,当出现复杂的逻辑判断时,往往需要进行重构来减少程序复杂度。
面对图1中所展示的结构,适用于使用策略模式进行结构上的改进和优化。应用策略模式令客户端的代码与实际的算法分离,便于对原有算法进行修改以及增加新算法等。在应用策略模式后,调整后的设计结构如图2所示。
此设计的优点在于可以使计算模块和客户端分开,当需要增加新的计算方式时,可以直接在服务端进行修改,客户端可以很简单地通过调用新增的策略来实现,对客户端的修改量很小。例如需要根据顾客所选择的支付方式提供一些不同的优惠政策时,所要进行的修改只是去增加继承自GetBillStrategy抽象类的一种新的计算方式策略类。重构可以帮助开发者改善代码结构,更加有益于新功能的添加和后续的维护工作。面对同样增加新功能的需求,优化后代码所需要进行的修改量远远少于未进行优化的代码。
接下来,对研究中出现的重构进行介绍。
Fig.2 Payment class structure after using strategy mode图2 应用策略模式后的Payment类结构
如前文曾经介绍过的,现有重构的命名都是基于文献[1]进行的,本文按照重构是否涉及软件设计和难易程度将重构分为3类:不涉及设计的重构、涉及设计的简单重构和基于设计模式的重构。
这里所说的是否涉及到设计,采用如下判定方式:所应用的重构方式是否改变了原有的类结构,包括类在包中的位置、类与类之间的依赖关系等,这样的变化可以通过类图直观地展现。
提炼函数,替换算法,分解/合并条件式,重新命名函数/重新命名属性等重构方法是在实际研究过程中观察到的不涉及软件设计的重构。不涉及设计的重构的操作比较单一,多数是一些简化和移动操作。
搬移函数,提炼类,用多态替换条件式,函数上移/下移,提炼父类等重构方式都涉及到对设计的优化与改进,其中一些实现是由比较简单的重构所构成。
设计模式是经过分类的反复使用的代码设计经验的总结,可以方便进行后续的代码复用。设计模式的应用会伴随着新的辅助类的添加或者是对现有类的结构调整,是一种比较高级的重构方式。
本章对研究的整体过程进行介绍:首先说明研究所关注的两个问题;其次介绍选取的研究对象,并解释选取研究对象的原因;最后介绍数据收集的方法。
本研究的目的在于发现重构在现实项目中是否有广泛的应用,研究面向软件设计的重构在所有重构中的重要性,以及分析重构是否会改善软件设计。在对开源项目的研究中,主要关注以下两个研究问题。
问题1重构在现实项目中是否有很多应用,其中是否出现了面向设计的重构。
研究问题1的主要目的是研究在开源项目中开发人员对重构的重视程度,并且在其中发现开发人员对面向软件设计的重构的关注度。在版本历史中,研究开发人员是否发现重构机会然后进行重构,可以了解重构在开源项目中是否被认可和应用,其中面向软件设计的重构的比例可以显示出这类重构是否是重要的一部分。
问题2在现实项目中,是否存在由于未进行重构改善不良设计,导致后续开发过程中遇到了不必要的障碍的情况,以及后续是否意识到并进行重构。
研究问题2的主要目的是研究面向设计的重构的影响,关注点在于开源项目中是否真实存在这样面向设计的重构。当存在这样的重构机会时,观察在后续没有进行重构的这一段开发过程中,开发人员是否因为这样的不良设计遇到了不必要的困难。如果在开源项目中找到了面向设计的重构,通过研究重构前的开发阶段,考虑面向设计的重构是否可以解决这些阻碍。
本文选取jEdit和Jabref两个开源项目作为研究对象。选择这两个开源项目的原因有以下两点:(1)这两个开源项目使用Java语言编写,Java语言是一种面向对象的高级编程语言,出现重构机会的可能性更高;(2)项目规模较小,更利于理解代码结构和内部逻辑。
jEdit是一款比较成熟的开源程序员文本编辑器,由Slava Pestov在1998年进行主要内核的开发,目前内核大致稳定没有继续修改,但仍有很多开发者在制作和更新相应插件。
Jabref是一个用Java语言编写的支持多平台的文献推送和管理工具,2005年开始开发,到目前为止仍在持续开发和更新。
在对这两个开源项目的研究中,分别选择了3个版本间的所有提交作为研究对象。jEdit选择了jedit-4-3-pre16至jedit-4-3-pre17和jedit-4-3-pre17至jedit-4-3-pre18两个阶段;Jabref选择了v2.11至v2.11.1和v2.11.1至v3.0两个阶段。在选择了研究目标后,对数据进行了预处理,最后采用人工阅读的方式收集所需数据。
由于开源项目的提交次数多,修改的文件之间的关系复杂,人工阅读所有提交历史并统计数据是不现实的。针对这个问题,对这两个项目的各个阶段设计了对应的数据库表结构来储存相关信息,方便查询和统计。在数据库上进行的数据统计需要基于人工阅读的经验。
人工阅读开源项目的提交历史是本文经验研究的主要方法,在人工阅读过程中主要关注两个研究问题。
问题1主要面向的对象是开发项目过程中开发人员实际做出的重构操作,并要关注其中涉及到软件设计的部分。对于这个研究问题,在人工阅读过程中需要注意观察提交日志带有refactoring关键字的提交。同时需要通过阅读源码发现未标明的重构,结合提交信息和具体的修改内容人工判断每次提交的意图,了解是否应用了重构操作,以及是否存在面向设计的重构。
问题2主要研究的是开发的某段过程中是否存在不好的设计,使后续开发遇到了不必要的困难。这个问题采用两种思路进行研究:第一种思路是在阅读前对项目的各个模块进行大致的理解,了解程序的运行流程和互相之间的调用处理关系,在这个基础上寻找不好的设计,并追踪这个设计对于后续开发的不良影响。第二种思路是先寻找到开发人员已经进行过的面向设计的重构,在这次重构的提交基础上回溯检查此次提交前的提交,从而发现没有进行改善设计的重构前,开发过程中是否遇到了阻碍。
本章对研究结果进行分析。数据来源于两个开源项目的4个开发阶段,在后文中表示如下:jEdit的jedit-4-3-pre16至jedit-4-3-pre17阶段表示为pre1617,jedit-4-3-pre17至jedit-4-3-pre18阶段表示为pre1718;Jabref的v2.11至v2.11.1阶段表示为v11to111,v2.11.1至v3.0阶段表示为v111to3。
所有提交在数据库统计中发现,有一部分不包含java文件,修改的是用于记录的log文档、配置文件等,可以通过文件类型进行排除。在排除了无关的提交后,pre1617、pre1718、v11to111和v111to3的提交次数为230次、134次、58次和278次。
在这些提交中,搜索“refactoring”和“restructure”关键词发现包含重构的提交。jEdit项目在pre1617中出现了1次标明重构的提交,Jabref在v111to3中出现了7次标明重构的提交(排除了对测试用例重构的提交)。但在后续的人工阅读阶段发现,重构操作远不止搜索到的8次提交,这一情况说明现在开发人员对于重构操作的认识不够,但是开发人员实际上进行了一部分自身没有意识到是重构的操作。例如:一个搬移getBoolean方法的重构被开发人员描述为“New method StandardUtilities.getBoolean()that will convert an object into a Boolean”,只提到了新方法的添加,没有体现该方法移动到StandardUtilities类中。两个项目中标明为重构的共8次提交中有2次是提炼函数重构,其余6次是重命名重构,这些都是简单的重构类型,并且这些重构大多只涉及一个类的修改。说明开发人员对于部分简单重构是有一定的理解的,或者说开发人员更认可同一个类中进行的重构。
jEdit的开发时间要远远早于Jabref,所研究的jEdit开发阶段的时间在2008年11月至2009年11月,Jabref所研究的开发阶段在2015年11月,这证明了重构的重要性正一步步被认可,提交日志中注明“refactoring”或“restructure”关键字的提交逐渐变多。
pre1617、pre1718、v11to111和v111to3所包含的重构次数分别为31次、8次、19次和43次。在jEdit中平均有10.71%的提交是关于重构的,在Jabref中平均有18.45%的提交是关于重构的,这些数据说明了重构在开发中的重要性,开发人员进行了不少没有意识到的重构。
开发人员进行过的重构主要有8种类型,分别是搬移函数、提炼类、方法上移、改变参数、搬移值域、重命名函数或属性、替换算法、提炼方法和自封装值域。所列举的前5种重构会涉及到软件设计的调整,因为这些重构会更改类之间的调用关系或层次结构。
各个种类的重构操作在pre1617、pre1718、v11to111和v111to3中的比例如图3所示,明显看到重命名和搬移函数重构操作在各个研究阶段分布较多。重命名和搬移函数重构的经常出现表明,在开发过程中对类的职责划分不清晰。重命名重构以下的部分表示面向软件设计的重构操作,面向软件设计的重构在每个阶段所占比例大约在50%左右,pre1718因为包含了较少的重构操作(8次),所以被排除在外。从这个比例可以看出,面向设计的重构操作在所有重构中占有较高比例。
Fig.3 Refactoring classification ratio图3 重构分类比例
研究中观察到的面向设计的重构操作包含搬移函数、提炼类、方法上移、改变参数和搬移值域。这些重构有一个共同点:在重构中涉及到的修改的类大多只有两个。这说明目前观察到的涉及软件设计的重构是比较简单的重构,只是两个类之间的结构的分配,没有比较复杂的涉及多个类结构的重构出现。
重构在实际项目中有着重要的应用,项目中对其关注度也越来越高。但是目前开发人员对于重构的认知度偏低,对实际上是重构操作的提交很少进行注明。开发人员对于重命名等简单重构已经有普遍认知,基本能够主动标识。面向软件设计的重构在重构中出现频率较高,主要是在两个类之间进行相关的函数和属性搬移,调整类的层次结构等简单重构,但没有应用设计模式的复杂重构出现。
由于未及时重构,不合理的设计对后续开发造成了阻碍的情况是存在的,但是其中的大部分没有被合理地重构或者处理方式不合适。下面通过两个最典型的例子进行分析研究,并展示合理的重构对后续开发的积极影响。其中5.2.1小节中的例子被开发人员识别出重构机会并进行了重构,5.2.2小节中的例子没有进行重构。
Jabref的图形化界面模块中存在一个包gui,专门用来保存与界面相关的对象,其中一部分对象用来表示对话框。有一个对话框类DuplicateResolverDialog实现了用于保存位置和大小信息的方法savePosition。
此处出现了设计缺陷,保存对话框大小和位置信息的方法属于通用的设置方法,很有可能被同样是对话框的其他类复用。并且在DuplicateResolver-Dialog的init初始化方法中有一段代码的意图是设置对话框的大小和位置信息,这样独立的操作没有提炼成方法。
在后续的开发中,id为313bf3b的提交中出现了新的需求:为MergeEntryDialog类和MergeEntry-DOIDialog类添加保存对话框大小和位置的功能。按照之前较差的设计,开发人员实现的方式就是在MergeEntryDialog类和MergeEntryDOIDialog类中增加savePosition方法。实现与DuplicateResolverDialog类相同,同时初始化中设置初始大小和位置的功能也同样实现,结构如图4所示。按照这样的设计,后续的向其他对话框类中添加相同方法时需要不断地增加新方法,修改初始化方法,会产生大量的冗余代码。此外一旦需要对这个方法的逻辑进行修改,就不得不对每个实现该方法的对话框类进行修改,增加了维护和修改的成本。
Fig.4 Irrelevant dialog structure图4 无关联的对话框结构
id为c47fe58的提交中发现了这个问题,对于结构的调整是提取savePosition方法为GUIGlobals的静态方法,并通过重命名函数和增加参数的重构修改为storeDialogSize方法。与此同时,设置大小和位置的代码块提取出来成为GUIGlobals的静态方法set-DialogSize,结构如图5所示。在id为c582705的提交中对这一部分又进行了重构,提炼单独的Position-Window类来维护这两个静态方法用以调用。
Fig.5 Dialog structure after refactoring in project图5 项目中重构后对话框结构
这样的设计改进是可以消除冗余代码和减少后期维护难度的,但不是最好的面向对象的解决办法。提取方法作为静态方法的处理方式破坏了面向对象的理念,很难利用多态处理相关问题。在研究中发现有十几次类似的通过将方法提取到通用类中改为静态方法的方式处理相似问题,这样的处理方式并没有对设计进行修改,开发人员考虑到修改后的测试成本和软件的正确性问题等多方面原因,选择最简单的增加静态功能的方式进行处理,没有从根本上改善设计问题。
设置和存储对话框大小位置信息的功能在逻辑上与对话框类联系紧密,应该作为对话框类的方法实现。一个较好的改进方案是为需要实现该功能的对话框实现统一的父类,其中storeDialogSize和set-DialogSize方法,实现该功能的类继承自这个父类获取该方法的实现,重构后结构如图6。这样不仅可以消除冗余代码,而且当特别的子类需要有特别的逻辑修改时还可以通过覆写的方式修改,改善了代码的可维护性和可扩展性。
Fig.6 Dialog structure after reasonably refactoring图6 合理重构后对话框结构
Jabref在搜索部分需要选择不同的搜索模式而使用不同的策略进行搜索,设计的方式是使用一个枚举类型的SearchMode保存相关内容,在SearchBar通过对应的RadioButton来进行切换选择策略。Jabref没有进行很好的设计,导致有大量switch和if…else…语句出现在方法中,主要表现在initSearchModeMenu、getSearchModeMenuItem、getSearchMode、focus和 on-SearchTextChanged这5个方法中。
在id为a1b231e的提交中,为了更简单地进行搜索模式的选择,搜索模式需要从原有的6种减少到3种。面对这样的新需求,SearchBar类需要在上述5个方法中做大量的修改,如图7和图8所示为get-SearchModeMenuItem方法修改部分。其他的方法也都包含关于这几种模式的选择判定的switch或if…else…语句,同样需要进行大量分支的删除,重复出现的判断对后续修改造成的困难展露无遗。
Fig.7 Function getSearchModeMenuItem before changing图7 修改前getSearchModeMenuItem方法
这个设计问题可以通过应用状态模式解决。后文对简化后3种模式的实现进行优化分析,设计结构调整如图9。
Fig.9 SearchBar structure after refactoring图9 重构后SearchBar结构
在没有应用设计模式的重构前,实现对搜索模式的增删修改时,需要对SearchBar类中涉及到模式类型判断的方法进行大量修改,同时需要修改用于维护信息的SearchMode类。如果能及时应用本文所调整的结构,增删模式的操作变得极为简单。当需要变更模式类型时,只需要增加SearchMode对应的模式实现,并修改SearchBar的初始化方法实现对应按钮的添加,再在Globals存储的模式数组中添加名称字符串,就可以完成模式的增删,不需要在项目中修改大量的方法,同时可以减少大量判断语句的使用,提高代码性能,代码结构也更加清晰易懂。
Fig.8 Function getSearchModeMenuItem after changing图8 修改后getSearchModeMenuItem方法
本章介绍在研究过程中产生的思考以及研究中存在的不足。
不良设计是导致代码质量下降的重要原因之一,阻碍开发人员对软件的开发和维护。及时利用重构改善不良设计是理想的处理方法,可以降低不良设计对后续开发造成的危害。研究中发现不良设计的出现与原因可以总结为3种情况:
(1)在软件设计阶段产生的不良设计。软件架构师不可能设计出绝对正确的软件设计,这种不良设计的出现是无可避免的。
(2)由于技术债产生的不良设计。由于人力、时间和资源等因素的限制,开发人员无法在要求的短期时间内交付可展示的工作成果。为了及时交付工作成果,开发人员往往选择舍弃部分设计以在短期内完成工作,也就是软件工程领域的技术债问题。面向设计的技术债属于开发人员不得不因为现实因素做出让步的一种决策。
(3)在开发过程中产生的不良设计。软件开发是一个漫长的过程,期间会出现无数次的功能增加与变更。功能的增加与变更会改变原有的类结构,这会暴露出目前设计的问题。这种问题在设计之初可能不是一个不良设计,在后续的修改过程中才变成了不良设计。
考虑5.2.1小节的例子,最初只有DuplicateResolver-Dialog这一个对话框类实现了保存对话框大小的功能,仅在该类中维护相关方法是合理的。如果在设计之初设计成继承父类方法的方式,会使其他对话框类出现不必要的方法,父类在整个结构中显得多余。当增加MergeEntryDialog类和MergeEntry-DOIDialog类后,才发现原来设计的不足之处,需要消除冗余代码。
了解不良设计的出现阶段和原因能够帮助对不良设计进行追踪和记录,有利于为后续维护和修改提供有益信息。
(1)和(2)中产生的不良设计可以通过审查和记录的方式发现问题所在,但(3)中产生的不良设计需要在开发中定期进行重构才可以解决。
经过研究,开源项目历史中已经存在重构相关的提交,面向设计的重构在所有发现的重构中占有接近一半的数量,是重构中重要的一部分。面向设计的重构可以提高可理解度,也能够减少冗余的代码,降低代码的维护难度。如果在开发过程中自觉地进行重构,可以为以后的开发降低难度,提高效率。
但重构仍然面临一些问题:首先是对重构的认知,目前开发人员对于重构普遍只处于听说过的阶段,并不了解重构的具体内容,这使重构难以系统地进行;其次是重构的效益,重构后代码的优点更多体现在后续的开发过程中,这种未来效益很难得到重视;面向设计一类的复杂重构不仅修改一个类,常常涉及到几个类的变动,其间难免会出现漏洞,这样的修改成本和测试成本是开发人员不愿意承担的,因此开发人员更偏向于改动单一的类来完成需求,不会自觉地进行较为复杂的重构。
开发人员对重构的不理解,管理人员对重构的轻视和重构成本过高,是无法进行重构的主要原因。通过自动判定应当应用重构的位置和自动应用重构后,重构的价值可以更好地体现,降低重构难度和成本,重构才能真正应用到每个开发过程中。
研究可能会因为以下几个因素导致研究结果不准确,主要包括对面向设计的重构的识别,研究对象的选择和开发阶段的识别3个因素。
对于现有的重构操作,本文按照重构是否改变了软件设计将其分为3类,主要判断依据是重构是否改变了软件类之间的依赖关系,使用类图展示可以清晰地辨别出这种变化,但是有一部分重构无法准确判断是否改变了设计。
以添加参数的重构为例。添加参数的重构在研究中被定义为面向设计的重构操作:第一种情况也是大多数情况,是在A类的方法中增加了B类的参数,这样就更改了类与类之间的依赖关系,这样的操作认为是面向设计的重构。第二种情况是,在A类中添加了A类自身属性作为参数,这也属于添加参数的重构,但不改变类之间的关系,这一情况在研究中出现得不多,因此仍然认为添加参数是面向设计的重构。在研究中遇到第二种情况时,并不统计为添加参数的重构,仅仅统计入重构总次数。
研究中选取了两个项目,jEdit开发时间比Jabref更早。这里可能出现的问题是研究选取的对象太少,在两个开源项目间的统计和比较可能不具有普遍性,但是在研究项目中发现的实例确实可以证明不良设计对于软件开发的不良影响,而且验证了重构对于改善设计的重要作用。
本文在每个开源项目的两个开发阶段通过统计和阅读源码的方式进行研究,开发阶段的选取是随机的。这样选取开发阶段可能会存在问题,jEdit是保持内核基本不变的开源项目,选取的阶段可能设计已经基本定型,暴露出的问题可能偏少,而处于不断开发过程的Jabref项目中暴露设计问题的可能性更高。同时随机化挑选也有一定优势,随机化挑选开发阶段可以排除由于挑选开发活动频繁的阶段造成的结论特殊性。
软件在现实世界中不是一成不变的,随着时间的推移一直在不断进行演化。为了解决各种原因造成的代码设计问题,需要在开发过程中不断进行重构改善代码质量。
在对现有重构类型和应用场景已经有一定理解的基础上,本文提出了两个研究问题进行探索。研究中选取两个开源项目作为研究对象,在每个研究对象上随机选取两个版本间的开发历史作为观察,通过数据预处理后人工阅读的方式收集开源项目中的实际例证和数据进行问题研究。
研究证明重构是开发过程中重要的一部分,在研究的两个开源项目的提交历史中分别占有10.71%和18.45%的比例。虽然重构在项目中应用很多,但是开发人员在应用重构的过程中往往没有意识到所做的操作是重构,没有在提交日志中说明,提交日志中指明修改是重构的提交只占实际重构中极小的一部分。提交日志中指明本次修改是重构的提交中,主要的重构类型是重命名,这说明开发人员对于重构操作的类型和定义的了解只处于最初级的阶段。面向设计的重构在所有重构中占据将近1/2的数量,是重构的一个重要方面。开发人员在开发中识别出并进行重构的面向设计重构只涉及两个类之间的变动,操作过于简单。
研究过程中发现了这样的情况,由于未进行合理设计,导致进行功能扩展的过程中遇到了不必要的障碍,极少有开发人员意识到是设计问题并进行重构调整。但是所进行的重构并没有在根本上解决设计问题,有时反而会导致其他设计问题的出现。研究通过提出合理的解决办法,将合理重构方式与开发人员实际重构进行对比,体现出应用合理重构对于提高代码质量的重要性,很大程度上改善了代码的可维护性。软件设计在开发过程中具有指导性意义,在演化过程中软件设计的退化问题和技术债的管理问题往往需要应用重构去解决。
未来的研究需要找到合适的评价软件设计质量的度量,从而指导在何时何处应用何种重构改善代码设计,没有指导的盲目重构是没有意义的。目前没有足够的工具帮助开发人员管理面向设计的技术债问题,在未来对这些方面需要进一步的研究。
[1]Flower M.Refactoring:improving the design of existing code[M].Upper Saddle River,USA:Addison-Wesley Professional,1999:53-89.
[2]Martin R C.Agile software development:principles,patterns,and practices[M].Upper Saddle River,USA:Prentice Hall,2002:9-16.
[3]Gamma E,Helm R,Johnson R,et al.Design patterns:elements of reusable object-oriented software[M].Upper Saddle River,USA:Addison-Wesley Longman Publishing Co Inc,1995:241-276.
[4]Kerievsky J.Refactoring to patterns[M].Upper Saddle River,USA:Addison-Wesley,2002:232.
[5]Mens T,Tourwe T.A survey of software refactoring[J].IEEE Transactions on Software Engineering,2004,30(2):126-139.
[6]Mens T,Demeyer S,Bois B D,et al.Refactoring:current research and future trends[J].Electronic Notes in Theoretical Computer Science,2010,82(3):483-499.
[7]Khomh F,Penta M D.An exploratory study of the impact of code smells on software change-proneness[C]//Proceedings of the 16th Working Conference on Reverse Engineering,Lille,France,Oct 13-16,2009.Washington:IEEE Computer Society,2009:75-84.
[8]Olbrich S,Cruzes D S,Basili V,et al.The evolution and impact of code smells:a case study of two open source systems[J].Biology of Reproduction,2009,68(5):390-400.
[9]Munro M J.Product metrics for automatic identification of“bad smell”design problems in Java source-code[C]//Proceedings of the 11th International Software Metrics Symposium,Como,Italy,Sep 19-22,2005.Washington:IEEE Computer Society,2005:15.
[10]Mäntylä M V,Vanhanen J,Lassenius C.Bad smells — humans as code critics[C]//Proceedings of the 20th International Conference on Software Maintenance,Chicago Illinois,USA,Sep 11-17,2004.Washington:IEEE Computer Society,2004:399-408.
[11]Kataoka Y,Notkin D,Ernst M D,et al.Automated support for program refactoring using invariants[C]//Proceedings of the 2001 International Conference on Software Maintenance,Italy,Nov 6-10,2001.Washington:IEEE Computer Society,2001:736-743.
[12]Cunningham W.The WyCash portfolio management system[J].ACM SIGPLAN OOPS Messenger,1992,4(2):29-30.
[13]Guo Yuepu,Seaman C,Gomes R,et al.Tracking technical debt—an exploratory case study[C]//Proceedings of the 27th International Conference on Software Maintenance,Williamsburg,USA,Sep 25-30,2011.Washington:IEEE Computer Society,2011:528-531.
[14]Schmid K.A formal approach to technical debt decision making[C]//Proceedings of the 9th International ACM Sigsoft Conference on Quality of Software Architectures,Vancouver,Canada,Jun 17-21,2013.New York:ACM,2013:153-162.
[15]Tom E,Aurum A,Vidgen R.An exploration of technical debt[J].Journal of Systems&Software,2013,86(6):1498-1516.
[16]Ernst N A,Bellomo S,Ozkaya I,et al.Measure it?manage it?ignore it?software practitioners and technical debt[C]//Proceedings of the 10th Joint Meeting on Foundations of Software Engineering,Bergamo,Italy,Aug 30-Sep 4,2015.New York:ACM,2015:50-60.
RUAN Hang was born in 1993.He is an M.S.candidate at Software Engineering Laboratory,Fudan University.His research interest is software maintenance and evolution.
阮航(1993—),男,辽宁凌源人,复旦大学软件工程实验室硕士研究生,主要研究领域为软件维护与演化。
CHEN Heng was born in 1992.He is an M.S.candidate at Software Engineering Laboratory,Fudan University.His research interest is software maintenance and evolution.
陈恒(1992—),男,江苏泰州人,复旦大学软件工程实验室硕士研究生,主要研究领域为软件维护与演化。
彭鑫(1979—),男,湖北黄冈人,2006年于复旦大学计算机科学专业获得博士学位,现为复旦大学教授,CCF高级会员,主要研究领域为需求工程,自适应软件,软件产品线,软件维护,逆向工程。
ZHAO Wenyun was born in 1964.He received the M.S.degree from Fudan University in 1989.Now he is a professor at Fudan University,and the senior member of CCF.His research interests include software reuse,software product line and software engineering.
赵文耘(1964—),男,江苏常熟人,1989年于复旦大学获得硕士学位,现为复旦大学教授,CCF高级会员,主要研究领域为软件复用,软件产品线,软件工程。
Empirical Study of Design-Oriented Refactorings in Open Source Projects*
RUAN Hang1,2,CHEN Heng1,2,PENG Xin1,2+,ZHAO Wenyun1,2
1.Software School,Fudan University,Shanghai 201203,China 2.Shanghai Key Laboratory of Data Science,Fudan University,Shanghai 201203,China
Software is constantly changing and evolving,driven by the increment of requirements.Software often degrades from the original design,the quality of codes also becomes poor.Developers will meet more barriers and difficulties in the future development when there are some technical debts in the original design.It is necessary to improve the quality of codes by refactorings.This paper introduces some common types of refactorings under the review of related literature,and introduces a simple case to verify the importance of the design-oriented refactorings.This paper aims to answer two questions:(1)whether there are applied refactorings in the open source project and whether there are design-oriented refactorings among them;(2)whether there exists some situation that the structure with a bad design causes difficulties in the fellow-up development and whether the developers apply some refactorings to it later.This paper proves the wide use and importance of refactorings in open source projects,particularly the importance of the design-oriented refactorings.
technical debt;software design;refactoring;open source project
the Ph.D.degree in computer science from Fudan University in 2006.Now he is a professor at Fudan University,and the senior member of CCF.His research interests include requirements engineering,self-adaptive software systems,software product line,software maintenance and reverse engineering.
2016-09, Accepted 2016-11.
A
TP311
+Corresponding author:E-mail:pengxin@fudan.edu.cn
RUAN Hang,CHEN Heng,PENG Xin,et al.Empirical study of design-oriented refactorings in open source projects.Journal of Frontiers of Computer Science and Technology,2017,11(9):1418-1428.
10.3778/j.issn.1673-9418.1609025
*The National Natural Science Foundation of China under Grant No.61370079(国家自然科学基金);the National Key Research and Development Program of China under Grant No.2016YFB1000801(国家重点研发计划).
CNKI网络优先出版: 2016-11-07, http://www.cnki.net/kcms/detail/11.5602.TP.20161107.1703.008.html
摘 要:软件在演化过程中经常被修改,软件结构往往会偏离原有的设计方向,软件质量也会逐渐变差。不良设计造成的技术债务在后续开发过程中会带来许多困难和阻碍,需要及时重构,改善原有代码的不良设计。对常见的重构操作进行了简单介绍和分类。在两个开源项目上进行了经验研究,关注两个问题:(1)重构在开源项目中是否被广泛应用,其中是否存在面向设计的重构;(2)是否存在没有及时重构改善原有代码的不良设计,导致后续开发遇到不必要的困难的情况,并且后续是否进行了重构。初步证明了重构在开源项目中的广泛应用和重要性,以及面向设计的重构的重要作用。