Vincent的备忘录

  博客园  :: 首页  ::  :: 联系 :: 订阅 订阅  :: 管理

1         重构(Refactoring)

重用是再工程的灵魂,再工程可以在不同层次重用一次工程的资源。重用那些完善而具有一致性文档、可读性很高的可维护性程序,无人不趋之若鹜。但对那些天书般的文档,打满了补丁的程序,还要重用吗?——这也是再工程方法学的使命之一,它必须提出对那些坏文档坏程序的重用解决方案。重构(Refactoring)就是解决这些“坏东西“的武器。

6.1       重构的概念

重构(Refactoring)就是在不改变软件现有功能的基础上,通过调整程序代码改善软件的质量、性能,使其程序的设计模式和架构更趋合理,提高软件的扩展性和维护性

6.2       为什么要重构

   

在不改变系统功能的情况下,改变系统的实现方式。为什么要这么做?投入精力不用来满足客户关心的需求,而是仅仅改变了软件的实现方式,这是否是在浪费客户的投资呢?

通过重构可以达到以下目标:

·         持续偏纠和改进软件设计

重构和设计是相辅相成的,它和设计彼此互补。有了重构,你仍然必须做预先的设计,但是不必是最优的设计,只需要一个合理的解决方案就够了,如果没有重构、程序设计会逐渐腐败变质,愈来愈像断线的风筝,脱缰的野马无法控制。重构其实就是整理代码,让所有带着发散倾向的代码回归本位。

·         使代码更易为人所理解

Martin Flower在《重构》中有一句经典的话:"任何一个傻瓜都能写出计算机可以理解的程序,只有写出人类容易理解的程序才是优秀的程序员。"对此,笔者感触很深,有些程序员总是能够快速编写出可运行的代码,但代码中晦涩的命名使人晕眩得需要紧握坐椅扶手,试想一个新兵到来接手这样的代码他会不会想当逃兵呢?

·         帮助发现隐藏的代码缺陷

孔子说过:温故而知新。重构代码时逼迫你加深理解原先所写的代码。笔者常有写下程序后,却发生对自己的程序逻辑不甚理解的情景,曾为此惊悚过,后来发现这种症状居然是许多程序员常患的"感冒"。当你也发生这样的情形时,通过重构代码可以加深对原设计的理解,发现其中的问题和隐患,构建出更好的代码。

·         从长远来看,有助于提高编程效率

当你发现解决一个问题变得异常复杂时,往往不是问题本身造成的,而是你用错了方法,拙劣的设计往往导致臃肿的编码。改善设计、提高可读性、减少缺陷都是为了稳住阵脚。良好的设计是成功的一半,停下来通过重构改进设计,或许会在当前减缓速度,但它带来的后发优势却是不可低估的。

6.3       如何重构

下面将列举一些常见的情况,并说明如何针对这些情况进行重构。

1.  尽量消除重复的代码,将它们合而为一

根据重复的代码出现在不同的地方,分别采取不同的重构的策略:

·         在同一个Class的不同地方:通过采用重构工具提供的Extract Method功能提炼出重复的代码, 然后在这些地方调用上述提炼出方法。

·         在不同Subclasses中:通过Extract Method提炼出重复的代码,然后通过Pull Up Method将该方法移动到上级的Super class内。

·         在没有关系的Classes中:通过对其中一个使用Extract Class将重复的代码提炼到一个新类中,然后在另一个Class中调用生成的新类,消除重复的代码。

2.  拆解过长的函数

过长的函数在我们的日常代码中经常可见,在C#中常通过#region#endregion区隔为不同的功能区域。

重构策略:通过Extract Method将过长的函数按照功能的不同进行适当拆解为小的函数,并且给这些小函数一个好名字。通过名字来了解函数提供的功能,提高代码的理解性。

3.  拆解过大的类

过大的类也经常见到,特别是类中含有大量的成员变量。

重构策略:通过Extract Class将一些相关成员变量移植到新的Class中,如Employee类,一般会包含有联系方式的相关属性(电话, Mobile,地址,Zip等等),则可以将这些移植到新的EmployeeContact类中。

4.  过长的参数列

过长的参数列的主要问题是难以理解,并且难以维护。如果要增加新的参数或者删除某一参数,易造成参数前后不一致。

重构策略:如果可以通过向已存在的对象查询获取参数,则可通过Replace Parameter with Method,移除参数列,通过在函数内部向上述已存在的对象查询来获取参数。如果参数列中若干参数是已存在对象的属性,则可通过Preserve Whole Object将这些参赛替换为一个完整对象,这样不仅提高代码的可读性,同时已易于代码今后的维护。另外,还可以将若干不相关的参数,使用Introduce Parameter Object来创建一个新的参数类。不过,我个人觉得如果这些情况过多的话,会产生很多莫名其妙的参数类了,反而降低代码的可读性。

5.  发散式变化

现象:当某个Class因为外部条件的变化或者客户提出新的功能要求等时,每次修改要求我们更新Class中不同的方法。不过这种情况只有在事后才能觉察到,因为修改都是在事后发生的。

重构策略:将每次因同一条件变化,而需要同时修改的若干方法通过Extract Class将它们提炼到一个新Class中。实现目标是:每次变化需要修改的方法都在单一的Class中,并且这个新的Class内所有的方法都应该与这个变化相关。

6.  “霰弹式“修改

现象:当外部条件发生变化时,每次需要修改多个Class来适应这些变化,影响到很多地方。就像霰弹一样,发散到多个地方。

重构策略:使用Move MethodMove FieldClass中需要修改的方法及成员变量移植到同一个Class中。如果没有合适的Class,则创建一个新Class。实现目标是,将需要修改的地方集中到一个Class中进行处理。

7.  特性依赖

现象:Class中某些方法“身在曹营心在汉”,没有安心使用Class中的成员变量,而需要大量访问另外Class中的成员变量。这样就违反了对象技术的基本定义:将数据和操作行为(方法)包装在一起。

重构策略:使用Move Method将这些方法移动到对应的Class中,以化解其“相思之苦”,让其牵手。

8.  数据泥团

现象:指一些相同数据项目(Data Items),如Class成员变量和方法中参数列表等,在多个Class中多次出现,并且这些数据项目有其内在的联系。

重构策略:通过使用Introduce Parameter Object(创建新的参数对象取代这些参数)或Preserve Whole Object(使用已存在的对象取代这些参数),实现使用对象代替Class成员变量和方法中参数列表,清除数据泥团,使代码简洁,也提高维护性和易读性。

9.  基本类型偏执

现象:在Class中看到大量的基本型数据项目(Data Item),如Employee类中有大量的数据成员,Employee#, FirstName, MiddleName, LastName, Address, State, City, Street, Zip, OfficePhone, CellPhone, Email……等等。

重构策略:使用Extract Class(提炼新类)或Preserve Whole Object(使用已存在的对象取代这些参数),实现使用对象代替基本型数据项目(Data Item)。如上述Employee类中就可分别提炼出EmployeeNameEmployeeContact两个新类。

10.平行继承

现象:为某个class增加一个subclass时,也必须为另一个class相应增加一个subclass

重构策略: 在一个class继承体系的对象中引用(refer to)另一个class继承体系的对象,然后运用Move MethodMove Field将被引用class中的一些方法和成员变量迁移宿主class中,消除被引用class的继承体系。

11.冗余类

现象:某一些class由于种种原因,现在已经不再承担足够责任,有些多余了

重构策略:通过Collapse Hierarchy,将这些冗余的class合并到superclasssubclass中,或者通过Inline Class(与Extract Class相反),将这些冗余class中的所有Method/Field迁移到其他相关的class中。

12.无用的抽象

现象:系统中出现一些无用的abstract class,或者非必要的delegation(委托),或者多余的参数等等。

重构策略:分别使用Collapse Hierarchy合并abstract class,使用Inline Class移除非必要的delegation,使用Remove Parameter删除多余的参数。

13.临时值域

现象:class中存在一些Field,这些Field只在某种非常特定的情况下需要。

重构策略:通过Extract Class将这些孤独的Field及其相关的Method移植的一些新的Class中。提炼出来的新Class可能没有任何抽象意义,只是提供Method的调用,这些新Class一般称为Method Object

14.消息链

现象:向一个对象请求另一个对象,然后再向后者请求另一个对象,……,这就是Message Chain,意味着Message Chain中任何改变,将导致Client端不得不修改。

重构策略:通过Hide Delegate(隐藏委托关系)消除Message Chain,具体做法是在Message Chain的任何地方通过Extract Method建立一个简单委托(Delegation)函数,来减少耦合(Coupling)。

15.中间人

现象:过度运用delegation,某个/某些Class接口有一半的函数都委托给其他class,这样就是过度delegation

重构策略:运用Remove Middle Man,移除简单的委托动作(也就是移除委托函数),让client直接调用delegate受托对象。和上面的Hide Delegate(隐藏委托关系)刚好相反的过程。由于系统在不断的变化和调整,因此[合适的隐藏程度]这个尺度也在相应的变化,Hide DelegateRemove Middle Man重构策略可以系统适应这种变化。另外,可保留一部分委托关系(delegation),同时也让Client也直接使用delegate受托对象。

16.不合适的类关系

现象:两个Class过分亲密,彼此总是希望了解对方的private成分。

重构策略:可以采用Move MethodMove Field来帮助他们划清界限,减少他们之间亲密行为。或者运用Change Bidirectional Association to Unidirectional,将双向关联改为单向,降低Class之间过多的依存性(inter-dependencies)。或者通过Extract Class将两个Class之间的共同点移植到一个新的Class中。

17.“双胞胎类“

现象:两个函数做相同的事情,却有不同的signature

重构策略:使用Rename Method,根据他们的用途来重命名。另外,可以适当运用Move Method迁移某些行为,使Classes的接口保持一致。

18.不完整的类库

现象:Library Class(类库)设计不是很完美,我们需要添加额外的方法。

重构策略:如果可以修改Library ClassSource Code,直接修改最好。如果无法直接修改Library Class,并且只想修改Library Class内的一两个函数,可以采用Introduce Foreign Method策略:在Client Class中建立一个函数,以外加函数的方式来实现一项新功能(一般而言,以server class实例作为该函数的第一个参数)。如果需要建立大量的额外函数,可应该采用Introduce Local Extension:建立一个新class,使它包含额外函数,并且这个class或者继承或者wrap(包装)source class

19.纯数据类

现象:Data Class指:一些Class拥有Fields,以及用来访问Fieldsgetter/setter函数,但是没有其他的功能函数

重构策略:找出其他class中访问Data Class中的getter/setter的函数,尝试以Move Method将这些函数移植到Data Class中,实现将数据和操作行为(方法)包装在一起,也让Data Class承担一定的责任(方法)。

20.拒绝继承

现象:Subclass不想或不需要继承superclass的部分函数和Field

重构策略:为subclass新建一个兄弟(sibling class),再运用Push Down MethodPush Down Fieldsuperclass中的相应函数和Field下推到兄弟class,这样superclass就只包含subclass共享的东西了。其实,也就是将superclass中一些与特定的函数和Field放到特定的subclass中,superclass中仅包含subclass共享的函数和Field。如果不想修改superclass,还可以运用Replace Inheritance with Delegation来达到目的。也就是以委托取代继承,在subclass中新建一个Field来保存superclass对象,去除subclasssuperclass的继承关系,委托或调用superclass的方法来完成目的。

21.过多的注释

现象:当代码中出现一段长长的注释,一般是由于代码比较糟糕,需要进行重构,除去代码的坏味道。

重构策略:通过上面提及的各种重构策略,将代码的坏味道去除,使注释变成多余。如果需要注释/解释一段代码做了什么,则可以试试Extract Method,提取出一个独立的函数,让函数名称解释该函数的用途/功能。另外,如果觉得需要注释来说明系统的某些假设条件,也可尝试使用Introduce Assertion(引入断言),来明确标明这些假设。

6.4       重构的工具

    现在许多常用的开发环境都提供了代码重构功能的支持,比如Visual Studio2005, Eclipse等。同时还有一些第三方的IDE插件可以提供重构功能,比如Resharper

7         再工程遇到的阻力

    大概没有人愿意回过头去翻弄过去的陈糠烂谷,更莫说是他人的程序。向前走和向后看是难以兼顾的目标。买了新汽车,旧的就扔掉,这是硬件世界的逻辑,也顺从人们求新喜新的天性。软件世界的哲学偏偏逆道而驰,“继承过去”是它的立身之本,身后的历史越长,前面的路越宽——这不是硬要难为软件开发者吗?

    再工程是软件工程的一部分。从生存期概念来说,它属于维护期。它的作用期远大于第一次软件工程期,一般最小也应考虑在510倍以上。例如初次(一次)开发工程周期在2个月~1年的话,那使用周期至少应在110年。这也是由软件价值高昂、投资回收需要相当周期的一般市场规则决定的。使用中的软件是不可能不维护的,而且与硬件完全不同,软件越维护越好使。

    然而,和最初的软件工程诞生于对失败的领悟一样,在再工程需求和“失败”没有成熟,即软件生存期的中后期全貌尚未完全显现出来的时候,要想研究它的问题、规律及其解法也是勉为其难。

    第一次(正向)软件工程方法学研究已取得了许多重大进展,在其指导下开发的既存软件资源越来越多,覆盖面也越来越大,使新系统软件开发即一次(正向)软件工程的瓶颈变宽,从而以维护为中心的需求剧增,但相关的方法学和技术人员严重匮乏,使逆向工程和再工程开发成了新的软件工程瓶颈,且此危机有愈演愈烈之势。

8         再工程会带来什么

   关注和研究软件工程方法学的人不难发现它有着严重的“虎头蛇尾”问题。即软件生存期前期研究成果较多,而后期(测试和维护)研究薄弱。微软的 William Gates1999年曾指出,测试将是今后软件工程最重要的研究领域。更有不少人指出:21世纪软件维护技术与测试技术将并行发展。

而软件再工程有两个突出特征:一是比一次软件工程更迫切地需要计算机辅助支持,二是测试工作比例远大于一次软件工程。前者在再工程方法学研究和软件模式运动推动下可以找到自动化解决方案,后者则须强化对测试方法学体系的研究。二者恰好构成了对软件工程方法学薄弱环节研究的推动力。这种推动力来势之猛很难估量,上个世纪末以来发展迅速的电子商务、电子政务等都属再工程范畴。


posted on 2006-06-20 11:15  Vincent.Hu  阅读(429)  评论(0编辑  收藏  举报