Refactoring 应用的场合
增加功能
如果你的代码不需要增加新的功能,那么你几乎没有必要对他进行Refactoring。增加功能是进行Refactoring最常见的起因。通常,refactoring的第一个目标就是为了让代码更容易理解。Refactoring不仅仅可以使难以理解的代码更加清晰,他也可以使你对它的理解呈现一种交互状态,你和代码的交互。理解了,新的功能就很容易加入。
另一个原因是原来代码的结构很难直接扩展,可能是因为一个类和另外一个类的绑定太紧,或者是他使用了大量的case语句,或者是有一些重复的代码让你觉得加入你的代码会使结构更差。总而言之,直接加入新功能不是太方便。这时候,你就需要Refactoring,使得旧的代码在能让旧的test case通过的情况下,让新的代码加入更加方便、快捷。
重用
重用可以说是增加功能的一个特例。因为这是OO增加功能最主要的方法。但这里有一些差别。
如果你的代码是一个框架,那么当把代码交给其他人使用时,你必须重点考虑框架的重用方法。譬如,你想在框架之上增加一层facade,这时候,你可能需要refactoring一些内部类,让这个Facade的结构更容易加入。
对框架Refactoring可能需要是一个反馈的过程。因为框架一旦稳定就可能长期存在,一般你不会直接对这个框架增加功能。这时,不同应用程序对框架使用经验的积累可能使得你的Refactoring具有更加明确的目标,更加深层次的抽象。
修正bug
bug产生的原因有很多。可能是你对解决的问题理解不够,也可能是程序的代码过于纷乱。当你的代码结构不是很清晰时,bug往往隐藏在代码之中,使你很难发现。应用Refactoring让代码结构结构更清晰的同时,也使得bug自动浮出水面。
Refactoring所追求的目标和过程往往使得代码的Bug无处遁形。Eric E. Allen在IBM中developerWorks的bug pattern中的一个模式就是copy-and-paste方法造成代码重复,从而产生bug的一个例子。这个 Rogue Title Pattern是一个非常常见的bug例子,当你修正了程序中一个bug时,你发现程序运行的结果是你明明已经修正的bug还在作怪。究其原因,就是程序员在实现一个功能的时候把一段代码复制过去,稍加修改,就成为一个新的函数。你修改了一个地方,但却忘了另外一个地方。
Refactoring力求排除重复代码,如果有这样的bug,在refactoring以后,一次修改就能纠正所有的问题。
Code Review
Review可以使得专家知识传播到整个开发队伍。他们能够帮助更多的人理解一个大系统更多方面的问题。它们对于编写清晰的代码也有很好的帮助。俗话说得好,三个臭皮匠顶一个诸葛亮,Review也使得你能够接受更多其他人的好意见和想法,从而使你的设计和代码更加合理。
按照Martin Fowler的建议,我使用refactoring作为我的主要review方法。我有比较多的机会做这样的Code Review,在程序员发现一个难以解决的bug,或者当他们觉得自己的思路无法进行下去的时候,他们通常会要求我的帮助。
我的方法通常是问几个简单的问题,然后直接找到他们的代码,进行refactoring,在这个过程中,我总是一边refactoring,一边和程序员讲解我这样做的意图。令人惊奇的是,一旦我这样去做,程序员往往能够在我refactoring的过程中自己发现问题。由于Refactoring的结果非常具体,我很容易对程序员讲述一些本来就可以直接这样实现的例子,这对提高程序员的编程能力有极大的帮助。
味道
Kent Beck的" Code Smell"或许是OO社团最有人情味的名言了。Martin Fowler和Kent Beck专门合著了《Refactoring》其中的一章就叫《Bad Smells in Code》。
虽然对Refactoring的研究涉及到了形式化地探测代码的结构,譬如专门的重复代码监测工具。但这些研究尚处于幼年期。更何况,很多代码的结构并不是能够用简单的数字统计和抽象语法树所能解决的。
Code Smell是高水平的程序员对代码的一种感觉,当你能够闻到这样的味道时,你就可以在不涉及到程序所要解决的具体问题时,就"闻到"代码结构的好坏。
Kent Beck 和Martin Fowler列出的代码味道有:
Duplicated CodeRefactoring和命名
Refactoring使代码更容易理解的最有效和常用的手段之一就是给类、方法和属性一个恰如其分的名字。很多公司和个人都编出了大量的代码命名规范。如果这些规范太繁琐,那就无法执行。有好的命名规则是好事,但并不总是如此。最重要的是,在整个项目开发团队内,必须保持一致的命名规则,也许不是世界上最好的,但却是对该项目最好的。下面一些是Refactoring中一些有效的命名原则:
类命名
Kent Beck提出其中大多数的规则。Kent Beck的《Smalltalk Best Practice Pattern》是我至今为止读过的所有书里面最符合我审美观点的书籍-渊博的知识和春秋的笔法。
简单超类名-传达设计目的
命名总要受各种因素的影响。你想把名字取得尽量短,易于输入、格式化、容易说出口。同时,你也想让名字尽可能包含更多的信息,这样读者就能够更快地理解名字所包含的实际内容。你可能取一些尽量为人所熟悉的名字,这样在名字中就可以传递更多的共同经验。同时,你也想让名字尽量唯一,别人的代码命名就不会和你重复。
Kent Beck给出的第一个规则就是不要缩写。缩写对输入(20年内10-100次)的考虑多余对理解(20年内1000-10000次)的考虑。理解缩写词需要两步,第一步理解这些缩写代表的词语,第二步采取理解这些词语所代表的意义。
对一个大层次的根类命名是一个重大的决策。在未来的20年中,人们可能不断地使用你所给出的名字。你必须不犯错误。
人们通常在命名超类时加上各种各样的修饰,富有计算机科学意义、给人深刻印象但最终却没有意义的单词,如Object,Thing,Component,Part,Manager,Entity或Item。
你在创建一个词汇表,而不是写一个程序。暂时做一回诗人。简单、生气勃勃、容易记忆远远比说出全部内容更有效。规则:
Name a superclass with a single word that conveys its purpose in the design。
(用一个单词命名超类,传达它的设计目的),好的例子包括:Number 、Collection、Magnitude、Model
全称子类名-区分异同
命名类的一种方法是给一个唯一的名字。唯一的名字可以让你用用最短的信息表达最多的信息。
这对于通用术语来说是对的。Array是Collection的一个子类,因为绝大多数人都知道"Array"意味着什么。
但在绝大多数情况下,类继承的层次结构对于理解你的代码十分重要,特别是一个子类概念上是超类的变种同时又和超类共享实现的情况。你需要传递两部分信息:1. 新类如何相同,以及 2. 新类如何不同
要表达相同,你可以借用超类的名字。这不一定是一个直接的子类和父类关系,层次上有一定距离也无妨。要表达不同,你需要一个单词确切地强调新类为什么不是超类的理由。所以,有规则: Name subclasses in your hierarchies by prepending on an adjective to the superclass name。(在超类名字前加上内容命名你层次中的子类。)例如:BigInteger 是一个可以表达很大很大数字的整数。
方法命名-揭示意图
为什么好的代码总有很多简单的方法,代码可能只有一行,譬如:
class ParagraphEditor。。。。
public void highlight(aRectangle:Rectangle){
reverse(aRectangle);
}
这个highlight只是分派到reverse,为什么还需要?关键在于Communication。因为有了这个方法,以后的代码就可以用highlight来说话。我要加亮一个区域,我就highlight它。
你当然可以直接使用reverse,结果是一样的。但所有调用代码都揭示了实现-"我通过反转一个矩形来加亮它"。代码应当揭示意图,它另外的好处是可以更方便通过继承修整。如果你想要一个ParagraphEditor用颜色加亮,那么只需继承ParagraphEditor并覆盖highlight即可。所以:
communicate what is to be done rather than how it is to be done。(传达你要做什么(接口)而不是你如何做(实现))
实例(临时)变量命名-暗示角色
任何实例变量传达的信息包括两部分:
一个变量的目的或者说它担当的角色对读者非常重要,因为它能够正确地引导读者的注意力。一般,你在阅读代码时脑袋里总有一个目的。如果你理解变量的角色,而这个角色和你的目的无关,那么你可以直接跳过使用该变量的无关代码。如果发现该变量的角色和你的目的有关,那么你就能马上缩小你的阅读范围-那些引用此变量的相关代码。
一个变量如何被使用和发送给它什么消息通常是它的"类型"。理解类型并非不重要。但是,对于实例变量来说,你能够了解这个变量所担当角色的唯一地方就是它的名字。如果一个Point中有两个实例变量叫做int1、int2而并非x、y,那么在你理解哪一个是横向坐标,哪一个是纵向坐标前,你可能要都上一堆代码。更何况你还要阅读与之相关的很多代码。而变量的类型很容易从它声明、传递给它的消息看出来。所以:
Name instance variable for the role they play in the computation。Make the name plural if the variable will hold a Collection。
(用计算中实例变量所承担的角色对它命名。如果变量持有一个集合,使用复数。)
参考书目
Martin Fowler : 《Refactoring:Improving the design of existing Code》 Kent Beck:《Extreme Programming Explained》 Kent Beck:《Smalltalk Best Practice Pattern》 William C. Wake: 《Extreme Programming Explored》 Jeff Langr: 《Evolution of Test and Code Via Test-First Design》 Robert C. Martin: 《An Extreme Programming Episode》 Don Robert,John Brant and Ralph Johnson:《A Refactoring Tool for Smalltalk》 Jrefactory: jrefactory.Sourceforge.net Gamma , Junit: http://members.pingnet.ch/gamma/junit.htm Refactoring Browser: http://chip.cs.uiuc.edu/users/brant/Refactory/ GOF:《Design Patterns: Elements of Reusable Object Oriented Software》 MartinFowler :《UML distilled》 Braint Foote and Joseph Yoder:《Big Ball of Mud》 William Opdyke Ph.D. Thesis:《Refactoring Object-Oriented Frameworks》 Braint Foote and William Opdyke:《Life Cycle and Refactoring Patterns that Support Evolution and Reuse》 Mel O Cinneide:Ph.D. :《Automated Application of Design Patterns: a Refactoring Approach》 Donald Bradley Roberts: Ph.D.:《Practical Analysis for Refactoring》 C2:http://www-900.ibm.com/developerWorks/cn/java/l-refactory/part7/www.c2.com Eric Gamma, Kent Beck: Junit: http://www.junit.org/ Lance Tokuda:《Evolving Object-Oriented Designs with Refactoring》 Michael Hunger:《Refactoring:Benefits and Disadvantages》 TOMCAT BOOK Shiyiying:Code Smell serials William F. Opdyke,Object-Oriented Refactoring,Legacy Constraints and Reuse Stephen R.Schach:《Classical and Object-Oriented Software Engineering》 Christopher Alexander:《The Timeless Way of Building》
关于作者
石一楹,现任浙江大学灵峰科技开发公司技术总监。多年从事OO系统分析、设计。现主要研究方向为Refactoring和分析模式。
本文地址:http://com.8s8s.com/it/it36718.htm