zoukankan      html  css  js  c++  java
  • 读书报告之《改动代码的艺术》 (I)

    《改动代码的艺术》,英文名《Working Effectively with Legacy Code》,中文翻译的文笔上绝对谈不上“艺术”二字。愧对艺术二字(当然译者不是这个意思大笑)。书中第三部分不论是样例还是讲解都有点混乱,远不如《重构——改善既有代码设计》一书。

    此书精华在于第一、二部分。

    怎样学习这本书,作为一个最底层的码农,作为长期在别人代码上修修补补的苦逼二手货开发者,我仅仅能给的建议就是:你能够将它看做是怎样做定制功能的指导书——从某种意义上讲,非常多时候引入測试,实际就是加入一个叫做“測试”的定制功能。并且。这样似乎也恰好印证了该书的中文名”改动代码的艺术”。

     

    其它的,我不想谈。也不懂。

    就这样。

    既然是要将这本书看做是怎样做定制功能的指导书,那么就先从本书第二部分“改动代码的技术”開始看。

    1. 减少改动的风险

    • 好的代码编辑工具。
      哪些把二进制数据也能玩得出神入化的大牛。我就不考虑了。对像我这种普通猿类,没能吃上ALZ112。更别提ALZ113了。智商有限。仅仅能用工具补拙了。吐槽一下我们公司居然基本都在用source insight,这个工具中文编码支持又不好,搜索和补全命令不强,每次都仅仅能呵呵。

    • 单一目标的编辑。
      这个在重构一书中也重复强调了。这里个人的体会是。老老实实的遵循吧,当习惯成自然了,进步的时机就到了。别以为学了高量和相对论,经典力学就能随便玩了。 

    2. 须要改动大量同样的代码

    对改动同样代码,我近乎偏执。原因或许就是源于以下两句作者的话:

    • 当你热情地消除代码中的反复时。就会吃惊地发现,设计会自己浮现出来。
    • 消除反复是锤炼设计的强大手段。它不仅能让设计变得更灵活,还能令代码改动更快更easy。

    这里补充一点的是同样的代码。不一定是全然同样的代码。有时出现全然同样的代码,仅仅是由于一种巧合;非常多时候,碰到最多的是结构和逻辑上相似的反复代码。

    3. 时间紧迫。必须改动

    管时间是否紧迫,作为一种自我保护的本能。一般改动时,尽可能将改动的代码集中到单独的类或者方法中,实现上尽可能的是类似一种开关性质的,能够简单的enable or disable。

    可是,假设时间充裕哭。应该在可測的情况下进行可能的重构,我自己的感受是有时这样的自我保护的本能太过强烈,有些时候会有些畸形,这样写出来的代码或许是最安全的,但不是最优雅的。就像非常多贪心算法,不总是最优,但往往还够得上"优"

    这里借用原书的样例讨论

    原始的代码,对列表entries中的每一个对象,依次运行postDate()对象,然后加入的transactionBundle的管理池中。

      public void postEntries(List<Entry> entries) {
            for (Iterator<Entry> it =entries.iterator(); it.hasNext(); ) {
                Entry entry = (Entry)it.next();
                entry.postDate();
            }
            transactionBundle.getListManager().add(entries);
    }


    如今有个新的需求,需求描写叙述是这种:(需求描写叙述实际是非常关键的,不同的描写叙述方式会不自觉的影响程序猿的实现方式)

    entries列表中不是全部的对象都要运行postDate()和加入进transactionBundle的管理池中。仅仅有还尚未在transactionBundle的管理池中的对象才须要运行postDate()操作,仅仅有那些运行了postDate()的entry对象,才须要加入到transactionBundle的管理池中。

    依据上面的需求描写叙述。假设你是那99.9%的人,一般就会这样实现:

    public voidpostEntries(List<Entry> entries) {
           // 记录哪些entries中哪些对象运行了postDate()
           List<Entry>entriesToAdd = newLinkedList<Entry>();
          
            for (Iterator<Entry> it =entries.iterator(); it.hasNext(); ) {
                Entry entry = (Entry)it.next();
                // 仅仅有那些不在transactionBundle管理池中的entry对象才须要运行postDate()
                if (!transactionBundle.getListManager().hasEntry(entry)) {
                    entry.postDate();
                    entriesToAdd.add(entry);
                }
            }
           
            // 将那些运行了postDate的entry对象加入到transactionBundle管理池中
            transactionBundle.getListManager().add(entriesToAdd);
        }


    无疑,这种修改非常具有侵入性,一旦出错,非常难定位是本身已有的缺陷还是修改造成的——仅仅有在深入理解代码的修改逻辑之后才干分析错误原因。这个不好。

    这个需求。本质上就是先找出那些还没有在管理池中的entry对象,然后运行postDate()和add()操作。因此这里实际能够应用“新生方法”手法,引入一个侵入性相当弱的改动。

        

    public voidpostEntries(List<Entry> entries) {
           // 先剔除那些已经在transactionBundle管理池中的entry对象
            List<Entry> entriesToAdd =uniqueEntries(entries);
           
            for (Iterator<Entry> it = entriesToAdd.iterator();it.hasNext(); ) {
                Entry entry = (Entry)it.next();
                entry.postDate();
            }
            transactionBundle.getListManager().add(entriesToAdd);
       }

    // 剔除那些已经在transactionBundle管理池中的entry对象
    private List<Entry> uniqueEntries(List<Entry> entries) {
    	// return entries; //假设出现错误,能够直接return。
    	// 新生方法的优点就是代码隔离。能够高速定位是改动引入的问题还是原始代码本身就有的bug
    		
    	List<Entry> result = new LinkedList<Entry>();
            for (Iterator<Entry> it = entries.iterator(); it.hasNext(); ) {
                Entry entry = (Entry)it.next();
                if (!transactionBundle.getListManager().hasEntry(entry)) {
                    result.add(entry);
                }
            }
            return result;
    }


    当然也能够引入外覆方法的手法。

    外覆方法的第一步总是重命名原有方法和引入外覆方法,外覆方法名就是原有方法名。

    这一步基本不会错。

    //rename "postEntries(List<Entry> entries)" aspostEntriesDirectly
    private voidpostEntriesDirectly(List<Entry> entries) {
            for (Iterator<Entry> it =entries.iterator(); it.hasNext(); ) {
                Entry entry = (Entry)it.next();
                entry.postDate();
            }
            transactionBundle.getListManager().add(entries);
    }
       
    // new wrapper method use signature "public voidpostEntries(List<Entry> entries)"
    public voidpostEntries(List<Entry> entries) {
           postEntriesDirectly(entries);
    }


    下一步,调整外覆方法的实现,这里基本与新生方法同样

    // new wrapper method use signature "public voidpostEntries(List<Entry> entries)"
        public voidpostEntries(List<Entry> entries) {
           // 先剔除那些已经在transactionBundle管理池中的entry对象
           List<Entry>entriesToAdd = uniqueEntries(entries);
          
           postEntriesDirectly(entriesToAdd);
        }


    假设习惯了思考使用弱侵入式的改动方式。后面两种方式会自然而然的得到。外覆方法与新生方法的差别是外覆方法保留了原有方法(仅仅是方法名做了改动)。
    假设有须要,还能够新生类和外覆类。原理都差点儿相同。

    最后啰嗦一下。假设一開始需求是这样描写叙述的:

    对entries列表中的Entry对象,首先要检查是否已经在管理池中。仅仅有不存在时才运行postDate()操作。并把它加入到管理池中。

    这样描写叙述后。要想到后面两种方法就会更自然一些。
    所以说需求描写叙述是非常关键。可是没人会为我们做这个,一切仅仅能靠自己。一切从需求分析開始。

  • 相关阅读:
    dp学习笔记1
    hdu 4474
    hdu 1158(很好的一道dp题)
    dp学习笔记3
    dp学习笔记2
    hdu 4520+hdu 4522+hdu 4524(3月24号Tencent)
    hdu 1025(最长非递减子序列的n*log(n)求法)
    hdu 2063+hdu 1083(最大匹配数)
    hdu 1023
    《 Elementary Methods in Number Theory 》Exercise 1.3.12
  • 原文地址:https://www.cnblogs.com/claireyuancy/p/7232085.html
Copyright © 2011-2022 走看看