• 重构 小步进行曲


    [非原创,原文链接]

    导读:笔者根据自己的经验说明的重构的重要性,以及在实际操作中请谨记小步进行,让你的每一步都是安全的。

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

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

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

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

  • 相关阅读:
    互斥锁和条件变量实现生产者消费者问题
    信号量实现生产者消费者问题
    IPC进程间通信---共享内存
    IPC进程间通信---消息队列
    图的遍历---广度优先遍历和深度优先遍历
    图的两种存储方式---邻接矩阵和邻接表
    内存分配---FF、BF、WF三种算法
    C++的前置++、后置++和前置--、后置--
    IPC进程间通信---信号量
    Linux进程间通信---管道和有名管道
  • 原文地址:https://www.cnblogs.com/gotodsp/p/3548811.html
Copyright © 2020-2023  润新知