读书报告之《修改代码的艺术》 (I)

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

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

其他的,我不想谈,也不懂。就这样。

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

1. 降低修改的风险

  • 好的代码编辑工具。

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

  • 单一目标的编辑。

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

2. 需要修改大量相同的代码

对修改相同代码,我近乎偏执,原因也许就是源于下面两句作者的话:

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

这里补充一点的是相同的代码,不一定是完全相同的代码。有时出现完全相同的代码,只是因为一种巧合;很多时候,碰到最多的是结构和逻辑上相似的重复代码。

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()操作,并把它添加到管理池中。

这样描述后,要想到后面两种方法就会更自然一些。

所以说需求描述是很关键。但是没人会为我们做这个,一切只能靠自己。一切从需求分析开始。

时间: 2024-08-08 18:19:09

读书报告之《修改代码的艺术》 (I)的相关文章

读书报告之《改动代码的艺术》 (I)

<改动代码的艺术>,英文名<Working Effectively with Legacy Code>,中文翻译的文笔上绝对谈不上"艺术"二字.愧对艺术二字(当然译者不是这个意思).书中第三部分不论是样例还是讲解都有点混乱,远不如<重构--改善既有代码设计>一书. 此书精华在于第一.二部分. 怎样学习这本书,作为一个最底层的码农,作为长期在别人代码上修修补补的苦逼二手货开发者,我仅仅能给的建议就是:你能够将它看做是怎样做定制功能的指导书--从某种意义

读书报告之《修改代码的艺术》 (II)续2

这里作为(II)的第二个续篇,继续复杂的嵌套if else 的处理. 为了保持篇幅不会太长,以一篇新的文章形式给出. 化简复杂的if else语句,基本的手段 针对头重脚轻的if else,使用return快速返回,从而减少嵌套层数. 合并分支.有些分支的执行内容相同,往往意味着可以合并为一个分支 扁平化. 这里给出最后一个举例,也是从网上随便搜索摘录的 原始代码 List<TWorkFlowwork> wfwList=errorProcessingService.findWorkFlowwo

《编写可读代码的艺术》读书笔记

表面内容 1.代码的写法应当是别人理解他所需的时间最小化.一条注释可以让你更快理解代码.尽管减少代码行数是一个好目标,但是八里街代码所需的时间最小化是一个更好的目标. 2.选择专业的词,比如函数名使用getxxx(),这个get没有表达出很多信息,是从缓存中得到?从数据库中得到?或者从网络得到?如果是网络,可以用更专业的fetchxxx()或者downloadxxx() 3.tmp,retval这样泛泛的名字,可以根据情况命名,比如tmpFile,让人知道变量是一个临时的文件.(tmp这个名字只

编写可读代码的艺术笔记

编写可读代码的艺术 表面层次上的改进 命名.注释以及审美--可以用于代码库每一行的小提示. 简化循环和逻辑 在程序中定义循环.逻辑和变量,从而使得代码更容易理解. 重新组织你的代码 在更高层次上组织大的代码块以及在功能层次上解决问题的方法. 精选话题 把"易于理解"的思想应用于测试以及大数据结构代码的例子. 第1章:代码应当易于理解 1.代码应当易于理解. 2.代码的写法应当使别人理解它所需的时间最小化. 第一部分:表面层次的改进 第2章:把信息装到名字里 1.使用专业的单词. 2.避

《编写可读代码的艺术》——简单总结

上个月好像冥冥中自有安排一样,我在图书馆看到这本 <编写可读代码的艺术> ( The Art of Readable Code) 期间因为工作的原因,停停看看,这几天终于看完了,可以大概总结如下: 1. 把信息装进名字里,给变量起个好名字 2. 审美,把代码分成段落,对齐 3. 应当取个好名字,而不是用注释去粉饰它 4. 用注释记录你的思想,比如当时为什么要这样写,记录开发过程中有哪些思考 5. 将自己代码中的不足和瑕疵记录下来,方便今后别人的维护,不要顾忌别人的看法! 6. 注释应该言简意赅

《编写可读代码的艺术》---变量和可读性

对变量的草率使用,会导致程序的难以理解,原因是以下几点 变量越多,就越难以全部跟踪他们的动向 变量的作用域越大,就需要跟踪它的动向越久 变量改变的越频繁,就越难以跟踪它的当前值. 下面来讨论如何改善这些问题. 1 减少变量 仅当我们需要的时候,才使用变量,下面将列举出一些没必要存在的变量的. 1.1 没有价值的临时变量 一般有经验的程序员是不会刻意写个没有价值的临时变量.造成临时变量没有使用价值的原因,可能是多次修修改改之后遗留的结果. 来个日期赋值的例子 DateTime now = Date

读《软件驱魔》调试和优化遗留代码的艺术

读<软件驱魔> 调试和优化遗留代码的艺术 软件维护方法论的书,其间还有作者的感悟,读起来情深意切啊 此书中文版,第一版是2014年5月 内容给人感觉作者早已成书多年了.但软件知识还是有不过时的东西. 软件发展到现在,在我们身边,已经可以发生着许多书中的故事. 如大量的历史代码无人维护或者是开发人员不可寻且没有文档,没有流程图等等. 在这种情况下,作者指点读者去如何做更有益. 用了许多方法,并科学的介绍了不只一种方法. 收益良多. 还介绍了可能会出现的人员交流的问题.这种问题我在工作中也是常遇到

读书报告之《修改代码的艺术》 (II)

今天继续从本书的第二章学习, 昨天我们已经总结了下面三个内容 1. 降低修改的风险 2. 需要修改大量相同的代码 3. 时间紧迫,必须修改 今天继续第四点 4. 修改时应当测试哪些方法 作者提出了影响结构图的概念.说穿了,就是CallRelation和ReferenceRelation,就是查看某个方法(变量)被哪些方法引用,以及自身又引用了哪些方法,依次类推.这个复杂的关系网实际就是一颗风险评估树(图).通过这棵树,我们可以知道某个修改会影响到哪些节点.这项参数,既是风险的直接量化指标,同时又

编写可读性代码的艺术

在做IT的公司里,尤其是软件开发部门,一般不会要求工程师衣着正式.在我工作过的一些环境相对宽松的公司里,很多程序员的衣着连得体都算不上(搞笑的T恤.短裤.拖鞋或者干脆不穿鞋).我想,我本人也在这个行列里面.虽然我现在改行做软件开发方面的咨询工作,但还是改不了这副德性.衣着体面的其中一个积极方面是它体现了对周围人的尊重,以及对所从事工作的尊重.比如,那些研究市场的人要表现出对客户的尊重.而大多数程序员基本上每天主要的工作就是和其他程序员打交道.那么这说明程序员之间就不用互相尊重吗?而且也不用尊重自