分享

4.kindle.读书笔记《重构:改善既有代码的设计》

 sjw0923cn 2018-08-08

任何一个傻瓜都能写出计算机可以理解的代码。唯有写出人类容易理解的代码,才是优秀的程序员。

记住,你的代码首先是为人写的,其次才是为计算机写的。

 

写在最开始

       为什么会想着读这本书?两个原因。第一,工作中对我自己写的代码不太放心,修改应该是早晚的事,那么先了解一些方法;第二,我更愿意读这类和编程语言没有太多关联的书(还有例如算法、设计模式等等),这样的话了解了思想就算以后不用java也能有所帮助,这样读起来感觉更划算。

       前前后后看这本书花了一个半月,毕竟拖延症晚期。正好在这一个半月中,工作上需要扩展一些功能,感觉就能够用上书中的一些知识,很开心,因为之前总说多看点书总是好的,你不知道哪天就能用上以前看到的知识,现在呢,这一步来得更快了。不过呢,感觉是用上了,但是有没有真的理解,以及真的是不是这么用的,就得再观察一下了。

 

        简单的概括整本书的一个结构:

        第一二章介绍了什么是重构;

        三四章大概说了重构前的一些准备(也可以理解为何时重构);

        第五章是整本书的一个格式上的约定(我是这么理解的,至少我觉得实际工作中记录重构不需要这样来做);

        从第六章开始到第十一章都是作者列出的一些具体重构方法,在我的这篇文字中我也尽量列出,更多的时候可以当作是“知识点”列出来,实际需要的时候再回来查阅细看;

        十二章的大型重构,也是总结了运用之前的各个重构方法完成大型项目的一些例子;

        十三章我觉得是一个引申出来的章节,现实中怎么去推广使用这种方法(这书已经有些年头了,不知道这章的内容在现在是否还有实际意义);

        十四章介绍了重构的工具,至今我也没有使用过,不多说;

        十五章总结;最后还列出了本书的要点,这点让我感觉很好。

 


 

第1章 重构,第一个案例


难以修改,可能会有两个原因,一是历史遗留下来的问题,系统已经多年岁月的冲刷,当年设计的架构已经很难支持现在的需求变更,二是当时确实没有考虑到;在稳定运行的系统中加入非必要的功能我是会有抵触的,加入新功能就面临着引入新bug的风险,但是呢很多时候并不是抵触就能不做。

 

看到这一段的时候笑了,看样子大部分的客户都一样,大部分的程序员也都遇到过同样的问题。

 

以下是第一章例子中,作者提到的一些建议:

  • 任何不会被修改的变量都可以被我当成参数传入新的函数,至于会被修改的变量就需格外小心。如果只有一个变量会被修改,我可以把它当作返回值。

 

  • 临时变量往往引发问题,它们会导致大量参数被传来传去,而其实完全没有这种必要。你很容易跟丢它们,尤其在长长的函数之中更是如此。

 

  • 最好不要在另一个对象的属性基础上运用switch语句。如果不得不使用,也应该在对象自己的数据上使用,而不是在别人的数据上使用。

 

第2章 重构原则


什么是重构:

  • 重构(名词):对软件内部结构的一种调整,目的是在不改变软件可观察行为的前提下,提高其可理解性,降低其修改成本。

  • 重构(动词):使用一系列重构手法,在不改变软件可观察行为的前提下,调整其结构。

 

为什么重构:

  • 当人们只为短期目的,或是在完全理解整体设计之前,就贸然修改代码,程序将逐渐失去自己的结构,程序员越来越难通过阅读源码而理解原来的设计。


 什么时候重构:

  • 第一次做某件事时只管去做;第二次做类似的事会产生反感,但无论如何还是可以去做;第三次再做类似的事,你就应该重构。事不过三,三则重构。

 

第3章 代码的坏味道 


这一章的内容是比较有用的,列举了很多容易造成问题的“迹象”,可以借鉴本章节的内容,对照着看自己写出的代码是否有当中列举的,是不是能在写的时候就规避这些“坏味道”。

 

  • 3.1 Duplicated Code(重复代码)

        可以说重复代码是最常见的一种情况了。就像最近的一个项目,客户和我们对整个项目都是一边摸索一边前行,就会出现一些需求和原来的差不多,仅仅是参数、或者查询的条件稍微有些不同,但是在最开始的时候并不知道会有类似的需求,而且也不知道后续会不会还有修改,不断的重复的代码就越来越多。



  • 3.2 Long Method(过长函数)

    • 每当感觉需要以注释来说明点什么的时候,我们就把需要说明的东西写进一个独立函数中,并以其用途(而非实现手法)命名。

  • 3.3 Large Class(过大的类)

  • 3.4 Long Parameter List(过长参数列)

        这个在工作中也见到一些,一个函数参数过多的后果可能会难以理解,还有就是如果需要添加新的参数会变得更加困难。书中介绍了一种方法解决这个问题,用传入一个对象来替代参数列(后文中应该还会提到),将需要的参数全部放到这个对象中,这是一个很好的解决方法。我在实际开发中用Map代替了对象,感觉更方便一些,但是因为类型不确定的关系总感觉会很危险,这还要花时间来观察和验证。

 

  • 3.5 Divergent Change(发散式变化)

  • 3.6 Shotgun Surgery(霰弹式修改)

  • 3.7 Feature Envy(依恋情结)

  • 3.8 Data Clumps(数据泥团)

  • 3.9 Primitive Obsession(基本类型偏执)

  • 3.10 Switch Statementsswitch惊悚现身)

        这是不太理解的一点,因为时间工作中我也用了很多的switch语句。书中提到使用面向对象的多态来替换switch,每一个case就用一个新的子类来替代。我还是觉得还是需要根据实际情况来看这个问题,也许我一个case分支中就三五句代码,为此新建一个类似乎也不太值得。这个问题继续关注吧。

 

  • 3.11 Parallel InheritanceHierarchies(平行继承体系)

  • 3.12 Lazy Class(冗赘类)

  • 3.13 Speculative Generality(夸夸其谈未来性)

  • 3.14 Temporary Field(令人迷惑的暂时字段)

  • 3.15 Message Chains(过度耦合的消息链)

  • 3.16 Middle Man(中间人)

  • 3.17 Inappropriate Intimacy(狎昵关系)

  • 3.18 Alternative Classes with DifferentInterfaces(异曲同工的类)

  • 3.19 Incomplete Library Class(不完美的库类)

  • 3.20 Data Class(纯稚的数据类)

  • 3.21 Refused Bequest(被拒绝的遗赠)

  • 3.22 Comments(过多的注释)

        关于注释这点也是很有意思的。我们总是讨厌其他人不写注释和文档,但是自己也不喜欢写注释与文档。书中给出了一种观点,你经常看到一段代码有着长长的注释,然后发现,这些注释之所以存在乃是因为代码很糟糕。因此,可以从注释中找到上述提到的“坏味道”,然后去除“坏味道”,最后使注释变得多余。当然这一切并不是说不写注释!

 

第4章 构筑测试体系


测试的重要性啦,无论何时,测试都是很重要的了,自测是必须的,至少把自己能想到的情形先自己测试一遍。而且我认为测试和经验是有些关系的,踩过的坑多了就知道哪些地方可能会有坑,然后就能稍微有针对的去进行测试。

还有我想提的一点是,越是简单的改动越要测试,往往出错的地方都是测试的时候觉得根本不会有问题的地方(毕竟觉得有问题的地方都会花精力去测试)。

 

第5章 重构列表


约定了一个之后使用的介绍重构方法的格式:

名称:……

简短概要:……

动机:……

做法:……

范例:……

 

第6章 重新组织函数

  • 6.1 Extract Method(提炼函数)

        有一段代码可以被组织在一起并独立出来,将这段代码放进一个独立函数中,并让函数名称解释该函数的用途。

这应该也是编程过程最常用的一个方法,不管是不是刻意的去重构。将重复代码和过长的函数进行提炼。

 

  • 6.2 Inline Method(内联函数)

  • 6.3 Inline Temp(内联临时变量)

  • 6.4 Replace Temp with Query(以查询取代临时变量)

    • 临时变量的问题在于:它们是暂时的,而且只能在所属函数内使用。由于临时变量只在所属函数内可见,所以它们会驱使你写出更长的函数,因为只有这样你才能访问到需要的临时变量。如果把临时变量替换为一个查询,那么同一个类中的所有函数都将可以获得这份信息。这将带给你极大帮助,使你能够为这个类编写更清晰的代码。

  • 6.5 Introduce Explaining Variable(引入解释性变量)

  • 6.6 Split Temporary Variable(分解临时变量)

  • 6.7 Remove Assignments to Parameters(移除对参数的赋值)

  • 6.8 Replace Method with Method Object(以函数对象取代函数)

 

第7章 在对象之间搬移特性


 面向对象编程的一个比较麻烦的问题——一件事到底该由哪个类来完成,后续工作中可以运用下列的重构方法修改最开始的设计。


  • 7.1 Move Method(搬移函数)

    • “搬移函数”是重构理论的支柱。如果一个类有太多的行为,或如果一个类与另一个类有太多合作而形成高度耦合,我就会搬移函数。通过这种手段,可以使系统中的类更简单,这些类最终也将更干净利落地实现系统交付的任务。

  • 7.2 Move Field(搬移字段)

    • 在你的程序中,某个字段被其所驻类之外的另一个类更多地用到。在目标类新建一个字段,修改源字段所有用户,令他们改用新字段。

  • 7.3 Extract Class(提炼类)

    • 某个类做了应该由两个类做的事。建立一个新类,将相关的字段和函数从旧类搬移到新类。

  • 7.4 Inline Class(将类内联化)

  • 7.5 Hide Delegate(隐藏“委托关系”)

  • 7.6 Remove Middle Man(移除中间人)

  • 7.7 Introduce Foreign Method(引入外加函数)

  • 7.8 Introduce Local Extension(引入本地扩展

 

第8章 重新组织数据


  • 8.1 Self Encapsulate Field(自封装字段)

        简单的说,就是给你的字段设置get和set方法,并且使用get、set方法去访问和修改字段。

 

  • 8.2 Replace Data Value with Object(以对象取代数据值)

  • 8.3 Change Value to Reference(将值对象改为引用对象)

  • 8.4 Change Reference to Value(将引用对象改为值对象)

  • 8.5 Replace Array with Object(以对象取代数组)

        其实我们的程序都是这样使用的,把用”|”分割的字符串分解成数组,然后根据不同位置的元素放到对象对应的属性当中。

 

  • 8.6 Duplicate Observed Data(复制“被监视数据”)

  • 8.7 Change Unidirectional Association toBidirectional(将单向关联改为双向关联)

  • 8.8 Change Bidirectional Association toUnidirectional(将双向关联改为单向关联)

  • 8.9 Replace Magic Number with SymbolicConstant(以字面常量取代魔法数)

        这个地方学习了,我们就常常会有代表各个状态的常量数字,代码中看到一个个数字用于判断状态是很容易懵的,建立一个常量,起个看得懂的名字,对理解程序还是会有很大帮助的。

        Static final int CONSTANT_XXXTYPE = 1 ;

 

  • 8.10 Encapsulate Field(封装字段)

        将字段声明为private,提供相应的访问函数。

 

  • 8.11 Encapsulate Collection(封装集合)

    •  有个函数返回一个集合。让这个函数返回该集合的一个只读副本,并在这个类中提供添加/移除集合元素的函数。

 

  • 8.12 Replace Record with Data Class(以数据类取代记录)

  • 8.13 Replace Type Code with Class(以类取代类型码)

  • 8.14 Replace Type Code with Subclasses(以子类取代类型码)

  • 8.15 Replace Type Code with State/Strategy(以State/Strategy取代类型码)

  • 8.16 Replace Subclass with Fields(以字段取代子类)

 

第9章 简化条件表达式


  • 9.1 Decompose Conditional(分解条件表达式)

        就是将if/then/else三个段落中分别提炼出独立函数。这样代码将会更清晰。

 

  • 9.2 Consolidate Conditional Expression(合并条件表达式)

  • 9.3 Consolidate Duplicate Conditional Fragments(合并重复的条件片段)

  • 9.4 Remove Control Flag(移除控制标记)

  • 9.5 Replace Nested Conditional with GuardClauses(以卫语句取代嵌套条件表达式)

    • 现今的编程语言都会强制保证每个函数只有一个入口,至于“单一出口”规则,其实不是那么有用。在我看来,保持代码清晰才是最关键的:如果单一出口能使这个函数更清楚易读,那么就使用单一出口;否则就不必这么做。

 

  • 9.6 Replace Conditional with Polymorphism(以多态取代条件表达式)

  • 9.7 Introduce Null Object(引入Null对象)

  • 9.8 Introduce Assertion(引入断言)

        有关于断言,几乎没有在程序中使用过,有一种说法是,断言只应该出现在测试当中,因为不满足断言条件的在正常情况中都不允许出现。

 

第10章 简化函数调用

  • 10.1 Rename Method(函数改名)

    • 给函数命名有一个好办法:首先考虑应该给这个函数写上一句怎样的注释,然后想办法将注释变成函数名称。

        关于函数名称,我们的程序中有一个是最让我崩溃的,两个函数,函数名称相同、(最开始的时候)返回值也相同,只有参数列表不同,乍一看觉得两个函数就是一样的,更崩溃的是,其中一个函数当中还引用了另一个!简直红红火火恍恍惚惚…代码大概意思是下面这样,我一定要去改了它!

Map sameName(String a , String b,int c){

       doSomething();

       ……

       Mapmap = sameName(a , c);

       doSomething();

       ……

       return otherMap;

}

Map sameName(String a ,int b){

       doSomething();

       ……

       return map;

}


  • 10.2 Add Parameter(添加参数)

  • 10.3 Remove Parameter(移除参数)

  • 10.4 Separate Query from Modifier(将查询函数和修改函数分离)

  • 10.5 Parameterize Method(令函数携带参数)

  • 10.6 Replace Parameter with ExplicitMethods(以明确函数取代参数)

  • 10.7 Preserve Whole Object(保持对象完整)

    • 你从某个对象中取出若干值,将他们作为某一次函数调用时的参数。改为传递整个对象。



  • 10.8 Replace Parameter with Methods(以函数取代参数)

    • 如果函数可以通过其他途径获得参数值,那么它就不应该通过参数取得该值。过长的参数列会增加程序阅读者的理解难度,因此我们应该尽可能缩短参数列的长度。



  • 10.9 Introduce Parameter Object(引入参数对象)

        这就是最开始在过长的参数列中提到的。运用一个对象包装所有需要的参数数据进行传递。该项重构的价值便在于缩短参数列。


  • 10.10 Remove Setting Method(移除设值函数)

  • 10.11 Hide Method(隐藏函数)

  • 10.12 Replace Constructor with FactoryMethod(以工厂函数取代构造函数)

        看到这一节的时候,我决定接下去我要看的书是《设计模式》.


  • 10.13 Encapsulate Downcast(封装向下转型)

        返回值的类型需要强制转换的时候,在函数return的时候进行类型强制转换,从而确定返回值的类型,而不是在接收到返回值之后再进行转换。


  • 10.14 Replace Error Code with Exception(以异常取代错误码)

        书中的意思是,我们常常会用返回“-1”类似的代码表示运行出错,此类情况用异常直接代替。


  • 10.15 Replace Exception with Test(以测试取代异常)

 

第11章 处理概括关系


        这一章主要用来处理继承相关的问题。主要也就是为了确定字段、方法到底应该放在子类还是超类当中,以及多个行为相似的类是否能提炼出超类等等。

 

  • 11.1 Pull Up Field(字段上移)

  • 11.2 Pull Up Method(函数上移)

  • 11.3 Pull Up Constructor Body(构造函数本体上移)

  • 11.4 Push Down Method(函数下移)

  • 11.5 Push Down Field(字段下移)

  • 11.6 Extract Subclass(提炼子类)

  • 11.7 Extract Superclass(提炼超类)

  • 11.8 Extract Interface(提炼接口)

  • 11.9 Collapse Hierarchy(折叠继承体系)

  • 11.10 Form Tem Plate Method(塑造模板函数)

  • 11.11 Replace Inheritance with Delegation(以委托取代继承)

  • 11.12 Replace Delegation with Inheritance(以继承取代委托)

 

第12章 大型重构


第13章 重构,复用与现实


实际情况中我们很少会有时间来重新审视自己原先的代码,我们总是在不断往既有代码中不停地加入新代码,以完成客户提出的新需求。

拒绝重构的原因有很多,我认为更关键的一点就是,我们的程序现在能够稳定运行,如果此时重构则可能会对程序造成破坏,这是有风险的。

 

第14章 重构工具 


本书出版的时候感觉支持重构的工具应该很少而且是不完善的,但是这么些年过去了,应该也有了很多的发展,有时间可以找一些来试试。


    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多