关于程序员:重构改善既有代码的设计读书笔记

1次阅读

共计 7746 个字符,预计需要花费 20 分钟才能阅读完成。

《重构·改善既有代码的设计》读书笔记

一、重构准则

1、重构定义

重构:对软件内部结构的一种调整。目标是在 不扭转软件可察看行为 的前提下,进步其可了解性 升高其批改老本

2、为何重构

在开始说为何重构之前,先说一下很多程序员为何不喜爱重构。

  • 工夫紧,始终忙着实现性能,感觉重构影响效率,而且重构不算绩效,几乎吃力不讨好
  • 感觉代码写完之后可能当前都不是本人保护了,只顾过后疾速实现性能
  • 重构有危险,它必须批改运作中的程序,这可能引入一些不易觉察的谬误
  • 对重构没有系统性、全局性的意识,面对一堆烂代码,没有重构技巧的领导,只能想到哪改到哪
  • 接手他人写的代码时,代码曾经腐化到无可救药了,重构老本极高

是的,对过后做完性能的程序员来说不重构的确绝对畅快,然而有没有想过未来接手这块代码的人的感触?本人遇到烂代码时都常常吐槽之前的开发者,但本人有养成继续重构的习惯吗?难道你也想在未来被他人吐槽?是当初花几分钟重构的老本高,还是未来找一个 bug 花半天的工夫老本高?当当前新来一个需要时,是不重构的改变老本高?还是重构之后的改变老本高?所以心愿大家多平时理解一下重构相干常识,做一个有素养的工程师,这样大家都好。上面列举了几个重构的起因:

  • 重构改良软件设计
  • 重构使软件更容易了解
  • 重构帮忙找到 bug
  • 重构进步编程速度

我的项目在演进,代码不停地在堆砌。如果没有人为代码的品质负责任,代码总是会往越来越凌乱的方向演进。当凌乱到肯定水平之后,质变引起量变,我的项目的保护老本曾经高过从新开发一套新代码的老本,想要再去重构,曾经没有人能做到了。

每个软件模块都有三项职责。第一项职责是它运行时所实现的性能。第二项职责是它要应答变动,一个难以扭转的模块是有问题的,即使可能工作,也须要对它进行批改。第三项职责是要和读者进行沟通。——《麻利开发·纪念版》

3、何时重构

  • 增加性能时重构
  • 事不过三,三则重构(三次法令)
  • 修补谬误时重构
  • 复审代码时重构

以上四点被称为重构机会的“增删改查”,目标是想及时通过最小的致力就能对咱们的零碎进行扩大和批改。留神,重构是继续的,不要总想着“憋大招”,等代码烂到肯定水平再重构,有时候我的项目代码太多了,重构很难做得彻底,最初又搞进去一个“四不像的怪物”,这就更麻烦了!所以,寄希望于在代码烂到肯定水平之后,集中重构解决所有问题是不事实的,咱们必须摸索一条可继续、可演进的形式。

4、重构的难题

  • 重构数据库
  • 批改接口(特地是已公布的接口)
  • 难以通过重构手法实现的设计改变
  • 何时不该重构

    • 重构的代码太凌乱,还不如重写一个来的简略
    • 我的项目曾经靠近最初期限

5、重构与设计

重构与设计是互补的,重构能够带来更简略的设计,同时又不损失灵活性,这也升高了设计过程的难度,加重了设计的压力。

6、重构与性能

除了对性能有严格要求的实时零碎,其余任何状况下“编写疾速软件”的机密就是:首先写出可调的软件,而后调整它以求获取足够的速度。

除非某段代码是影响零碎性能瓶颈的代码,否则不要适度地为了性能优化而就义代码品质,这样优化的投入产出比并不高,减少了代码实现的难度、就义了代码的可读性,性能上的晋升却并不显著。

二、代码的坏滋味

1、Duplicated Code(反复代码)

坏滋味首当其冲的就是 Duplicated Code,如果你在一个以上的地点看到雷同的反复构造,那么这个坏滋味就能够确定了,设法将它们合而为一

  • 同一个类中两个或更多的函数含有雷同的表达式

    利用 Extract Method(提炼办法) 提炼反复代码,而后援用新提炼的函数

  • 互为兄弟的子类含有雷同的表达式

    利用 Extract Method(提炼办法) 提炼反复代码,而后 Pull Up Method(函数上移) 堆到超类

  • 互为兄弟的子类,含有局部雷同的表达式

    个别常见类似的两个函数以雷同的程序执行大抵的操作,然而各操作不齐全一样

    利用 Extract Method(提炼办法) 提炼反复代码,可能发现是能够使用Form Template Method(塑造模板函数)

  • 有些函数以不必的算法做雷同的事

    应用 Substitute Algorithm(替换算法) 将其余函数替换掉

  • 两个互不相干的类呈现 Duplicated Code

    利用Extract Class(提炼类) 将反复代码提炼到一个独立的类中, 而后援用新类。

2、Long Method(过长的函数)

很久以前程序员就曾经意识到程序越长越难了解,在晚期编程语言,调用子程序须要额定开销,所以不违心应用小函数。当初 OO 语言简直曾经齐全罢黜了过程内的调用动作开销

函数命名准则:每当感觉须要正文阐明点什么的时候,就能够把须要阐明的货色写进一个独立函数中,并以其 ” 用处 ” 命名, 哪怕函数名比实现还要长,要害是要阐明用处(而非实现手法)

  • 无局部变量

    利用 Extract Method(提炼函数) 提炼函数即可

  • 有局部变量

    如果发现局部变量是保留某一个表达式的运算后果,那么用 Replace Temp with Query(以查问取代长期变量) 使构造清晰后,再Extract Method(提炼函数),如果提炼了函数后发现新函数对参数赋值了,利用Remove Assignments to Parameters(移除对参数的赋值)

3、Large Class(过大的类)

如果想用单个类做太多事件,其内往往就呈现了太多实例变量, 一旦如此,duplicate code 也就要呈现了。当发现一个类中,并非所有时刻都应用所有实例变量,或者某个类中呈现多个变量有着雷同前缀或结尾, 构的动机就呈现了

  • 如果一个类中用于很多类似的字段,而且办法又都只是和某几个字段有关系,那么能够思考这些字段是不是应该属于另一个类Extract Class(提炼类)
  • 如果你发现类中的某些行为只被一部分实例用到,其它没有用到,能够尝试 Extract SubClass(提炼子类) 或者 Extract Class(提炼类),这两种的抉择就是委托和继承之间的抉择,Extract SubClass(提炼子类) 通常更容易,但它也有限度:一旦对象创立实现,你无奈再扭转对象行为。而委托更灵便一些(策略模式)。

4、Long Paramenter List(过长参数列)

太长的参数列难以了解,太多的参数会造成前后不统一,不易应用,而且一旦你须要更多的数据,就不得不批改它。如果将对象传递给函数,大多数批改都将没有必要,因为你很可能只须要在函数内减少一两条申请,就能失去更多数据

  • 如果向已有的对象收回一条申请,就能够取代一个参数,那么你应该激活手法 Replace Parameter with Method(以函数取代参数)。在这里 ” 已有对象 ” 可能是函数所属类内的一个字段也可能是另一个参数。
  • 你也能够使用 Preserve Whole Object(放弃对象残缺) 将来自同一个对象的一堆数据收集起来,并以该对象替换它们。如果某些数据不足正当的对象归属,可应用 Introduce Parameter Object 为它们制作出一个参数对象
  • 这里有一个重要的例外:有时候你显著不心愿造成 ” 被调用对象 ” 与 ” 较大对象 ” 间的某种依赖关系。这时候将参数句从对象中拆解进去独自作为参数,也很荒诞不经。然而请留神其所引发的代码。如果参数列太长或变动太频繁,你就须要重新考虑本人的依赖构造了。

5、Divergent Change(发散式变动)

如果某个类常常因为不同的起因在不同方向上发生变化,发散式变动就呈现了

  • 应用 Extract Class(提炼类) 把变动的职责提炼到新的类

你看着一个类说:” 呃,如果新退出一个数据库,我必须批改这三个函数;如果新呈现一种工具,我必须批改这四个函数。” 那么此时兴许将这个类分成两个会更好,这么一来每个类就能够只因为一种变动而须要批改。当然,往往只有在退出新数据库或者新金融工具后,你能力发现这一点。针对某一个外界变动的所有响应批改,都只应该产生在单一类中,而这个新类内的所有内容都应该发硬此变动。为此,你应该找出某特定起因而造成的所有变动,而后使用 Extract Class(提炼类)将他们提炼到另一个类中。

6、Shotgun Surgery(霰弹式批改)

如果每遇到某种变动,你都必须在许多不同的类作出小批改,你所面临的坏滋味就是 Shotgun Surgery,你如果须要批改代码有很多处,你岂但很难找到它们, 也很容易遗记某个重要的批改。

  • 这种状况下你应该应用 Move Method(搬移函数)Move Field(搬移字段)把所有须要批改的代码放进同一个类。如果眼下没有适合的类,就发明一个。如果将代码移动到同一个类,使原始类简直为空,请尝试通过 Inline Class 解脱这些当初多余的类。

发散式变动(Divergent Change) 是指 ” 一个类受多种变动的影响 ”,霰弹式批改(Shotgun Surgery)是指 ” 一种变动引入引发多个类的批改 ”。这两种状况下,你都会心愿整顿代码,使 ” 外界变动 ” 与 ” 须要批改的类 ” 趋于一一对应

7、Feature Envy(依恋情结)

函数对于某个类的趣味高过对本人所处类的趣味。

  • 把这个函数移至另一个地点,移到它该去的中央。Move Method(搬移函数)
  • 如果一个函数用到几个类的性能,则该判断哪个类领有最多被此函数应用的数据,而后就把这个函数和那些数据摆在一起。Extract Method(提炼函数)Move Method(搬移函数)

8、Data Clumps(数据泥团)

数据项就像小孩子,总喜爱成群结对地的待在一块,如果删掉泛滥数据中的一项,其余数据因此失去意义, 就应该为它们产生一个新对象

  • 如果一个类里有很多有关系的数据 Field, 那么就要思考为这些有关系的数据建设一个新家。Extract Class(提炼类) 创立数据新对象
  • 如果函数参数援用很多有关系的 Field, 那么就要思考让这些扩散的参数变成参数对象。Introduce Parameter Object(援用参数对象)
  • 如果一个函数的参数来自同一个对象的若干个属性,能够思考援用对象。因为如果被调用的的函数扭转参数,必须查找并批改这个函数的所有调用Preserve Whole Object(放弃对象残缺)

    第二点与第三点相似,只不过第二点须要新建一个类来申明字段,第三点不必

9、Primitive Obsession(根本类型偏执)

简略一句话就是只喜爱在原代码的根底上加根本类型字段,不喜爱提取对象

  • 能够使用 Replace Data Value with Object(以对象取代数据值) 将本来独自存在的数据值替换为对象。
  • 如果想要替换的数据值是类型码,而它不影响行为,则能够使用 Replace Type Code with Class(以类取代类型码) 将它换掉。
  • 如果你有与类型码相干的条件表达式能够使用 Replace Type Code with SubClasses(以子类取代类型码) 或者 Replace Type Code with State/Strategy(以 State/Strategy 取代类型码) 加以解决。
  • 如果你有一组总是被放在一起的字段,能够使用Extract Class(提炼类)
  • 如果在参数列中看到根本数据类型,无妨试试Introduce Parameter Object(援用参数对象)
  • 如果你发现自己正从数组中筛选数据, 能够使用 Replace Array with Object(以对象取代数组)

10、Switch Statements(Switch 惊悚现身)

你常会发现 switch 语句分布于不同地点。如果要为它增加一个新的 case 子句,就必须找到所有 switch 语句并批改它们。面向对象中的多态概念可为此带来优雅的解决办法。

  • 条件表达式,它依据类型的不同而抉择不同的行为。Replace Conditional with Polymorphism(以多态取代条件表达式)
  • 有个类型码,它会影响行为,但你无奈通过继承的形式打消它,或者类型码的数值在对象的生命周期中发生变化Replace Type Code with State/Strategy(以状态 / 策略取代类型码)
  • 如果繁多函数 有些抉择事例, 且不想改变它们, 那么多态就有点杀鸡用牛刀了。Replace Parameter with Explicit Methods(以明确函数取代参数)

11、Parallel Inheritance Hierarchies(平行继承体系)

平行继承体系其实是散弹式批改的非凡状况。这种状况下,每当你为某个类减少一个子类,必须也为另一个类相应减少一个子类。如果你发现某个继承体系的类名称前缀和另一个继承体系的类名称前缀完全相同,便是闻到了这种坏滋味

  • 让一个继承体系的实例援用另一个继承体系的实例,, 如果在使用 Move Method(搬移函数)Move Field(搬移字段)就能够将援用端的继承体系消除于有形

12、Lazy Class(冗赘类)

如果一个类的所得并不值其身价,他就应该隐没。我的项目中常常会呈现这样的状况:某个类本来对得起本人的价值,但重构使它身形缩水,不再做那么多工作;或开发者当时布局了某些变动,并增加一个类来应酬这些变动,但变动理论没有产生。

  • 如果某些子类没有做足够的工作,应用Collapse Hierarchy(折叠继承体系)
  • 对于简直没用的组件,应用Inline Class(将类内联化)

13、Speculative Generality(沉默寡言将来性)

如果这段代码以后用不到,就删掉它

  • 如果你的某个抽象类没有太大作用,请用Collapse Hierarchy(折叠继承体系)
  • 不必要的委托能够用 Inline Class(将类内联化) 除掉
  • 如果函数的某些参数未被用上,可对它施行Remove Parameter(移除参数)
  • 如果函数名带有多余的形象象征,应该对它施行Rename Method(函数改名)

14、Temporary Field(令人蛊惑的临时字段)

如果类中有一个简单算法,须要好几个变量,往往就可能导致坏滋味令人蛊惑的长期字段(Temporary Field)呈现。因为实现者不心愿传递一长串参数,所以他把这些参数都放进字段。然而这些字段只在应用该算法时才无效,其余状况下只会让人蛊惑。

  • 这时能够利用 Extract Class(提炼类)把这些变量和其相干函数提炼到一个独立的类中。

15、Message Chains(适度耦合的音讯链)

如果你看到用户向一个对象申请另一个对象, 而后在向后者申请另一个对象,而后在申请另一个对象相似: getPerson().getDepartment().getAddress().getStreet() 就是音讯链

  • 应用Hide Delegate(暗藏委托关系)。实践上讲能够重构音讯链上的任何一个对象,但这么做会把一系列对象都变成中间人(Middle Man)。
  • 先察看音讯链最终失去对象来干什么,看是否以 Extract Method(提炼函数) 把应用该对象的代码提炼到一个独立的函数中,再用 Move Method(搬移函数) 把这个函数推入音讯链

16、Middle Man(中间人)

对象的基本特征之一就是封装——对外部世界暗藏其外部细节. 封装往往随同着委托(delegate)。但人们可能适度使用委托。你兴许会看到某个类接口有一半的函数都委托给其它类,这样就是适度使用。

  • 应用 Remove Middle Man(移除中间人) 间接和真正的负责的对象打交道
  • 如果这样不干实事的函数只有少数几个使用,应用Inline Method(内联函数) 把他们放进调用端
  • 如果这些中间人 (Middle Man) 还有其它行为,能够使用 Replace Delegation with Inheritance(以继承取代委托) 把它变成实责对象的子类, 这样既能够扩大愿对象行为,又不用放心那么多委托动作。

17、Inappropriate Intimacy(狎昵关系)

有时候你会看到两个类过于密切,破费太多工夫去探索彼此的 private 成分。

  • 能够采纳 Move Method(搬移函数)Move Field(搬移字段)帮它们划清界限,从而缩小狎昵行径
  • 也能够使用 Change Bidirectional Association to Unidirectional(将双向关联改为单向关联) 让其中一个类对另一个斩断情丝
  • 如果两个类切实志同道合,能够应用 Extract Class(提炼类) 把两者共同点提炼到一个平安地点,让它们坦荡应用新类
  • 或者应用 Hide Delegate(暗藏委托关系) 让另一个类为它们传递相思情

18、Alternative Classes with Different Interfaces(殊途同归的类)

两个函数做同一件事却有不同的签名

  • 应用 Rename Method(函数改名) 重命名函数,而后重复应用 Move Method(搬移函数) 将某些行为移入类,直到两者的协定统一为止。如果必须反复而赘余地移入代码能力实现这些,或者可使用 Extract Superclass(提炼超类) 为你本人赎点罪

19、Incomplete Library Class(不完满的库类)

  • 如果想批改库类的一两个函数,能够使用 Introduce Foreign Method(引入外加函数)
  • 如果想加一大堆额定行为,使用Introduce Local Extension(引入本地扩大), 益处 ” 函数和数据被对立封装,使得其它类局部过分简单 ”

20、Data Class(纯稚的数据类)

纯稚的数据类是指:它们领有一些字段,以及用于拜访(读写)这些字段的函数,除此之外一无长物。这样的类只是一种不会谈话的数据容器,它们简直肯定被其它类过分细琐地操控着。

  • 应用 Encapsulate Field(封装字段) 将这些字段封装起来
  • 如果这些类中有应用容器类的字段,应用Encapsulate Collection(封装汇合)
  • 对于那些不该被其余类批改的字段,请使用Remove Setting Method(移除设值函数)

而后找到这些取值 / 设值函数的调用点。尝试以 Move Method(搬移函数) 把那些调用行为搬移到 Data Class。如果无奈搬移整个函数,能够采纳 Extract Method(提炼函数) 后搬移。不久之后就能够使用 Hide Method(暗藏函数) 把这些取值 / 设值函数暗藏起来

21、Refused Bequest(被回绝的遗赠)

子类不想继承父类某个函数或数据

  • 为这个子类新建一个兄弟类,再使用 Push Down Method(函数下移)Push Down Field(字段下移)把所有用不到的函数下推给那个兄弟。这样一来,超类就只持有所有子类共享的货色。
  • 应用 Replace Inheritance with Delegation(以委托取代继承) 手法重构

22、Comments(过多的正文)

不是说不该写正文,是因为人们常常把正文当做 ” 除臭剂 ” 应用。经常会有这种状况:你看到一段代码有着长长的正文,而后发现,这些正文之所以存在仍是因为代码很蹩脚。

  • 如果你须要正文来解释一块代码做了什么,试试Extract Method(提炼函数)
  • 如果函数曾经提炼进去,但还须要正文来解释其行为,试试Rename Method(函数改名)
  • 如果你须要正文阐明某些零碎的需要规格,试试Introduce Assertion(引入断言)
  • 如果你还不晓得该做什么,这才是引入正文的良好机会
正文完
 0