重构 小步进行曲

日期: 2010-11-16 作者:余昭辉(横刀天笑) 来源:TechTarget中国 英文

  对于重构的重要性相信不需要再强调。在开发的过程中,随着代码的演进,需求的改变我们必须持续不断的对既有代码进行重构:重命名(以更精确地反映元素的职责),提取方法(用更具描述性的名字来归纳一段代码),提取基类(消除重复,提高抽象层次)等。如果我们只是一味的去开发新的代码,而对老代码不闻不问,以为只要它能工作就够了,总有一天我们会在这个上面栽跟头的,设计会慢慢的走向腐化。

  好了,本文并不是想强调重构有多重要,因为那不用强调。我想说的是:重构,是小步进行曲。

  在阅读Martin Fowler的《重构》时,几次都没有将全书读完,前面几章倒是读的大呼过瘾,不过后面的重构目录让我心生困惑:重构需要这么小的步骤么?连一个重命名都需要这么繁琐的进行。曾几时我还小人的想:老马是不是才思枯竭,没有什么写的了,但又不想出版这么一本薄薄的册子,如是故意弄的这么小步伐,凑篇幅。

  不过现在我越来越觉得小步进行的重要性,如果我不能小步的重构一块代码,我甚至都不会去重构它。

  在最近的结对编程中,经常碰到这样的场景:

  我:这段代码太长了,我们重构一下吧

  Partner:好

  我:(鼠标键盘一起上了)

  Partner:你这样不对(然后抢过键盘)

  (实际上我们仅仅是为了想建立一个新类,然后将这个类里的一些方法移动到新的类里,这样原有类的代码就变短了,职责也清晰了,而新类也有很清晰的职责。)

  我:(我的做法是,新建一个类,然后将方法copy过去)

  Partner:(而搭档的做法是,新建一个类NewClass,然后在原有类里声明一个这个类的字段newClass,然后将要抽离的方法method的方法体再抽取一个方法internalMethod,然后将新方法internalMethod移动到NewClass中,这样method方法就变成newClass.internalMethod,而method的调用者没有任何变化,而在这中间的每一步都运行一下单元测试)

  原有类:

以下是引用片段:
1: public class OldClass{   
2:     //many other code   
3:     public void method(){   
4:         //many code   
5:     }   
6:     //many other code   
7: }

  第一步:

以下是引用片段:
1: public class NewClass{   
2: }   
3:     
4: public class OldClass{   
5:     private NewClass newClass = new NewClass();   
6:     //many other code   
7:     public void method(){   
8:         //many code   
9:     }  
10:     //many other code  
11: }

  第二步:

以下是引用片段:
1: public class NewClass{   
2: }   
3:     
4: public class OldClass{   
5:     private NewClass newClass = new NewClass();   
6:     //many other code   
7:        
8:     //原方法继续在这里,保证测试仍然可以通过   
9:     public void method(){  
10:         internalMethod();  
11:     }  
12:     //many other code  
13:    
14:     //使用IDE重构工具提取方法  
15:     public void internalMethod(){  
16:         //many code  
17:     }  
18: }

  第三步:

以下是引用片段:
1: public class NewClass{   
2:     //使用IDE 移动方法重构   
3:     public void internalMethod(){   
4:         //many code   
5:     }   
6: }   
7:     
8: public class OldClass{   
9:     private NewClass newClass = new NewClass();  
10:     //many other code  
11:       
12:     //原方法继续在这里,保证测试仍然可以通过  
13:     public void method(){  
14:         newClass.internalMethod();  
15:     }  
16:     //many other code  
17: }

  在经过上面的三步后,移动方法重构就基本完成了,然后再进行一些重命名等工作,将代码重构的更好。

  虽然我嘴上没说,但对这种小步进行的方式却很不以为然,我觉得这完全是在浪费时间,我们有能力控制重构过程的小混乱,然后让它恢复秩序。

  日子就这么一天天的过去了,终于有一天,因为大部分人去参加了公司的会议,没有新的故事可以做了,如是我决定做一下有个模块的重构工作,为了确保万无一失,我还花了很长时间来绘制重构完成后应该有的类阶层次结构图,然后贴在电脑屏幕旁,然后还做了一个简单的计划,我应该从哪里入手,怎么做怎么做之类的。唯独我没有做到的是小步进行,当时只是为了能尽快的达到结构图的目的,大步进行,我甚至没有借助IDE提供的工具,靠人肉重构(如果你也在使用这种方式,请千万不要继续下去了,这是重构大忌)。我不仅忘记了使用IDE提供的重构工具,甚至还忘记了分步频繁提交的准则,实际上我一开始,这次重构之旅注定要失败。到了下午的时候,我看到测试类里满眼的红色标记(编译不通过),我都焦头烂额了,但是我甚至还一错再错将单元测试暂时屏蔽,以为我可以在重构完成后再回过头来修理测试。最后,我在没有测试的情况下,继续摸黑前行。最后,好不容易将功能代码能编译通过了,我舒了一口气,然后敲了几个命令编译-部署,去倒了一杯咖啡,我回来的时候刷新了一下页面,给了我当头一击,一个刺眼的错误页面展现在眼前。因为我已经走的太远,我都不知道如何去调查错误,这个错误是从几何时引入的。没办法,在下班前的一刻我将整天的工作全部rollback。

  在日后的结对编程过程中,我仔细的观察了同事的重构过程,每一步都那么小,每一次都保证测试通过然后再进行下一步重构,每一次有意义的重构都赶紧提交代码,我明白了老马的用心良苦:

  1、如果我们每一步都很小,我们就能在下次重构之前记住我们上一次干了啥,重构完成之后,运行测试发现测试未通过,我们就能很容易的定位错误,修正错误。

  2、如果每一步都很小,我们在修改功能代码后,我们也能很容易的演进测试,让测试反应当前的变化,这样测试就会与功能代码共同演进。

  3、如果每一步都很小,我们在跑完测试后可以马上提交代码,当我们发现重构发生问题时,我们可以回退到上一步,而不至于像我那样将整天的工作全部回滚。

  4、如果每一步都很小,我们就可以借助IDE对重构提供的支持来安全的进行,因为IDE对步骤太大的重构支持并不好。

  如果你也在重构,请谨记小步进行,让你的每一步都是安全的。请不要厌烦《重构》中重构目录的罗嗦,因为那才是重构的精髓所在。

我们一直都在努力坚持原创.......请不要一声不吭,就悄悄拿走。

我原创,你原创,我们的内容世界才会更加精彩!

【所有原创内容版权均属TechTarget,欢迎大家转发分享。但未经授权,严禁任何媒体(平面媒体、网络媒体、自媒体等)以及微信公众号复制、转载、摘编或以其他方式进行使用。】

微信公众号

TechTarget微信公众号二维码

TechTarget

官方微博

TechTarget中国官方微博二维码

TechTarget中国

相关推荐

  • 代码重构工具选择的七大标准

    代码重构工具有很多,不管是Java、PHP、.NET还是其他,都有工具帮助重构代码的繁重过程变得轻松一点和安全。其数量已经多到眼花缭乱令人难以选择的地步。

  • 新入行程序员应知的十个秘密

    初出茅庐的你带着仍残留墨香的毕业证书踏上工作岗位,马上就被书上没写的规则和各种繁杂的日常事务来了个下马威。这样的故事实在是司空见惯,编程工作也不例外。

  • 实例操作:十个步骤教你将闭源项目转换为开源

    Difio是一个闭源项目,但是作者决定把它开源,以便能够内部部署,以及吸引更大的社区参与。以下是作者Alexander Todorov撰写的在将Difio开源过程中所经历的10个步骤。

  • HBuilder:最快的Web开发IDE

    HBuilder是DCloud推出的一款支持HTML5的Web开发IDE。快,是HBuilder的最大优势,通过完整的语法提示和代码输入法、代码块等,大幅提升HTML、js、css的开发效率。