• 重构,小步进行曲


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

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

    在阅读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对步骤太大的重构支持并不好。

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

  • 相关阅读:
    使用phantomjs进行刷商务通对话
    利用python打造自己的ftp暴力破解工具
    notepad++开发中常用的插件
    织梦重装漏洞其实并不是那么好利用
    织梦开启调试模式
    网站安全开发人员不可缺少的火狐插件
    dos批量替换当前目录后缀名
    wpf 帧动画
    C 语言 mmap
    C 语言 ioctl
  • 原文地址:https://www.cnblogs.com/yuyijq/p/1876496.html
Copyright © 2020-2023  润新知