重构:改善既有代码的设计(第二版读书笔记) - 重构、坏代码、写好代码

偶然发现重构这本书推出了js版,果断入手,名书之一,尤为仍是js版本,相较于java版来讲,确定更适合前端阅读,购买来自当当。html

本书做者 马丁·福勒,主要著做有:分析模式---可重用对象模型、Kent Beck. 规划极限编程、 UML精粹---标准对象建模语言简明指南(第三版)、 企业应用架构模式以及本书。本书内容以各类代码的“坏味道”,来推动合适的重构手法,和初版内容相比,有一些部分是更新了(那些被淘汰的代码、不合适的例子)。但主体思想仍是没有变,总而言之是一本值得读的好书前端

重构,改善既有代码设计(第二版)java

下面来分享本文章核心内容,读书笔记。程序员

读书中感悟最深的名言

  1. 原文:重构技术就是以微小的步伐修改程序。若是你犯下错误,很容易即可发现它。es6

    感悟:细碎的步子前进,能够使得咱们避免bug,在实际中,咱们应当是以稍大的步骤来改进,当遇到问题时,撤销咱们的更改 转而走向细碎的步子推动算法

  2. 原文:重构前,先检查本身是否拥有一套可靠的测试集,这些测试必须有自我检视能力。编程

    感悟:和鲍勃大叔在代码整洁之道(clean code)中的观点一致,先编写测试,才能再开发。重构亦如此json

  3. 原文:一些重构手法也会显著地影响性能。但即使如此,我一般也不去管它,继续重构,由于有了一份结构良好的代码,回头调优其性能也容易得多设计模式

    感悟:不要由于性能问题而不敢重构,一份好的代码再去调优是很容易的,更况且在如今各类缓存、压缩、浏览器的优化等加持下,真正影响性能的每每只是咱们项目中的某一小块代码数组

  4. 原文:每当感受须要以注释来讲明点什么的时候,咱们就把须要说明的东西写进一个独立函数中,并以其用途(而非实现手法)命名。咱们能够对一组甚至短短一行代码作这件事。哪怕替换后的函数调用动做比函数自身还长,只要函数名称可以解释其用途,咱们也该绝不犹豫地那么作。关键不在于函数的长度,而在于函数“作什么”和“如何作”之间的语义距离。

    感悟:一个最好的程序就是不须要任何注释,本身自己就已经说明了程序的运转流程,这和在初入行时,老师让咱们多写注释,最好每一行都写的不太同样,但也不算老师或者马丁大叔说错了,初入行时,咱们什么都不懂,常常编写匪夷所思的代码,因此注释很必要,可是更好的注释是咱们自身的代码。这个在不少代码规范中或者一些优秀的代码也都是这么作的。

tips:好的代码中,注释应该是短小精悍的,应该只是咱们须要描述一些很是规作法的说明,或者隐患的说明以及咱们某些时候冒出的一些对程序有用但没有作的想法

  1. 原文:事实上,撰写测试代码的最好时机是在开始动手编码以前。当我须要添加特性时,我会先编写相应的测试代码。听起来离经叛道,其实否则。编写测试代码其实就是在问本身:为了添加这个功能,我须要实现些什么?编写测试代码还能帮我把注意力集中于接口而非实现(这永远是一件好事)。预先写好的测试代码也为个人工做安上一个明确的结束标志:一旦测试代码正常运行,工做就能够结束了

    感悟:编写测试代码的最佳时机是在开始编写以前,将业务最终效果编为测试代码,有利于咱们明白咱们为何要作这个功能,须要实现什么样的东西。在做者看来,不管是新增功能、修改功能、bug fix都应该测试先行。这是一种正确的思路,也是咱们如今不少程序员中所缺乏的。大部分人作的都是先写功能,在写测试。甚至一些公司连测试代码都没有

注: 本书经典语句比较多,只挑选了其中我以为感悟最深入的五点写出

两顶帽子的概念

Kent Beck提出了“两顶帽子”的比喻。使用重构技术开发软件时,我把本身的时间分配给两种大相径庭的行为:添加新功能和重构。添加新功能时,我不该该修改既有代码,只管添加新功能。经过添加测试并让测试正常运行,我能够衡量本身的工做进度。重构时我就不能再添加功能,只管调整代码的结构。此时我不该该添加任何测试(除非发现有先前遗漏的东西),只在绝对必要(用以处理接口变化)时才修改测试软件开发过程当中,我可能会发现本身常常变换帽子。首先我会尝试添加新功能,而后会意识到:若是把程序结构改一下,功能的添加会容易得多,因而我换一顶帽子,作一下子重构工做。程序结构调整好后,我又换上原先的帽子,继续添加新功能。新功能正常工做后,我又发现本身的编码形成程序难以理解,因而又换上重构帽子……整个过程或许只花10分钟,但不管什么时候我都清楚本身戴的是哪一顶帽子,而且明白不一样的帽子对编程状态提出的不一样要求。

  • [x] 时刻牢记本身正在作什么,不要混在一块儿。这也是我本身所欠缺的东西

读书内容详解

重构的定义

  • [x] 所谓重构(refactoring)是这样一个过程:在不改变代码外在行为的前提下,对代码作出修改,以改进程序的内部结构。重构是一种经千锤百炼造成的有条不紊的程序整理方法,能够最大限度地减少整理过程当中引入错误的几率。本质上说,重构就是在代码写好以后改进它的设计。

应该重构的缘由

  1. 需求变化

    需求的变化使重构变得必要。若是一段代码能正常工做,而且不会再被修改,那么彻底能够不去重构它。能改进之固然很好,但若没人须要去理解它,它就不会真正妨碍什么。

  2. 新需求(预备性重构)

    重构的最佳时机在添加新功能以前,在动手添加新功能以前,我会看看现有的代码库,此时常常会发现:若是对代码结构作一点微调,工做会变的容易不少(旧代码重构来扩展新功能)

  3. 帮助理解的重构

    我须要先理解代码在作什么,而后才能着手修改。这段代码多是我写的,也多是别人写的。一旦我须要思考“这段代码到底在作什么”,我就会自问:能不能重构这段代码,令其一目了然?我可能看见了一段结构糟糕的条件逻辑,也可能但愿复用一个函数,但花费了几分钟才弄懂它到底在作什么,由于它的函数命名实在是太糟糕了。这些都是重构的机会。

  4. 捡垃圾式重构

    当我在重构过程当中或者开发过程当中,发现某一块很差,若是很容易修改能够顺手修改,但若是很麻烦,我又有紧急事情时候,能够选择记录下来(但不表明我就一点都作不到把他变好)。就像野营者的老话:至少让营地比你到达时更干净,长此以往,营地就很是干净(来自营地法则)

  5. 见机行事的重构

    重构常常发生在咱们平常开发中,随手可改的地方。当咱们发现很差的味道,就要将他重构

  6. 长期的重构

    能够在一个团队内,达成共识。当你们遇到时候,就改正它例如,若是想替换一个正在使用的库,能够先引入一层新的抽象,使其兼容新旧两个库的接口,而后一旦调用方彻底改成了使用这层抽象,替换下面的库就会如容易的多

  7. 复审代码(code review)时的重构

    开发者与审查者保持持续沟通,使得审查者可以深刻了解逻辑,使得开发者能充分认同复审者的修改意见(结对编程)

不知道什么时候该重构,那就遵循三次法则(来自书中)

Don Roberts给了我一条准则:第一次作某件事时只管去作;第二次作相似的事会产生反感,但不管如何仍是能够去作;第三次再作相似的事,你就应该重构正如老话说的:事不过三,三则重构

重构的意义

  1. 改进软件的设计(也能够说是增长程序的健壮、耐久)

    经过投入精力改善内部设计,咱们增长了软件的耐久性,从而能够更长时间地保持开发的快速

  2. 使得代码更容易理解

  3. 找到潜在bug

  4. 提升编程速度

重构的挑战

  1. 延缓新功能开发

    实际上,这只是一部分不理解重构真正缘由的人的想法,重构是为了从长效上见到收益,一段优秀的代码能让咱们开发起来更顺手,要权衡好重构与新功能的时机,好比一段不多使用的代码。就不必对他重构

  2. 代码全部权

    有时候咱们常常会遇到,接口发布者与调用者不是同一我的,而且甚至多是用户与咱们团队的区别,在这种状况下,须要使用函数更名手法,重构新函数,而且保留旧的对外接口来调用新函数,而且标记为不推荐使用。

  3. 分支的差别

    常常会有长期不合并的分支,一旦存在时间过长,合并的可能性就越低,尤为是在重构时候,咱们常常要对一些东西进行更名和变化,因此最好仍是尽量短的进行合并,这就要求咱们尽量的将功能颗粒化,若是遇到还没开发完成且又没法细化的功能,咱们能够使用特性开关对其隐藏

  4. 缺少一组自测试的代码

    一组好的测试代码对重构颇有意义,它能让咱们快速发现错误,虽然实现比较复杂,但他颇有意义

  5. 遗留代码

    不可避免,一组别人的代码使得咱们很烦恼,若是是一套没有合理测试的代码则使得咱们更加苦恼。这种状况下,咱们须要增长测试,能够运用重构手法找到程序的接缝,再接缝处增长测试,虽然这可能有风险,但这是前进所必需要冒的风险,同时不建议一气呵成的把整个都改完,更倾向于可以逐步地推动

什么时候不该该重构

  1. 不须要修改的代码
  2. 隐藏在一个API之下,只有当我须要理解其工做原理时,对其进行重构才有价值
  3. 重写比重构还容易

重构与其余的关系

  1. 开发:短时间会耽误必定的开发事件,但从长期来看,重构使得新功能会更容易开发
  2. 性能:会影响部分性能,但在大多数的加持下,显得微不足道,而且重构有利于性能优化的点集中于某一处或者几处
  3. 架构:相辅相成
  4. 需求:需求推进重构前进

代码的坏味道

本书之中的核心之一:简单来讲就是碰到什么样子的代码,你就须要警戒起来,须要进行重构了!

本文章中主要分红三部分进行描述,第一部分为名字就是它的术语,第二部分为详解:它的描述及一些实际场景,第三部分重构:就是他的参考重构手法,但这些手法仅做为参考,有时咱们可能会须要更多的手法

  1. 神秘命名

    详解:也包含那些随意的abc或者汉语拼音,总之一切咱们看不懂的、烂的都算,好的命名能节省咱们很大的时间

    重构:改变函数声明、变量更名、字段更名

  2. 重复代码

    详解:天然这个就好理解了,只要是咱们看到两段类似的语法均可以肯定为这段代码能够提炼,一般提炼出来会更好,固然这个要看具体状况,我的感受真的遇到那种只有两处,且代码使用地方八杆子打不着,在代码稳按期间也不用浪费这个时间(这个时间不止体如今改动过程,也包括你可能由于改动致使的隐藏bug,尤为是系统核心模块,一旦出现问题只能立刻回滚,不会给你时间去找问题

    重构:提炼函数、移动语句、函数上移等手法

  3. 过长的函数

    描述:短小才是精悍!好比一些条件分支、一个函数作了不少事情、循环内的处理等等的都是应该重构的

    重构:提炼函数(经常使用)、以查询取代临时变量、引入参数对象、保持对象完整性、以命令取代参数(消除一些参数)、分解条件表达式、以多态取代条件表达式(应对分支语句)、拆分循环(应对一个循环作了不少事情)

  4. 过长的参数列表

    描述:正常来讲,函数中所需的东西应该以参数形式传入,避免全局变量的使用,但过长的参数列表其实也很恶心。

    重构:查询取代参数、保持对象完整、引入参数对象、移除标记参数、函数组合成类

  5. 全局数据

    描述:最多见的就是全局变量,但类变量与单例模式也有这样的问题,咱们一般没法保证项目启动后不被修改,这就很容易形成诡异的bug,而且很难追查到

    重构:封装变量

  6. 可变数据

    描述:数据的可变性和全局变量同样,若是我其余使用者修改了这个值,而引起不可理喻的bug。 这是很难排查的。

    重构:封装变量,拆分变量,移动语句、提炼函数,查询函数和修改函数分离,移除设值函数,以查询取代变量函数组合成类

  7. 发散式变化

    描述:发散式变化是指某个模块常常由于不一样的缘由在不一样的方向上变化了(能够理解为某一处修改了,形成其余模块方向错乱)

    重构:拆分阶段、搬移函数、提炼函数、提炼类

  8. 霰弹式修改

    描述:和发散式变化接近,却又相反。咱们每次修改一个功能或者新增一个功能都须要对多处进行修改;而且随着功能增多咱们可能还须要修改更多。 这样程序时是很不健康的,其实我我的理解为:霰弹用来描述发散式变化更好,想一想霰弹是一个点发射出去变成不少。而本条应该用另外一个词来描述更好,但我还想不到叫什么词。或许叫多路并进?仅限我的观点,每一个人理解可能不同,建议以做者为准

    重构:搬移函数、搬移字段、函数组合成类、函数组合成变换、拆分阶段、内联函数、内联字段

  9. 依恋情结

    描述:一个模块内的一部分频繁的和外面的模块进行交互沟通,甚至超过了它与内部的沟通。也就是违反了高内聚低耦合,遇到这种的“叛乱者”,不如就让他去他想去的地方吧

    重构:搬移函数、提炼函数

  10. 数据泥团

    描述:杂合缠绕在一块儿的。代码中也如是,咱们可能常常看到三四个相同的数据,两个类中相同字段等等。总之像泥同样,这里也是这样那里也是这样,就是他了

    重构:提炼类、引入参数对象、保持对象完整性

  11. 基本类型偏执

    描述:一些基本类型没法表示一个数据的真实意义,例如电话号码、温度等,

    重构:以对象取代基本类型、以子类取代类型码、以多态取代条件表达式

  12. 重复的switch

    描述:不仅是switch,大片相联的if也应该包含在内,甚至在古老的前端时代,曾经一度无条件反对这样的写法。

    重构:多态取代条件表达式

  13. 循环语句

    描述:在js中体现为传统的for类循环

    重构:用管道来取代循环(管道:map、forEach、reduce、filter等一系列)

  14. 冗赘的元素

    描述:元素指类和函数,可是这些元素可能由于种种缘由,致使函数过于小,致使没有什么做用,以及那些重复的,均可以算做冗赘

    重构:内联函数、内联类、折叠继承类

  15. 夸夸其谈通用性

    描述:为了未来某种需求而实现的某些特殊的处理,但其实可能致使程序难以维护难以理解,直白来讲就是没个锤子用的玩意,你留下他干个屁

    重构:折叠继承体系、内联函数、内联类、改变函数声明、移除死代码

  16. 临时字段

    描述:那些自己就足以说明本身是谁的,不须要名字来描述的

    重构:提炼类、提炼函数、引入特例

  17. 过长的消息链

    描述:一个对象请求另外一个对象,而后再向后者请求另外一个对象,而后再请求另外一个对象……这就是消息链,举个例子来讲

    new Car().properties.bodyWork.material.composition().start()

    这意味着在查找过程当中,不少的类耦合在一块儿。我的认为,不只是结构的耦合,也很难理解。这也包含某类人jq的那一大串的连续调用。都是很难让人理解的。

    重构: 隐藏委托关系、提炼函数、搬移函数

  18. 中间人

    描述:若是一个类有大部分的接口(函数)委托给了同一个调用类。当过分运用这种封装就是一种代码的坏味道

    重构:移除中间人、内联函数

  19. 内幕交易

    描述:两个模块的数据频繁的私下交换数据(能够理解为在程序的鲜为人知的角落),这样会致使两个模块耦合严重,而且数据交换隐藏在内部,不易被察觉

    重构:搬移函数、隐藏委托关系、委托取代子类、委托取代超类

  20. 过大的类

    描述:单个类作了过多的事情,其内部每每会出现太多字段,一旦如此,重复代码也就接踵而至。这也意味着这个类毫不只是在为一个行为负责

    重构:提炼超类、以子类取代类型码

  21. 殊途同归的类

    描述:两个能够相互替换的类,只有当接口一致才可能被替换

    重构:改变函数声明、搬移函数、提炼超类

  22. 纯数据类

    描述:拥有一些字段以及用于读写的函数,除此以外一无可取的类,通常这样的类每每半必定被其余类频繁的调用(若是是不可修改字段的类,不在此列,不可修改的字段无需封装,直接经过字段取值便可),这样的类每每是咱们没有把调用的行为封装进来,将行为封装进来这种状况就能获得很大改善。

    重构:封装记录、移除取值函数、搬移函数、提炼函数、拆分阶段

  23. 被拒绝的遗赠

    描述:这种味道比较奇怪,说的是继承中,子类不想或不须要继承某一些接口,咱们能够用函数下移或者字段下移来解决,但不值得每次都这么作,只有当子类复用了超类的行为却又不肯意支持超类的接口时候咱们才应该作出重构

    重构:委托取代子类、委托取代超类

  24. 注释

    描述:这里提到注释并不是是说注释是一种坏味道,只是有一些人常常将注释看成“除臭剂”来使用(一段很长的代码+一个很长的注释,来帮助解释)。每每遇到这种状况,就意味着:咱们须要重构了

    重构:提炼函数、改变函数声明、引入断言

重构手法介绍

若是说上面的味道是核心的话,那手法应该就是本书的重中之重。一般咱们发现哪里味道不对以后,就要选择使用不一样的手法进行重构。将他们变得味道好起来。

本文中每一个手法一般包含三个模块:时机(遇到什么状况下使用)、作法(详细步骤的归纳)、关键字(作法的缩影)

提炼函数

  • 时机:
  1. 当咱们以为一段大函数内某一部分代码在作的事情是同一件事,而且自成体系,不与其余掺杂时
  2. 当代码展现的意图和真正想作的事情不是同一件时候,如做者提到的例子。想要高亮,代码意思为反色,这样就不容易让人误解,印证了做者前面说的:当你须要写一行注释时候,就适合重构了
  • 作法:
  1. 一个以他要作什么事情来命名的函数
  2. 待提炼代码复制到这个函数
  3. 检查这个函数内的代码的做用域、变量
  4. 编译查看函数内有没有报错(js能够经过eslint协助)
  5. 替换源函数的被提炼代码替换为函数调用
  6. 测试
  7. 替换其余代码中是否有与被提炼的代码相同或类似之处
  • 关键字:

    新函数、拷贝、检查、做用域/上下文、编译、替换、修改细节

内联函数

  • 时机:

    1. 函数内代码直观表达的意思与函数名字相同
    2. 有一堆杂乱无章的代码须要重构,能够先内联函数,再经过提炼函数合理重构
    3. 非多态性函数(函数属于一个类,而这个类被继承)
  • 作法:

    1. 检查多态性(若是该函数属于某个超类,而且它具备多态性,那么就没法内联)
    2. 找到全部调用点
    3. 将函数全部调用点替换为函数本体(非一次性替换,能够分批次替换、适应新家、测试)
    4. 删掉该函数的定义(也可能会不删除,好比咱们放弃了有一些函数调用,由于重构为渐进式,非一次性)
  • 关键字:

    检查多态、找调用并替换、删除定义

提炼变量

  • 时机:
  1. 一段又臭又长的表达式
  2. 在多处地方使用这个值(多是当前函数、当前类乃至于更大的如全局做用域)
  • 作法:
  1. 确保要提炼的表达式,对其余地方没有影响
  2. 声明一个不可修改的变量,并用表达式做为该变量的值
  3. 用新变量取代原来的表达式
  4. 测试
  5. 交替使用三、4
  • 关键字:

反作用、不可修改的变量、赋值、替换

内联变量

  • 时机:
  1. 变量没有比当前表达式有什么更好的释义
  2. 变量妨碍了重构附近代码
  • 作法:
  1. 检查确认变量赋值的右侧表达式不对其余地方形成影响
  2. 确认是否为只读,若是没有声明只读,则要先让他只读,并测试
  3. 找到使用变量的地方,直接改成右侧表达式
  4. 测试
  5. 交替使用三、4
  • 关键字

反作用、只读、替换变量

改变函数声明

最好能把大的修改拆成小的步骤,因此若是你既想修改函数名,又想添加参数最好分红两步来作。
不论什么时候,若是遇到了麻烦,请撤销修改,并改用迁移式作法)
  • 时机:
  1. 函数名字不够贴切函数所作的事情
  2. 函数参数增长
  3. 函数参数减小
  4. 函数参数概念发生变化
  5. 函数由于某个参数致使的函数应用范围小(全局有不少相似的函数,在作着相似的事情)
  • 作法(适用于肯定了函数或者参数只在有限的小范围内使用,而且仅仅更名)
  1. 先肯定函数体内有没有使用这个参数(针对于参数)
  2. 肯定函数调用者(针对于函数)
  3. 修改函数/参数的声明,使其达到咱们想要的效果
  4. 找到全部的函数/参数声明的地方将其更名
  5. 找到全部函数/参数调用的地方将其替换
  • 关键字

使用变量者、函数调用者、修改函数、声明更名、调用替换

  • 作法(标准化作法)
  1. 对函数内部进行重构(若是有必要的话)
  2. 使用提炼函数手法,将函数体提炼成一个新函数,同名的话,能够改成一个暂时的易于搜索的随意名字(如:aaa_getData,只要好搜索且惟一便可。),非同名的话,使用咱们想要的名字做为新函数名字
  3. 在新函数内作咱们的变动(新增参数、删除参数、改变参数释义等)
  4. 改变函数调用的地方(若是是新增、修改、删除参数)
  5. 测试
  6. 对旧函数使用内联函数来调用或返回新函数
  7. 若是使用了临时名字,使用改变函数声明将其改回原来的名字(这时候就要删除旧函数了)
  8. 测试
  • 关键字:

内部重构、提炼新函数、好搜索的临时名字、变动、改变调用、旧函数使用新函数、改变调用名字

封装变量

  • 时机:
  1. 当咱们在修改或者增长使用可变数据的时候
  2. 数据被大范围使用(设置值)
  3. 对象、数组无外部变更须要内部一块儿改变的需求时候,最好返回一份副本
  • 作法:
  1. 建立封装函数(包含访问和更新函数)
  2. 修改获取这个变量和更新这个变量的地方
  3. 测试
  4. 控制变量外部不可见(能够借助es6类中的get来实现不可变量以及限制可见)
  5. 测试
  • 关键字:

新函数、替换调用、不可见

变量更名

  • 时机:
  1. 变量/常量的名字不足以说明字段的意义
  2. 垃圾命名
  • 作法:
  1. 针对普遍使用的

    1.1 先用封装变量手法封装

    1.2 找到全部使用该变量的代码,修改测试(若是是对外已发布的变量,能够标记为不建议使用(做者没提到,可是我的感受是能够这样的)

    1.3 测试

  2. 只做用于某个函数的直接替换便可

  3. 替换过程当中能够以新名字做为过渡。待所有替换完毕再删除旧的名字

  • 关键字:

封装变量手法、替换名字、中间过渡

引入参数对象

  • 时机:
  1. 一组参数总在一块儿出现
  2. 函数参数过多
  • 作法:
  1. 建立一个合适的数据结构(若是已经有了,能够略过)

    数据结构选择:一种是以对象的形式,一种是以类的形式,做者推荐以类的形式,可是在我看来,要根据场景,若是这组数据以及其相关行为能够变为一组方法,如数组类里面的比较两个数组是否彻底一致,这就能够以类来声明(js中也能够以export来导出而使用)

  2. 使用改变函数声明手法给原函数增长一个参数为咱们新的结构

  3. 测试

  4. 旧数据中的参数传到新数据结构(变动调用方)

  5. 删除一项旧参数,并将之使用替换为新参数结构

  6. 测试

  7. 重复五、6

  • 关键字:

新结构、增长参数、入参新结构、删除旧参数、使用新结构

函数组合成类

  • 时机:
  1. 一组函数(行为)老是围绕一组数据作事情
  2. 客户端有许多基于基础数据计算派生数据的需求
  3. 一组函数能够自成一个派系,而放在其余地方老是显得不够完美
  • 作法:
  1. 若是这一组数据还未作封装,则使用引入参数对象手法对其封装
  2. 运用封装记录手法将数据记录封装成数据类
  3. 使用搬移函数手法将已有的函数加入类(若是遇到参数为新类的成员,则一并替换为使用新类的成员)
  4. 替换客户端的调用
  5. 将处理数据记录的逻辑运用提炼函数手法提炼出来,并转为不可变的计算数据
  • 关键字:

提炼变量、封装成类、移入已有函数、替换调用、移入计算数据

函数组合成变换

  • 时机:
  1. 函数组合成变换手法时机等同于组合成类的手法,区别在于其余地方是否须要对源数据作更新操做。 若是须要更新则使用类,不须要则使用变换,js中推荐类的方式
  • 作法:
  1. 声明一个变换函数(工厂函数)
  2. 参数为须要作变换的数据(须要deepclone)
  3. 计算逻辑移入变换函数内(比较复杂的能够使用提炼函数手法作个过渡)
  4. 测试
  5. 重复三、4
  • 关键字:

变换函数、变换入参、搬移计算逻辑

封装记录

  • 时机:
  1. 可变的记录型结构
  2. 一条记录上有多少字段不够直观
  3. 有须要对记录进行控制的需求(我的理解为须要控制权限、须要控制是否只读等状况)
  4. 须要对结构内字段进行隐藏
  • 作法:
  1. 首先用封装变量手法将记录转化为函数(旧的值的函数)
  2. 声明一个新的类以及获取他的函数
  3. 找到记录的使用点,在类内声明设置方法
  4. 替换设置值的方法(es6 set)
  5. 声明一个取值方法,并替换全部取值的地方
  6. 测试
  7. 删除旧的函数
  8. 当咱们须要更名时,能够保留老的,标记为不建议使用,并声明新的名字进行返回
  • 关键字:

转化函数、取值函数、设值函数、替换调用者、替换设置者

以对象取代基本类型

  • 时机:
  1. 随着开发迭代,咱们一个简单的值已经不只仅只是简单的值那么简单了,他可能还要肩负一些其余的职责,如比较、值行为等
  2. 一些关键的、非仅仅只有打印的功能的值
  • 作法:
  1. 若是没被封装,先使用封装变量手法
  2. 为要修改的数据值建立一个对象,并为他提供取值、设值函数(看需求)
  3. 使用者(多是另一个大类)修改其取值设值函数
  4. 测试
  5. 修改大类中的取值设值函数的名称,使其更好的语义化
  6. 为这个新类增长其行为(多是转换函数、比较函数、特殊处理函数、操做函数)等
  7. 根据实际需求对新类进行行为扩展(若是有必要的话)
  8. 修改外部客户端的使用
  • 关键字:

新类、取设值函数、行为入类、扩展类

以查询取代临时变量

  • 时机:
  1. 修改对象最好是一个类(这也是为何提倡class,由于类能够开辟一个命名空间,不至于有太多全局变量)
  2. 有不少函数都在将同一个值做为参数传递
  3. 分解过长的冗余函数
  4. 多个函数中重复编写计算逻辑,好比讲一个值进行转换(好几个函数内都须要这个转换函数)
  5. 若是这个值被屡次修改,应该将这些计算代码一并提炼到取值函数
  • 作法:
  1. 检查是否每次计算过程和结果都一致(不一致则放弃)
  2. 若是能改成只读,就改为只读
  3. 将变量赋值取值提炼成函数
  4. 测试
  5. 去掉临时变量
  • 关键字:

只读、提炼函数、删变量

提炼类

  • 时机:
  1. 一个大的类在处理多个不一样的事情(这个类不纯洁了)
  • 作法:
  1. 肯定分出去的部分要作什么事情
  2. 建立一个新的类,表示从旧地方分离出来的责任
  3. 旧类建立时,为新类初始化
  4. 使用搬移函数手法将须要的方法搬移到新的类(搬移函数时候就将调用地方更名)
  5. 删除多余的接口函数,并为新类的接口取一个适合本身的名字
  6. 考虑是否将新的类开放为公共类
  • 关键字:

职责边界确认、建立新域、新旧同步初始化、行为搬家、接口删除

内联类

  • 时机:
  1. 一个曾经有不少功能的类,在重构过程当中,已经变成一个毫无单独职责的类
  2. 须要对两个类从新进行职责划分
  • 作法:
  1. 将须要内联的类中的全部对外可调用函数(也多是字段)在目标类中新建一个对应的中间代理函数
  2. 修改调用者,调用代理方法并测试
  3. 将原函数中的相关方法(字段)搬移到新地方并测试
  4. 原类变为空壳后就能够删除了
  • 关键字:

代理、修改调用者、方法搬家、抛弃旧类

隐藏委托关系

  • 时机:
  1. 一个类须要隐藏其背后的类的方法或事件
  2. 一个客户端调用类的方法时候,必须知道隐藏在后面的委托关系才能调用
  • 作法:
  1. 在服务类(对外的类)中新建一个委托函数,让其调用受托类(背后的类)的相关方法
  2. 修改全部客户端调用为这个委托函数
  3. 重复12直到受托类所有被搬移完毕,移除服务类中返回受托类的函数
  • 关键字:

委托函数、替换调用者、删除委托整个类

移除中间人

  • 时机:
  1. 由于隐藏委托关系(当初多是比较适合隐藏的)手法形成的如今转发函数愈来愈多
  2. 过分的迪米特法则形成的转发函数愈来愈多
  • 作法:
  1. 在服务类(对外)内为受托对象(背后的类)建立一个返回整个委托对象的函数
  2. 客户端的调用转为连续的访问函数进行调用
  3. 删除本来的中间代理函数
  • 关键字:

委托整个类、修改调用、删除代理

替换算法

  • 时机:
  1. 旧算法已经不知足当前功能
  2. 有更好的方式能够完成与旧算法相同的事情(一般是由于优化)
  • 作法:
  1. 保证待替换的算法为单独的封装,不然先将其封装
  2. 准备好更好的算法,
  3. 替换算法过去
  4. 运行并测试新算法与旧算法对比(必定要对比,也许你选的还不如之前呢)
  • 关键字:

算法封装、编写新算法、替换算法、比较算法

搬移函数

  • 时机:
  1. 随着对项目(模块)的认知过程当中,也多是改造过程当中,一些函数已经脱离了当前模块的范围
  2. 一个模块内的一些函数频繁的与其余模块交互,却不多和自身内部进行交互(出现了叛变者)
  3. 一个函数在发展过程当中,如今他已经有了更通用的场景
  • 作法:
  1. 查找要搬移的函数在当前上下文中引用的全部元素(先将依赖最少的元素进行搬离)
  2. 考虑待搬移函数是否具备多态性(复写了超类的函数或者被子类重写)
  3. 复制函数到目标上下文,调整函数,适应新的上下文
  4. 函数内使用的变量考虑是一块儿搬移仍是以参数传递
  5. 改写原函数为代理函数(也能够内联)
  6. 检查新函数是否能够继续进行搬离
  • 关键字:

肯定关系、肯定继承、优先基础、函数搬家、相关部分位置肯定、原址代理、优化新函数

搬移字段

  • 时机:
  1. 随着业务推动过程当中,原有的数据结构已经不能很好的表示程序的逻辑
  2. 每当调用一个函数时,须要传入的记录参数,老是须要传入另外一条记录或者他的某些字段一块儿
  3. 修改(行为)一条记录时,老是须要同时改动其余记录
  4. 更新(数据)一条字段时,老是须要同时在多个结构中做出修改
  • 作法:
  1. 源字段已经被封装(若是未封装,则应该先使用封装变量手法对其封装)
  2. 目标对象上建立一个字段,及其访问函数
  3. 源对象对目标对象的字段作对应的代理
  4. 调整源对象的访问函数,令其使用目标对象的字段
  5. 测试
  6. 移除源对象的字段
  7. 视状况而定决定是否须要内联变量访问函数
  • 关键字:

封装、新字段、源址代理、代理新址、旧字段移除、肯定是否内联

搬移语句到函数

  • 时机:
  1. 重复代码
  2. 每次调用a方法时,b操做也老是每次都执行
  3. 某些语句放在特定函数内更像一个总体
  • 作法:
  1. 将重复代码使用搬移函数手法到紧邻目标函数的位置
  2. 若是目标函数紧被惟一一个原函数调用,则只须要将原函数的重复片断粘贴到目标函数便可
  3. 选择一个调用点进行提炼函数,将目标语句函数与语句提炼成一个新的函数
  4. 修改函数其余调用点,令他们调用新提炼的函数
  5. 调整函数的引用点
  6. 内联函数手法将目标函数内联到新函数里
  7. 移除原目标函数
  8. 对新函数应用函数更名手法(改变函数声明的简单作法)
  • 关键字:

代码靠近、单点提炼、中间函数、修改引用、函数内联、原函数删除、函数更名

函数搬移到调用者

  • 时机:
  1. 随着系统前进过程当中,函数某一块的做用发生改变,再也不适合原函数位置
  2. 以前在多个地方表现一致的行为,现在在不一样调用点面前表现了不一样的行为

tips: 本手法只适合边界有些许偏移的场景,不适合相差较大的场景

  • 作法:
  1. 简单状况下,直接剪切
  2. 将不想搬移的部分提炼成与当前函数同级函数(若是是超类方法,子类也要一块儿提炼)
  3. 原函数调用新的同级函数
  4. 替换调用点为新的同级函数和要内联的语句
  5. 删除原函数
  6. 使用函数更名手法(改变函数声明的简单作法)改回名字
  • 关键字:

提炼不变的为临时方法、搬移语句、删除原,更名字

以函数调用替换内联代码

  • 时机:
  1. 函数内作的某些事情与已有函数重复
  2. 已有函数与函数之间但愿同步变动
  • 作法:
  1. 内联代码替换为函数(可能有参数,就要对应传递)
  • 关键字:

内联替换

移动语句

  • 时机:
  1. 移动语句通常用于整合相关逻辑代码到一处,这是其余部分手法的基础
  2. 代码相关逻辑整合一处方便咱们对这部分代码优化和重构
  • 作法:
  1. 肯定要移动的语句要移动到哪(调整的目标是什么、该目标可否达到)
  2. 肯定要移动的语句是否搬移后会使得代码不能正常工做,若是是,则放弃
  • 关键字:

肯定反作用、肯定目标

拆分循环

  • 时机:
  1. 一个循环作了多件不相干事
  • 作法:
  1. 复制循环
  2. 若是有反作用则删除单个循环内的重复片断
  3. 提炼函数
  4. 优化内部
  • 关键字:

复制循环、行为拆分、函数提炼

以管道替代循环

  • 时机:
  1. 一组虽然在作相同事情的循环,可是内部过多的处理逻辑,使其晦涩难懂
  2. 不合适的管道(如过滤使用some)
  • 作法:
  1. 建立一个新变量,用来存放每次行为处理后,参与循环的剩余集合
  2. 选用合适的管道,将每一次循环的行为进行搬移
  3. 搬移完全部的循环行为,删除整个循环
  • 关键字:

新变量、合适的管道、删除整个循环

移除死代码

  • 时机:
  1. 代码随着迭代已经变得没用了。
  2. 即便这段代码未来颇有可能还会使用,那也应该移除,毕竟如今版本控制很实用。
  • 作法:
  1. 若是不能够外部引用,则放心删除(若是可能未来极有可能会启用,在这里留下一行注释,标示曾经有过这段代码,以及它被删除的那个提交的版本号)
    二、若是外部引用了,则须要仔细确认还有没有其余调用点(有eslint规则限制的话。其实能够先删了,看有没有报错)
  • 关键字:

检查引用

拆分变量

  • 时机:
  1. 一个变量被应用到两种/多种的做用下
  2. 修改输入参数的值
  • 作法:
  1. 在变量第一次赋值的地方,为函数取一个更加有意义的变量名(尽可能声明为const)
  2. 在第二次赋值地方声明该变量
  3. 以该变量第二次赋值动做为界,修改此前对该变量的全部引用。让他们引用新的变量
  4. 测试
  5. 重复上述,直到变量拆分完毕
  • 关键字:

新变量、赋值时声明、替换调用

字段更名

  • 时机:
  1. 记录结构中的字段须要改个名字
  • 作法:
  1. 若是结构简单,能够一次性替换
  2. 若是记录没有封装,最好是先封装记录
  3. 修改构造时候作兼容判断(老的值与新的值兼容判断:this.a = data.a || data.b)
  4. 修改内部设取值函数
  5. 修改记录数据类中的内部调用
  6. 测试
  7. 修改外部调用初始化时候的数据
  8. 删除初始化兼容判断
  9. 使用函数更名手法(改变函数声明的简单作法),修改调用处的调用方式及内部取设值函数为新字段名
  • 关键字:

封装、兼容初始化、内部取设只返回新字段,修改内部调用,测试、删除兼容、内部取设更名、替换外部调用

以查询取代派生变量

  • 时机:
  1. 两个变量相互耦合
  2. 设置一个变量的同时,将另外一个变量与该变量结合,经过计算后给另外一个变量设置值

tips:计算的参考变量,是不可变的,计算结果也是不可变的。能够不重构(仍是那句话,不可变的数据,咱们就不必理他)

  • 作法:
  1. 肯定能够引发变量发生变化的全部点(若是有来自其余模块变量,须要先用拆分变量手法
  2. 新建一个计算函数,计算变量值
  3. 引入断言(assert),确保计算函数的值与该变量结果相同
  4. 测试
  5. 修改读取变量的代码,用内联函数手法将计算函数内联进来)
  6. 移除死代码手法将旧的更新点的地方清理掉
  • 关键字:

来源肯定、结果相同、计算函数、清理更新点

将引用对象改成值对象

  • 时机:
  1. 几个对象中共享了一个对象,而且要联动变动的状况下
  2. 值对象就是每次设置都直接设置这个值,好比:
值对象:a.b=new b(1)
    引用对象:a.b.c=1
  • 作法:
  1. 检查重构的目标是否为不可变对象,若是不是的话,则看看是否能够将其改成不可变对象
  2. 移除设值函数手法去掉第一个设引用值函数(每次都用设置值的方式复写整个对象)
  3. 测试
  4. 重复二、3
  5. 判断两次相同输入时候,值是否相等
  • 关键字:

不可变、替换设置引用值为设置值

将值对象改成引用对象

  • 时机:
  1. 数据副本在多处使用,而且须要一处变化其余地方同步更新
  • 作法:
  1. 建立一个仓库(若是没有的话),仓库要支持:每次访问相同数据都是一个相同的引用对象、支持注册新数据和获取同一个引用数据(js能够在简单场景下简单的使用{})
  2. 确保仓库的构造函数有办法找到关联对象的正确实例
  3. 修改调用点,令其从仓库获取关联对象。
  4. 测试
  • 关键字:

共享仓库、单例的引用对象、替换调用点

分解条件表达式

  • 时机:
  1. 条件逻辑内,过长的函数,致使反而难以理解条件逻辑的场景
  2. 单个条件逻辑处理的函数过大
  • 作法:
  1. 对条件判断的每一个分支分别运用提炼函数手法
  2. 若是条件表达式过长,对条件表达式运用提炼函数手法
  3. 优化当前条件逻辑(如使用三元表达式)
  • 关键字:

提炼分支、提炼条件、优化判断

合并条件表达式

  • 时机:
  1. 无其余反作用的嵌套if
  2. 无其余反作用的,且返回一致的并列if
  3. 这些if都是关联的(能够用是否能提炼出一个合适的函数名来做为依据,但也不是绝对,咱们能够选择不提炼函数,可是仍是建议是相关的if做为一组)
  • 作法:
  1. 肯定条件表达式有反作用,先用将查询函数和修改函数分离的手法对其处理
    二、若是是嵌套函数通常是用逻辑与合并,若是是并列的if通常是用逻辑或合并,若是两种均有,就要组合使用了(可是我更建议他们应该分离成多个判断)
    三、测试
  2. 重复二、3
  3. 对合并后的条件表达式进行提炼函数手法(有必要的话)
  • 关键字:

分离反作用、合适的逻辑符、提炼条件函数

以卫语句取代嵌套表达式

  • 时机:
  1. 无其余反作用的嵌套if
  2. 无其余反作用的,且返回一致的并列if
  3. 这些if都是关联的(能够用是否能提炼出一个合适的函数名来做为依据,但也不是绝对,咱们能够选择不提炼函数,可是仍是建议是相关的if做为一组)
  • 作法:
  1. 选取最外层须要被替换的条件逻辑,将其替换为卫语句(单独检查条件、并在条件为真时马上返回的语句,叫作卫语句)
  2. 测试
  3. 重复一、2
  • 关键字:

从外而内

以多态取代条件表达式

  • 时机:
  1. 多种并列或者嵌套的条件逻辑,让人难以理解
  2. switch
  3. 同行为不一样类型的判断
  • 作法:
  1. 肯定现有的条件类是否具备多态性,若是没有,能够经过将行为封装成类(借助其余手法如函数组合成类等)

  2. 在调用方使用工厂函数得到行为对象的实例

  3. 针对不一样类型建立子类(至关于在超类在分化)

  4. 调用方此时应当经过一个工厂返回合适的子类

  5. 将超类中针对子类类型所作的判断,逐一移入对应子类进行复写(相关子类复写超类的分支函数),超类只留下默认值

注意:这种手法实际上是在面向对象开发中很经常使用的一种方式,可是若是不是

  1. 在写一个面向对象很明确的项目
  2. 这个判断过于大
  3. 能够明确这些子类抽取出来是有意义的(从后期维护角度来讲,须要对其增长一些行为)
  4. 这个子类能够自成体系

不如将其经过一个json或者map来进行指责划分。在js中我以为更经常使用的是以策略来代替if

  • 关键字:

多态、继承、封装、行为拆分

引入特例

  • 时机:
  1. 数据结构的调用者都在检查某个特殊值,而且这个值每次所作的处理也都相同

  2. 多处以一样方式应对同一个特殊值

三种状况
第一种原始为类,特例元素没有设置值的操做
第二种原始为类,特例元素有设置值的操做
第三种 原始就是普通的json

  • 作法:

针对于有本身对应行为的类

  1. 在原类中为特例元素增长一个函数,用以标记这个特例的状况,默认返回一个写死的就行)

  2. 为特例建立一个class,用以处理特例的正常逻辑和行为,须要把特例对象及其全部行为放到这个类

  3. 将本次特例的条件使用提炼函数手法抽成一个在类中的字段函数返回true

  4. 修改全部调用者为第3步的函数

  5. 修改第一步建立的类。让它返回咱们的特例对象

  6. 特例中的其余字段

针对于只读的类

  1. 将上面作法的建立一个b类改成在类内建立一个函数,返回对象便可。把特例所需信息所有返回在js

针对于原始不是类的

  1. 为特例对象建立一个函数,返回特例对象的深拷贝状态

  2. 将本次特例的条件使用提炼函数手法抽成一个统一的函数

  3. 对第一步建立的函数返回值作特殊加强。 将须要的特例的值,逐一放进来。

  4. 替换调用者使用函数的返回值

  • 关键字:

特例逻辑搬到class、过渡函数、替换调用者、修改新class

将查询函数和修改函数分离

  • 时机:
  1. 一个函数既有返回值又有设置值
  • 作法:
  1. 复制一份目标函数并更名为查找函数的名字
  2. 将被复制的函数删除设置值的代码
  3. 将调用者替换为新函数,并在下面调用原函数
  4. 删除原函数返回值
  5. 将原函数和新函数中的相同代码进行优化
  • 关键字:

新函数为查找、删除设置值、替换调用者、删除返回值、优化

函数参数化

  • 时机:
  1. 有多余一个函数的逻辑很是类似,只是有一些字面量不一样(有时候可能会碰到a、b很类似,a、c也很类似,可是b、c差距比较大时候,这种状况我的观点为:将ab、ac中逻辑紧密的抽成一个,不要形式化的就要吧abc抽到一块儿。反而拔苗助长)
  • 作法:
  1. 从这一组类似函数中,找到一组,一般来讲尽量选择调用比较少的地方
  2. 运用改变函数声明手法(改变参数)使其在调用时候,将变化的部分以参数形式传入)
  3. 修改当前这个函数的全部调用点,为调用新函数,并传递参数
  4. 修改新函数,让它使用新传进来的参数
  5. 将其余类似的函数,逐一替换为这个新函数,每次替换都要测试一下
  • 关键字:

调用较少、变化点入参、修改调用、替换使用

移除标记参数

  • 时机:
  1. 一个用来控制函数流程的参数
  • 作法:
  1. 针对参数的每一种可能值,新建一个明确函数(若是参数控制整个流程,则能够用分解条件表达式手法建立明确函数,若是只控制一部分函数则建立转发函数,将这些函数,统一经过这些明确函数进行转发)

  2. 替换调用者

tips:若是是这个标记即做为标记,又做为参数值。则对其进行拆分。

  • 关键字:

流程、行为拆分

保证对象完整的手法

  • 时机:
  1. 从一个代码中导出几个值
  2. 调用者将自身的部分参数传递
  3. 通常发生在引入参数对象手法以后
  • 作法:
  1. 新建一个空函数(多是新建,也多是用提炼函数),接受完整对象
  2. 新函数体内调用旧函数,而且使用合适的参数列表
  3. 修改旧函数的调用者,令他使用新函数,修改旧函数内部
  4. 使用内联函数手法将旧函数内代码搬移到新建的函数
    五、修改新函数的名字为旧函数
  • 关键字:

接受完整对象、新调用老、修改调用、内联、更名

以查询取代参数

  • 时机:
  1. 一个函数传入了多个相同的值(如老是能根据b参数不须要很复杂就能够查到a参数)
  2. 调用函数传入了一个函数自己就能够很容易得到的参数(指的是内部或者计算得到,而非从其余模块拿)
  3. 若是目标函数自己就具备引用透明性(函数的返回值只依赖于其输入值),用查询后,他去访问了一个全局变量,则不适合用本重构

一言以概之:这个函数自身或者经过参数都能获得另外一个值就能够使用这个手法

  • 作法:
  1. 若是有必要,能够将参数计算的过程提炼为一个只读变量或者一个函数
    二、将函数体内引用该参数的地方,都改成运用计算函数
    三、去掉该参数(调用者也要去掉)
  • 关键字:

提炼变量、参数消除

以参数取代查询

  • 时机:
  1. 一个函数内部由于引用了全局变量而致使了不透明
  2. 一个函数内部引用了一个即将被删除的元素
  3. 一个函数内部,过多的依赖了另外一个模块(这种有两种作法:一种是本手法,另外一种是搬移函数手法,要根据函数实际做用操做
  • 作法:
  1. 使用提炼变量手法将目标(但愿做为参数传入的查询)提炼出来
  2. 把整个函数体提炼,而且单独放到一个函数内(须要保留计算逻辑,计算逻辑做为代理函数每次的值以参数传入函数)
  3. 消除刚才提炼出来的变量(旧函数应该只剩下一个简单的调用)
  4. 修改调用方,改成调用新函数,并传入调用时候计算的计算值
  5. 删除原函数内的计算代理
  6. 新函数改回旧函数的名字(若是意义发生变化,须要从新起名字)
  • 关键字:

变量提炼、函数体换新、旧函数传参、旧函数调新函数,删除代理函数、函数更名

移除设值函数

  • 时机:
  1. 类内某个字段有一些设值函数
  2. 类无任何反作用(如:操做渲染html的append、往localstorage写东西、init调用接口、多处共享引用等)
  3. 很庞大的类(须要先做拆分优化)
  • 作法:
  1. 若是没法拿到设置变化的值,就经过构造函数的参数传入
  2. 在构造函数内部调用设值函数进行更新
  3. 移除全部的设置值的函数调用,改成new一个类
  4. 使用内联函数手法消除设值函数。

tips:能够批量操做多个设值函数。

  • 关键字:

设值替换为new

以工厂函数取代构造函数

  • 时机:
  1. 构造函数每次都须要new关键字,又臭又长(我的观点是这条不必,除非彻底忍受不了)
  2. 构造函数若是不是default导出的话,这个名字那就是固定的。有时候语义化不明显
  3. 有时虽然都是调用同一个类。但所处环境不一样,我调用意义就不一样
  • 作法:
  1. 新建一个工厂
  2. 工厂调用并返回现有的构造函数
  3. 替换调用者
  4. 尽量缩小构造函数可见范围(js中很难实现,可能只能藏的深一些)
  • 关键字:

工厂函数、调用类、替换调用

以命令取代函数手法

  • 时机:
  1. 在js中,体现为又臭又长的还无法进行指责划分的函数(多是它们都属于同一部分逻辑,也多是由于内部写法致使很差划分)
  • 作法:
  1. 新建一个空的类
  2. 搬移函数手法将函数搬移到这个新的类
  3. 给类改个有意义的名字,若是没什么好名字就给命令对象的实际具体执行的函数起一个通用的名字,如:execute或者call
  4. 将原函数做为转发函数,去构造类
  5. 将函数内的参数,改成构造时候传入
  6. 若是能够将其余字段修改成只读
  • 关键字:

新的类、函数搬家、原类转发函数、构造入参、只读

函数上移手法

  • 时机:
  1. 子类中有绝大部分都在复制某个函数
  2. 这些函数函数体都相同或者近似
  • 作法:
  1. 确保待提高函数的行为彻底一致,不然须要先将他们一致化
  2. 检查函数体内的全部调用和字段都能从超类中调用(若是有不一致则考虑先把它们提高)
  3. 检查函数名字所有一致,不一致的话先将他们名字统一
  4. 将函数复制到超类中
  5. 逐一移除子类中的函数。每一次都要测试
  • 关键字:

函数体一致化、名字一致化、引用调用先行、提高函数、删除重写

字段上移手法

  • 时机:
  1. 子类中有绝大部分都在复制某个字段
  • 作法:
  1. 检查该字段的全部使用点,确保是在一样的方式被使用
  2. 若是名字不一样,先把名字统一化
  3. 移动到父类,并确保子类都能访问父类的这个字段
  4. 逐一移除子类的该字段
  • 关键字:

一样方式使用、统一名字、字段上移、删除子类字段

构造函数本体上移

  • 时机:
  1. 子类中有绝大部分都在复制某个构造函数函数
  2. 这些构造函数函数体都相同或者近似
  • 作法:
  1. 若是超类没有构造函数,就先定义一个,全部子类增长super关键字
  2. 使用移动语句将子类的公共语句移动到super紧挨着以后
    三、提高到超类构造函数中
    四、逐一移除子类的公共代码,若是这个值来自于调用者,则从super上传给父类
    五、若是要上移的语句有基于子类的字段而设置初始化的值的,查看是否能够将这个字段上移,若是不能,则使用提炼函数语句,将这句提炼为一个函数,在构造函数内调用他
    六、函数上移
  • 关键字:

构造函数内的语句上移

函数下移、字段下移

  • 时机:
  1. 超类中的函数(字段)只与一部分子类有关(这个范围须要掌控好,我一般选择若是使用超过三分之二的,而且在剩余的三分之一里面,这个函数/字段没有反作用,就选择上移,不然下移)
  • 作法:
  1. 将超类中的函数(字段)本体逐一复制到每个须要此函数(字段)的子类中
  2. 删除超类中的函数(字段)
  • 关键字:

按需放置

以子类取代状态码

  • 时机:
  1. 一个类中有一些有必要的多态性被隐藏
  2. 根据某个状态码来返回不一样的行为
  • 作法:

直接继承超类的

  1. 将类型码字段进行封装,改成一个get type()的形式
  2. 选择其中一个类型码,为其建立一个本身类型的子类
  3. 建立一个选择器逻辑(根据类型,选择正确的子类)把类型码复制到新的子类
  4. 测试
  5. 逐一建立、添加选择逻辑的代码
  6. 移除构造函数的这个参数
  7. 将与类型相关的代码重构优化

间接继承(经过类型的超类而非现有超类进行继承)

  1. 用类型类包装类型码(以对象取代基本类型手法)
  2. 走直接继承超类的逻辑,惟一不一样的是,此次要继承类型超类,而非当前超类
  • 关键字:

封装类型码、多态化、选择子类的函数、移除类型参数

移除子类

  • 时机:
  1. 随着程序发展子类原有行为被搬离殆尽
  2. 本来是为了适应将来,而增长子类,可是如今放弃了这部分代码。
  3. 子类的用处太少,不值得保留
  • 作法:
  1. 检查子类的使用者,是否根据不一样子类进行处理
  2. 若是处理了则将处理函数封装为一个函数,并将他们搬移到父级
  3. 新建一个字段在超类,用以表明子类的类型
  4. 将选择哪一个类来实例化的构造函数搬移到超类
  5. 逐步搬移全部的类型
  6. 将本来的类型处理改成使用新建的字段进行判断处理
  7. 删除子类
  • 关键字:

工厂函数取代子、类型提炼、检查类型判断

提炼超类

  • 时机:
  1. 两个类在作相似的事情
  2. 两个类随着程序发展,有一些共同部分须要合并到一块儿
  • 作法:
  1. 新建超类(可能已经存在)
  2. 调整构造函数(从数据开始)
  3. 调整子类须要的字段
  4. 将多个子类内共同的行为复制到超类
  5. 检查客户端代码。考虑是否调整为超类
  • 关键字:

相同事情搬移到超类

以委托取代子类

  • 时机:
  1. 类只能继承一个,没法多继承
  2. 继承给类引入了紧密的关系(超类、子类耦合严重)
  • 作法:
  1. 使用以工厂函数取代构造函数将子类封装
  2. 建立一个委托类、接受全部子类的数据,若是用到了超类,则以一个参数指代超类
  3. 超类中增长一个安放委托类的字段
  4. 增长一个建立子类的工厂,让他初始化超类中的委托字段
  5. 将子类中的函数搬移至委托类,不要删除委托代码(若是用到了其余元素也要一并搬离)
  6. 若是这个函数被子类以外使用了,把留在子类的委托移动到超类中,并加上卫语句,检查委托对象初始化
  7. 若是没有其余调用者,使用移除死代码手法去掉没人使用的委托代码
  8. 测试
  9. 重复567。直到全部函数都搬到了委托类
  10. 找到调用子类的地方,将其改成使用超类的构造函数
  11. 去掉子类
  • 关键字:

工厂函数初始化类、委托类、全部子类数据搬移至委托类、超类增长委托类的字段、子类函数搬移到委托类、删除子类

以委托取代超类手法

  • 时机:
  1. 错误的继承(如父子不是同一个意义的东西,可是子还想要用超类的一些字段)
  2. 超类不是全部方法都适用于子类
  • 作法:
  1. 在子类中建立一个属性,指向新建的超类实例
  2. 子类中用到超类的函数,为他们建立转发函数(用上面的属性)
  3. 去除子类与超类的继承关系
  • 关键字:

子类属性指向超类、转发函数、去除继承

读后感:

收获最大的莫过于感叹做者过于谨慎,震惊于做者重构能力之强,对代码重构理解程度之深,虽然做者有一些“墨迹”,但不能否认,这是极佳的一种方式,虽然工做中咱们不得已没有那么多时间去这么小的步子,咱们能够步子稍微大一些,当遇到问题时,在回滚并放慢步子。

第二点收获就为做者对于代码好坏的定义,好的代码就是让人可以理解,可以让人很快的找到本身要修改的地方,并能够高效的规避报错风险。虽然前期投入时间可能会多一些,但后期的效果倒是让人可以惊讶的,正如做者所说的:清晰的代码更容易理解,使你可以发现更深层次的设计问题,从而造成积极正向的循环。

做者一直在强调,重构是一步一步改进的,不是说一会儿就要如何如何,不仅是说单次改进过程要小幅度多测试,也是在说咱们不必定要将代码中全部都实现到近乎完美的地步,而是应该抉择一个代码重构与真实状况的平衡点,这和大刀阔斧直谈架构重构的也不同,代码是在不断构筑-设计中保持本身的新鲜性,直谈大型架构重构,只能是笑谈,毕竟架构为设计、编写。而直接重构整个架构,除非你想被老板炒鱿鱼了。

最后本章做者使用了不少的手法,虽然都只是一些经常使用的什么提炼函数啊、内联变量啊之类的,但到处又透露着咱们须要学习的!

重构是为了代码能被人读懂(所谓什么更好扩展啊、更好的设计模式啊、结构化啊等等等等都是为了这点。因此我统归为为了能读懂)能够选择牺牲一些(不是说能够彻底忽略了性能),毕竟在现代浏览器、打包工具的加持、缓存的加持下,肉眼看到的问题以及咱们思考的问题也许已经被各类加持下悄悄消失了

从长久来看,重构对于往后的维护往后的开发,随着时间的流逝确定是一个正收益,可是短时间来讲可能要影响咱们一些,咱们要权衡好这些点之间的平衡,毕竟工做是为了赚钱,公司也是为了盈利,不可能给咱们无限时间去搞这些,做者同时也提出了重构并非工做日志中的某一个任务的时机,主要体如今:新功能开发时、为了代码可读性、代码整合、有计划的重构代码以及坚持长期的重构以及review的重构。能够说是随时随地均可以重构,但也不是任何地方任什么时候机均可以重构,咱们要利用好测试的套件,保证原效果的前提下,结合实际状况,多维度思考,即便阅读事后,也应该时常翻开这本书,进行反复阅读,以提醒本身。

在本书中我学到了如何甄别坏的代码,以及怎么处理他们,学到了开发中应该测试先行以及一些重构的基本知识。

不过做为jser,我我的以为虽然做为一本通用型书籍,确实应该不掺杂不少的语法,不过既然选定了js,自动化重构这块其实怎么说呢,写的都是IDE。可是js中并无这类工具。而后做者也没有说在js下应该能够借助某些功能来帮助重构。因此这块仍是一片空白,虽然这种事理应本身去研究。可是仍是想免费更好嘛

相关文章
相关标签/搜索