What Refacing:
重构(Refactoring)就是在不改变软件现有功能的基础上,通过调整程序代码改善软件的质量、性能,使其程序的设计模式和架构更趋合理,提高软件的扩展性和维护性。
经典的Refacing Definition:
(1)Refactoring means rewriting existing source code with the intent of improving its design rather than changing its external behavior. The focus of refactoring is on the structure of the source code, changing the design to make the code easier to understand, maintain, and modify. - 来自Borland Together提供的文档,觉得这个定义很清晰明了。
(2)重构是这样一个过程:在不改变代码外在行为的前提下,对代码做出修改,已改进程序的内部结构。-来自Martin Fowler的定义。
Refactoring (noun): a change made to the internal structure of software to make iteasier to understand andcheaper to modifywithout changing its observable behavior.
Refactor (verb): to restructure softwareby applying a series of refactoringswithout changing its observable behavior.
Why Refacing:
Improves the design of software
Makes software easier to understand - leads to higher levels of understanding
Helps to find bugs
Helps to be more effective at writing robust code
Helps to program faster
1、·持续偏纠和改进软件设计。
2、·使代码更易为人所理解。
3、·帮助发现隐藏的代码缺陷。
4、·从长远来看,有助于提高编程效率。
When Refacing:Three strikes and you refactor.
When you add functionality.
When you need to fix a bug.
When you do a code review.
1、·代码中存在重复的代码。
2、·过大的类和过长的方法。
3、·牵一毛而需要动全身的修改。
4、·类之间需要过多的通讯。
5、·过度耦合的信息链。
6、·不完美的设计。
7、·缺少必要的注释。
不要当你需要新的功能的时候,或者原来的程序出了Bug,你才想起重构。
总结:代码重构在你开始写代码就是被要考虑进去的,边写代码边重构。
What makes programs hard to work with?
Kent Beck:
We want programs:
easy to read,
have all logic specified in one and only place,
do not allow changes to endanger existing behavior,
allow conditional logic to be expressed as simply as possible.
Refactoring and Design
Refactoring and Performance
Refactoring will make software run more slowly, but it also makes the software more easy for performance tuning. The secret to fast software is towrite tunable software first andthen to tune it for sufficient speed.
Run a profiler to find the
performance hot spots.
Focus on these performance hot spots and
optimize them.
Rerun the profiler, if the performance does not improve, back out the change.
Continue the process until the performance satisfies your users.
How Refacing:
减少代码坏味道(Bad Smell in Codes)的重构策略
1.Duplicated Code 尽量消除重复的代码,将它们合而为一 Do Not Repeat Yourself.
根据重复的代码出现在不同的地方,分别采取不同的重构的策略:
在同一个Class的不同地方:通过采用重构工具提供的Extract Method功能提炼出重复的代码, 然后在这些地方调用上述提炼出方法。
在不同Subclasses中:通过Extract Method提炼出重复的代码,然后通过Pull Up Method将该方法移动到上级的Super class内。
在没有关系的Classes中:通过对其中一个使用Extract Class将重复的代码提炼到一个新类中,然后在另一个Class中调用生成的新类,消除重复的代码。
2.Long Method 拆解过长的函数
过长的函数在我们的日常代码中经常可见,在C#中常通过#region #endregion区隔为不同的功能区域。
重构策略:通过Extract Method将过长的函数按照功能的不同进行适当拆解为小的函数,并且给这些小函数一个好名字。通过名字来了解函数提供的功能,提高代码的理解性。
3.Large Class 拆解过大的类
过大的类也经常见到,特别是类中含有大量的成员变量。
重构策略:通过Extract Class将一些相关成员变量移植到新的Class中,如Employee类,一般会包含有联系方式的相关属性(电话, Mobile,地址,Zip等等),则可以将这些移植到新的EmployeeContact类中。
4.Long Parameter List 过长的参数列
过长的参数列的主要问题是难以理解,并且难以维护。如果要增加新的参数或者删除某一参数,易造成参数前后不一致。
重构策略:如果可以通过向已存在的对象查询获取参数,则可通过Replace Parameter with Method,移除参数列,通过在函数内部向上述已存在的对象查询来获取参数。
如果参数列中若干参数是已存在对象的属性,则可通过Preserve Whole Object将这些参赛替换为一个完整对象,这样不仅提高代码的可读性,同时已易于代码今后的维护。
另外,还可以将若干不相关的参数,使用Introduce Parameter Object来创建一个新的参数类。不过,我个人觉得如果这些情况过多的话,会产生很多莫名其妙的参数类了,反而降低代码的可读性。
5.Divergent Change(发散式变化)
现象:当某个Class因为外部条件的变化或者客户提出新的功能要求等时,每次修改要求我们更新Class中不同的方法。不过这种情况只有在事后才能觉察到,因为修改都是在事后发生的么(废话)。
重构策略:将每次因同一条件变化,而需要同时修改的若干方法通过Extract Class将它们提炼到一个新Class中。实现目标是:每次变化需要修改的方法都在单一的Class中,并且这个新的Class内所有的方法都应该与这个变化相关。
6.Shotgun Surgery(霰弹式修改)
现象:当外部条件发生变化时,每次需要修改多个Class来适应这些变化,影响到很多地方。就像霰弹一样,发散到多个地方。
重构策略:使用Move Method和Move Field将Class中需要修改的方法及成员变量移植到同一个Class中。如果没有合适的Class,则创建一个新Class。实现目标是,将需要修改的地方集中到一个Class中进行处理。
比较Divergent Change(发散式变化)和Shotgun Surgery(霰弹式修改):
前者指一个Class受到多种外部变化的影响。而后者指一种变化需要影响到多个Class需要修改。都是需要修理的对象。
7.Feature Envy(依恋情结)
现象:Class中某些方法“身在曹营心在汉”,没有安心使用Class中的成员变量,而需要大量访问另外Class中的成员变量。这样就违反了对象技术的基本定义:将数据和操作行为(方法)包装在一起。
重构策略:使用Move Method将这些方法移动到对应的Class中,以化解其“相思之苦”,让其牵手。
8.Data Clumps(数据泥团)
现象:指一些相同数据项目(Data Items),如Class成员变量和方法中参数列表等,在多个Class中多次出现,并且这些数据项目有其内在的联系。
重构策略:通过使用Introduce Parameter Object(创建新的参数对象取代这些参数)或Preserve Whole Object(使用已存在的对象取代这些参数),实现使用对象代替Class成员变量和方法中参数列表,清除数据泥团,使代码简洁,也提高维护性和易读性。
9.Primitive Obsession(基本型偏执狂)
现象:在Class中看到大量的基本型数据项目(Data Item),如Employee类中有大量的数据成员,Employee#, FirstName, MiddleName, LastName, Address, State, City, Street, Zip, OfficePhone, CellPhone, Email……等等。
重构策略:使用Extract Class(提炼新类)或Preserve Whole Object(使用已存在的对象取代这些参数),实现使用对象代替基本型数据项目(Data Item)。如上述Employee类中就可分别提炼出EmployeeName和EmployeeContact两个新类。
10.Switch Statements(Switch语句)
现象:同样的Switch语句出现在不同的方法或不同的Class中,这样当需要增加新的CASE分支或者修改CASE分支内语句时,就必须找到所有的地方,然后进行修改。这样,就比较麻烦了。
重构策略:(1)首先采用Extract Method将Switch语句提炼到一个独立的函数。
(2)然后以Move Method搬移到需要多态性(Polymorphism)的Superclass里面或者是构建一个新的Superclass。
(3)进一步使用Replace Type Code with Subclasses或者Replace Type Code with State/Strategy。这步就比较麻烦些,不过记住如下基本规则:这里一般有3个Class分别为Source Class、Superclass和Subclass。
Source Class:
l 使用Self Encapsulate Field,将Type Code成员变量封装起来,也就是建立对应的Setter/Getter函数。
l 在Source Class中增加一个Superclass类型的成员变量,用来存放Subclass实例对象。
l 在Source Class中的Getter函数,通过调用Superclass的Abstract Query函数来完成。
l 在Source Class中的Setter函数,通过调用Superclass中的Static工厂化方法来获取合适的Subclass实例对象。
Superclass:
新建的一个Class(注:就是上面通过Move Method搬移生成的Superclass),根据Type Code的用途命名该Class,作为Superclass。
l 在Superclass中建立一个Abstract Query函数,用来获取Subclass的Type Code。
l 在Superclass中创建Static工厂化方法生产对应的Subclass对象,这里会存在一个Switch语句(不要再动脑筋来重构这个Switch语句了,这个Switch语句不会在多处重复存在,并且这里用于决定创建何种Subclass对象,这是完全可以接受的)。
Subclass:
l 根据每一个Switch/Type分支,建立对应的Subclass,并且Subclass的命名可以参考Switch/Type分支的命名。
l 在每一个Subclass中重载Superclass的Abstract Query函数,返回特定的Type Code。
(4)现在Superclass仍然存在Switch分支,是时候轮到Replace Conditional with Polymorphism上场了。具体而言,就是在每一个Subclass中创建重载方法(注:该方法是Superclass中含有Switch语句的方法),并将Superclass中Switch语句对应的Case分支剪切过来。最后将Superclass中该方法初象化Abstract,并清除Switch语句及其所有的Case分支。
这样就完成了整个重构过程,这个比较麻烦。
11.Parallel Inheritance Hierarchies(平行继承体系)
现象:为某个class增加一个subclass时,也必须为另一个class相应增加一个subclass。重构策略: 在一个class继承体系的对象中引用(refer to)另一个class继承体系的对象,然后运用Move Method和Move Field将被引用class中的一些方法和成员变量迁移宿主class中,消除被引用class的继承体系(注:这种平行继承体系好象比较少见也)。
12.Lazy Class(冗赘类)
现象:某一些class由于种种原因,现在已经不再承担足够责任,有些多余了。如同国有企业冗余人员一样,需要下岗了。
重构策略:通过Collapse Hierarchy,将这些冗余的class合并到superclass或subclass中,或者通过Inline Class(与Extract Class相反),将这些冗余class中的所有Method/Field迁移到其他相关的class中。
13.Speculative Generality(夸夸其谈未来性)
现象:系统中出现一些无用的abstract class,或者非必要的delegation(委托),或者多余的参数等等。
重构策略:分别使用Collapse Hierarchy合并abstract class,使用Inline Class移除非必要的delegation,使用Remove Parameter删除多余的参数。
14.Temporary Field(令人迷惑的暂时值域)
现象:class中存在一些Field,这些Field只在某种非常特定的情况下需要。
重构策略:通过Extract Class将这些孤独的Field及其相关的Method移植的一些新的Class中。提炼出来的新Class可能没有任何抽象意义,只是提供Method的调用,这些新Class一般称为Method Object。
15.Message Chains(过度耦合的消息链)
现象:向一个对象请求另一个对象,然后再向后者请求另一个对象,……,这就是Message Chain,意味着Message Chain中任何改变,将导致Client端不得不修改。
重构策略:通过Hide Delegate(隐藏委托关系)消除Message Chain,具体做法是在Message Chain的任何地方通过Extract Method建立一个简单委托(Delegation)函数,来减少耦合(Coupling)。
16.Middle Man(中间转手人)
现象:过度运用delegation,某个/某些Class接口有一半的函数都委托给其他class,这样就是过度delegation。
重构策略:运用Remove Middle Man,移除简单的委托动作(也就是移除委托函数),让client直接调用delegate受托对象。和上面的Hide Delegate(隐藏委托关系)刚好相反的过程。
由于系统在不断的变化和调整,因此[合适的隐藏程度]这个尺度也在相应的变化,Hide Delegate和Remove Middle Man重构策略可以系统适应这种变化。
另外,可保留一部分委托关系(delegation),同时也让Client也直接使用delegate受托对象。
17.Inappropriate Intimacy(狎昵关系)
现象:两个Class过分亲密,彼此总是希望了解对方的private成分。
重构策略:可以采用Move Method和Move Field来帮助他们划清界限,减少他们之间亲密行为。或者运用Change Bidirectional Association to Unidirectional,将双向关联改为单向,降低Class之间过多的依存性(inter-dependencies)。或者通过Extract Class将两个Class之间的共同点移植到一个新的Class中。
18.Alternative Classes with Different Interfaces(异曲同工的类)
现象:两个函数做相同的事情,却有不同的signature。
重构策略:使用Rename Method,根据他们的用途来重命名。另外,可以适当运用Move Method迁移某些行为,使Classes的接口保持一致。
19.Incomplete Library Class(不完美的程序库类)
现象:Library Class(类库)设计不是很完美,我们需要添加额外的方法。
重构策略:如果可以修改Library Class的Source Code,直接修改最好。如果无法直接修改Library Class,并且只想修改Library Class内的一两个函数,可以采用Introduce Foreign Method策略:在Client Class中建立一个函数,以外加函数的方式来实现一项新功能(一般而言,以server class实例作为该函数的第一个参数)。
如果需要建立大量的额外函数,可应该采用Introduce Local Extension:建立一个新class,使它包含额外函数,并且这个class或者继承或者wrap(包装)source class。
20.Data Class(纯稚的数据类)
现象:Data Class指:一些Class拥有Fields,以及用来访问Fields的getter/setter函数,但是没有其他的功能函数。(感觉这些Data Class如同Entity Class或Parameter Class,用来传递参数,我认为这种情况下没有必要重构。)
重构策略:找出其他class中访问Data Class中的getter/setter的函数,尝试以Move Method将这些函数移植到Data Class中,实现将数据和操作行为(方法)包装在一起,也让Data Class承担一定的责任(方法)。
21.Refused Bequest(被拒绝的遗赠)
现象:Subclass不想或不需要继承superclass的部分函数和Field。
重构策略:为subclass新建一个兄弟(sibling class),再运用Push Down Method和Push Down Field将superclass中的相应函数和Field下推到兄弟class,这样superclass就只包含subclass共享的东西了。其实,也就是将superclass中一些与特定的函数和Field放到特定的subclass中,superclass中仅包含subclass共享的函数和Field。
如果不想修改superclass,还可以运用Replace Inheritance with Delegation来达到目的。也就是以委托取代继承,在subclass中新建一个Field来保存superclass对象,去除subclass对superclass的继承关系,委托或调用superclass的方法来完成目的。
22.Comments(过多的注释)
现象:(晕倒,这个也要重构,Remove掉所有的Comments吗?不是。)当代码中出现一段长长的注释,一般是由于代码比较糟糕,需要进行重构,除去代码的坏味道。
重构策略:通过上面提及的各种重构策略,将代码的坏味道去除,使注释变成多余。
如果需要注释/解释一段代码做了什么,则可以试试Extract Method,提取出一个独立的函数,让函数名称解释该函数的用途/功能。另外,如果觉得需要注释来说明系统的某些假设条件,
也可尝试使用Introduce Assertion(引入断言),来明确标明这些假设。
当你感觉需要撰写注释时,请先尝试重构,试着让所有的注释都变得多余。
注:并不是一看到Switch语句及CASE分支,就马上/偏执狂采用上述重构策略进行重构,画蛇添足或吃亏不讨好(个人观点)。一般而言,只有看到多处出现相同的Switch语句时,才应该考虑进行重构。
Refactoring Practice:
1、When you find you have to add a feature to a program, and the program's code is not structured in a convenient way to add the feature, first refactor the program to make it easy to add the feature, then add the feature.
2、Before you start refactoring, check that you have a solid suite of tests. These tests must be self-checking.
3、Refactoring changes the programs in small steps. If you make a mistake, it is easy to find the bug.
4、Any fool can write code that a computer can understand. Good programmers write code that humans can understand.
5、Don't publish interfaces prematurely. Modify your code ownership policies to smooth refactoring.
6、Make sure all tests are fully automatic and that they check their own results.
7、A suite of tests is a powerful bug detector that decapitates the time it takes to find bugs.
8、Run your tests frequently. Localize tests whenever you compile—every test at least every day.
9、When you get a bug report, start by writing a unit test that exposes the bug.
10、It is better to write and run incomplete tests than not to run complete tests.
11、Think of the boundary conditions under which things might go wrong and concentrate your tests there.
12、Don't forget to test that exceptions are raised when things are expected to go wrong.
13、Don't let the fear that testing can't catch all bugs stop you from writing the tests that will catch most bugs.
1、把握全局,先了解业务
2、阅读代码,清除冗余代码,重构变量名等,删除不一致或无效的注释,补写注释,为下一步做准备
3、将通用的代码块提升为方法
4、小步前进,随时测试
5、重构要随时进行
6、避免过度设计。模式是把双刃剑。
最终的收获:每个程序员都应该不断加强自己的编程思想,注重编程质量。
1、 管理层不重视代码书写,认为是体力劳动;
2、 项目经理疲于应付进度,无心且无力;
3、 程序员水平参差不齐,缺乏正确的指导。
4、.经验不足,分析设计不到位;
5、.敏捷开发,虽然经验很多,但为了快速开发,没有经过分析设计;
6、.缺乏意识,只为实现功能而写代码,不管代码质量;
About Refacing:
重构工作虽然重要,但是得不到过多的认可,目前国内关注的是可用性,对于代码质量并没有得到应有的重视。
Refacing Books:
《work effectively with legacy code》 修改代码的艺术
《The Programtic Programmer From JoumeyMan to Master》 程序员修炼之道
《Pattern-Oriented Software Architecture Volume 4》 面向模式的软件架构 卷4
《Agile Principles、Patterns and Practice in C#》 敏捷软件开发 原则、模式与实践(C#版)
《Code Quality The Open Source Perspective》 高质量程序设计艺术
《Refactoring improving the Designe of Existing Code》 重构 改善既有代码的设计
《Design Patterns Explained 》 设计模式解析
《反模式 危机中软件、架构和项目的重构》
《Refactoring to Patterns》 重构与模式
《More Programming Pearls》 编程珠玑II
《Programming Pearls》 编程珠玑(第2版)
《Beginning Java Objects》 中文版:从概念到代码(第2版)
《设计模式解析(第2版)》
《敏捷软件开发:原则、模式与实践(C#版)》
《Java设计模式》
《重构与模式》
《UML面向对象建模与设计(第2版)》