lq920320/blogs

2017/11/24--《代码大全》重构章节部分内容

Opened this issue · 0 comments

  1. 软件演化的类型
  2. 重构简介
  3. 特定的重构
  4. 安全的重构
  5. 重构策略

神话: 一个管理很完善的软件项目,应该首先以系统化的方法进行需求开发,定义一份严谨的列表来描述程序的功能。设计完全遵循需求,并且完成得相当仔细,这样就让程序员的代码编写工作能够从头至尾直线型地工作。这也表明绝大多数代码编写后就已完美,测试通过后即可被抛到脑后。如果这样的神话是真的,那么代码被修改的唯一时机就是在软件维护阶段,而这一阶段只会在系统的最初版本交付用户之后。

现实情况: 在初始开发阶段,代码会有实质性的进化。在初始代码编写过程中,就会出现很多剧烈的改变,如同在代码维护阶段可以看到的那样。根据项目的规模不同,典型的项目花在编码、调试和单元测试上的时间会占到整个项目的30%到65%不等。如果代码编写和单元测试能够一帆风顺,这两个阶段所占项目时间的比例不会超过20%到30%。即使是管理完善的项目,每个月都有大约1/4的需求发生变化。需求的变化将不可避免地导致相关代码的改变——有时是实质性的代码改变。

另一个事实: 现在的开发增强了代码在构造阶段中改变的潜力。在旧式的软件生命周期中,项目成功与否的关键在于能否避免代码的改变。越来越多的现代开发方法已经放弃了对代码的前瞻性。如今的开发方法更多地以代码为中心,在整个项目生命周期中代码都会不断地演化。你可以期望代码的演化比以往任何时候更频繁。简单来讲,在新式的软件生命周期中,项目成功与否的关键在于能否应对需求的剧变。

软件演化的类型

软件演化就像生物进化一样,有些突变对物种是有益的,另外一些则是有害的。良性的软件演化使得代码得到了发展,就如猴子进化到穴居人再进化到我们人类。然而,有时演化的力量也会以另一种方式打击你的程序,甚至将它送入不断退化的螺旋形轨道。
区分软件演化类型的关键,就是程序的质量在这一过程中是提高了还是降低了。
区分软件演化类型的第二个标准,就是这样的演化是源于程序构建过程中的修改,还是维护过程中的修改。构建中的修改通常是由最初的开发人员完成,在这一阶段程序还没有被人们彻底遗忘。这时系统也未上线待售,因此,完成修正压力仅仅是来自于时间表——绝不会有500个愤怒的用户质问你为什么他们的系统会崩溃。出于同样的原因,构建期间的修改常常是随心所欲之作——系统处于高度动态阶段,出现错误的代价较小。这样的环境孕育着与维护期不同的软件演化风格。
软件演化的基本准则就是,演化应当提升程序的内在质量。
再庞大复杂的代码都可以通过重构加以改善。 ——Gerald Weinberg

重构简介

要实现软件演化基本准则,最关键的策略就是重构,Martin Fowler 将其定义为“在不改变软件外部行为的前提下,对其内部结构进行改变,使之更容易理解并便于修改”(Fowler 1999)。在现代编程理论中,“重构(refactoring)”一词源于Larry Constantine 在结构化程序设计中首次使用的“factoring”。当时指尽可能地将一个程序分解为多个组成部分。

1. 重构的理由

有时,代码在维护过程中质量会降低,而有时代码在最初诞生的时候就先天不良。

代码重复 重复的代码几乎总是代表着对最初设计里彻底分解方面的一个失误。无论何时,如果需要对某个地方进行修改,你都不得不在另一个地方完成同样的修改——重复代码总会将你置于一种两线作战的尴尬境地。重复的代码同样违背了Andrew Hunt 和 Dave Thomas 所提出的“DRY原则”:不要重复自己“Don't Repeat Yourself”(Hunt and Thomas 2000)。我想还是David Parnas说得最为精辟:“复制粘贴即设计之缪。”

冗长的子程序 在面向对象的编程中,很少会需要用到长度超过一个屏幕的子程序。这样的子程序通常暗示程序员是在把一个结构化程序的脚塞进一只面向对象的鞋子里。

循环过长或嵌套太深 循环内部的复杂代码常常具备转换为子程序的潜质,这样的改动将有助于对代码的分解,并减少循环的复杂性。

内聚性太差的类 如果看到有某个类大包大揽了许多彼此无关的任务,那么这个类就该被拆分成多个类,每个类负责一组具有内在的相互关联的任务。

类的接口未能提供层次一致的抽象 即使是那些从诞生之日起就具有内聚接口的类也可能渐渐失去最初的一致性。

拥有太多参数的参数列表 如果一个程序被分解得很好,那么它的子程序应当小巧、定义精确,且不需要庞大的参数列表。

类的内部修改往往被局限于某个部分 有时一个类会有这两种或更多独立的功能。如果你发现自己要么修改类里的一部分,要么修改另一部分,但极少的修改会同时影响类中的两个部分,这就表明该类应该根据独立的功能被拆分为多个类。

变化导致对多个类的相同修改 如果发现自己常常对同一组类进行修改,这表明这些类中的代码应当被重新组织,使修改仅影响到其中的一个类。

对继承体系的同样修改 每次为某个类添加派生类时,都会发现自己不得不对另一个类做同样的操作。

case语句需要做相同的修改 尽管使用case语句本身不是坏事,但如果不得不在程序的多个部分里对类似的一组case语句做出相同的修改,那么就应当问问自己,使用继承是否是更明智的选择。

同时使用的相关数据并未以类的方式进行组织 如果看到自己常常对同样的一组数据进行操作,是否改将这些数据及操作组织到一个类里面。

成员函数使用其他类的特征比使用自身类的特征还要多 这一状况暗示着这一子程序应该被放到另一个类中,然后再原来的类里调用。

过多使用基本数据类型 基本数据类型可用于表示真实世界中实体的任意数量,如果程序中使用了整型这样的基本数据类型表示某种常见的实体,如货币,请考虑创建一个简单的Money类,这样编译器就可以对Money变量执行类型检查,你也可以对赋给Money的值添加安全检查等功能。

某个类无所事事

一系列传递流浪数据的子程序 看看自己的代码,把数据传递给某个子程序,是否仅仅就是为了让该子程序把数据转交给另一个子程序。这样传来传去的数据被称为“流浪数据/tramp data”。这样做并非错误,只是需要自检,如此传递特定数据,是否与每个子程序接口所表示的抽象概念一致。

中间人对象无事可做 如果看到某个类中的绝大部分代码只是去调用其他类中的成员函数,请考虑是否应该把这样的中间人(middleman)去掉,转而直接调用其他的类。

某个类同其他类关系过于亲密 如果需要使程序具备更强的可管理性,并最大限度地减少更改代码对周围的连带影响,那么封装(信息隐藏)可能是最强有力的工具了。只要发现某个类对另一个类的了解程度超过了应该的程度——包括派生类了解基类中过多的东西,那么宁可让代码因较强的封装而出错,也不要减弱封装。

子程序命名不当

数据成员被设置成公用 这样会模糊接口和实现之间的接线,其本身也违背了封装的原则,限制了类在未来可以发挥的灵活性。因此,请认真考虑把public数据成员藏在访问器子程序背后。

某个派生类仅使用了基类的很少一部分成员函数 因此应当考虑进行更完善的封装:把派生类相对于基类的关系从“is-a”转变为“has-a”。即把基类转换成原来的派生类的数据成员,然后仅仅为原来的派生类提供所需要的函数。

注释被用于解释难懂的代码 注释在程序中扮演了重要的角色,但它不应当被用来为拙劣代码的存在而辩护。有箴言为证:“不要为拙劣的代码编写文档——应当重写代码”(Kernighan and Plauger 1978)。

使用了全局变量 当你再度遇到某段使用了全局变量的代码时,请花点时间来重新检查一下这些代码。

在子程序调用前使用了设置代码(setup code),或在调用后使用了收尾代码(takedown code)

//C++示例:在子程序调用前后的设置代码和收尾代码——糟糕的做法
//在调用子程序之前的设置代码
WithdrawalTransaction withdrawal;
withdrawal.SetCustormerId( custormerId );
withdrawal.SetBalance( balance );
withdrawal.SetWithdrawalAmount( withdrawalAmount );
withdrawal.SetwithdrawalDate( withdrawalDate );

ProcessWithdrawal( withdrawal );

//在调用子程序之后的收尾代码
customerId = withdrawal.GetCustomerId();
balance = withdrawal.GetBalance();
withdrawalAmount = withdrawal.GetWithdrawalAmount();
withdrawalDate = withdrawal.GetWithdrawalDate();

程序中的一些代码似乎是在将来的某个时候才会用到的 在猜测程序将来有哪些功能可能被用到这方面,程序员已经声名狼藉了。

  • 对这些“超前设计”的代码而言,需求不可能定义得很完备。这就意味着程序员对于未来需求的猜测很可能是错误的。
  • 即使程序员对未来需求的前瞻几近完全正确,他也不可能广泛预见未来需求所有的复杂脉络。这些错综复杂的关系将会埋葬程序员的基本设计构思。
  • 那些使用“超前设计”代码的未来程序员们并不知道自己手中的代码原本是经过“超前设计”的,或许他们会期望这些代码能比实际情况表现得更好。
  • “超前设计”的代码是画蛇添足,增加了程序的复杂性,带来了额外的测试、修补缺陷等工作量。其整体效应就是拖了项目的后腿。

2.拒绝重构的理由

在日常的讨论中,“重构”一词更多被用来指那些弥补缺陷、增加功能、修改设计等工作,全然成为了对代码做了任何修改的同义词。这一术语的深刻内涵已惨遭稀释。修改本身并不是什么了不得的好事,但如果是程序员深思熟虑而为之,且遵循规范恰如其分,那么在不断的维护下,这样的修改必将成为代码质量稳步提升之关键,且能避免如今随处可见的代码因质量不断下降而最终灭亡的趋势。

特定的重构

1.数据级的重构

用具名常量替代神秘数值

使变量的名词更为清晰且传递更多信息

将表达式内联化 把一个中间变量换成给它赋值的那个表达式本身。

用函数来代替表达式 用一个函数来代替表达式(这样一来,表达式就不会在代码中重复出现了)。

引入中间变量 要记住,给这个中间变量命名应能准确概括表达式的用途。

用多个单一用途的变量代替某个多用途变量 如果某个变量身兼数职——通常是i、j、temp、x——请用多个变量来让它们各司其职吧,各个变量还应该具有更为准确的变量名。

在局部用途中使用局部变量而不是参数 如果一个被用作输入的子程序参数在其内部又被用作局部变量,那么请直接创建一个局部变量来代替它。

将基础数据类型转化为类 如果一个基础数据类型需要额外的功能(例如更为严格的类型检查)或额外的数据,那么就把该数据转换为一个对象,然后再添加你所需要的类行为。

将一组类型码(type codes)转化为类或枚举类型

将一组类型码转换为一个基类及其相应的派生类 如果与不同类型相关联的不同代码片段有着不一样的功能,请考虑为该类创建一个基类,然后针对每个类型码创建派生类。例如对OutputType基类,就可以创建Screen、Printer和File这样的派生类。

将数组转换为对象 如果正在使用一个数组,其中的不同元素具有不同的类型,那么就应该用一个对象来替代它。将数组中的各个元素转化为该类的各个成员。

把群集(collection)封装起来 如果一个类返回一个群集,到处散布的多个群集实例将会带来同步问题。请让你的类返回一个只读群集,并且提供相应的为群集添加和删除元素的子程序。

用数据类来代替传统记录 建立一个包含记录成员的类。这样你可以集中完成对记录的错误检查、持久化和其他与该记录相关的操作。

2.语句级的重构

分解布尔表达式 通过引入命名准确的中间变量来简化复杂的布尔表达式,通过变量名更好地说明表达式的含义。

将复杂布尔表达式转换成命名准确的布尔函数

合并条件语句不通部分中的重复代码片段 如果你有完全相同的代码同时出现在一个条件语句中的if语句块和else语句块中,那么就应该讲这段代码移到整个if-then-else语句块的后面。

使用break或return而不是循环控制变量 如果在循环中用到了一个类似done这样的控制循环的变量,请使用break或return来代替它。

在嵌套的if-then-else语句中一旦知道答案就立刻返回,而不是去赋一个返回值

用多态来替代条件语句(尤其是重复的case语句) 结构化程序里很多的case语句中的逻辑都可以被放到继承关系中,通过多态函数调用实现。

创建和使用null对象而不是去检测空值 有时,null对象可以有一些相关的通用功能或数据,诸如引用一个不知名字的resident对象时把它作为“occupant”。遇到这种情况,应该把处理null值的功能从客户代码中提出来,放到相应的类中。做法如下:设计一个Customer类,在resident未知时将其定义为“occupant”;而不是让Customer类的客户代码反复检测对象的名字是否已知,并在未知时用“occupant”代替它。

3.子程序级重构

提取子程序或者方法 把内嵌的代码(inline code)从一个子程序中提取出来,并将其提炼为单独的子程序。

将子程序的代码内联化 如果子程序的程序体很简单,且含义不言自明,那么就在使用的时候直接使用这些代码。

将冗长的子程序转换为类 如果子程序太长,可以将其转换为类,然后进一步对之前的子程序进行分解,通过所得到的多个子程序来改善该代码的可读性。

用简单的算法替代复杂算法

增加参数 如果子程序需要从调用方获得更多的信息,可以增加它的参数从而为其提供信息。

删除参数 如果子程序已经不再使用某个参数,就删掉它。

将查询操作从修改操作中独立出来 通常,查询操作并不改变对象的状态。一次,一旦有了类似GetTotals()的操作改变了对象的状态,就应该将查询功能从状态改变中独立出来,提供两个独立的子程序。

合并相似的子程序,通过参数区分它们的功能 两个相似子程序唯一区别或许只是其中用到的常量值不同。请把它们合并到一起,然后将常量值通过参数传入。

将行为取决于参数的子程序拆分开来 如果一个子程序根据输入参数的值执行了不同的代码,请考虑将它拆分成几个可以被单独调用的、无须传递特定参数的子程序。

传递整个对象而非特定成员 如果发现有同一个对象的多个值被传递给了一个子程序,考虑是否可修改其接口使之接受整个对象。

传递特定成员而非整个对象

包装向下转型的操作 通常当子程序返回一个对象时,应当返回其已知的最精确的对象。这尤其适用于返回迭代器、群集、群集元素等的情况。

4.类实现的重构

将值对象转化为引用对象 如果发现自己创建并维护着多个一模一样的大型复杂对象,请改变对这些对象的使用方式。即仅仅保留一份主拷贝(值对象),然后其他地方使用对该对象的引用(引用对象)。

将引用对象转化为值对象 如果看到自己对某个小型的简单对象进行了多次引用操作,请将这些对象都设置为值对象。

用数据初始化替代虚函数 如果有一组派生类,差别仅仅是虚函数返回的常量不同。与其派生类中覆盖成员函数,不如让派生类在初始化时设定适当的常量值,然后使用基类中的通用代码处理这些值。
指向基类的指针在操作它的多态类对象时,会根据不同的类对象,调用其相应的函数,这个函数就是虚函数。

改变成员函数或成员数据的位置 请考虑对类的继承体做出修改。这些修改通常可以减少派生类的重复工作:

  • 将子程序上移到基类中。
  • 将成员上移到基类中。
  • 将构造函数中的部分代码上移到基类中。
    下面这些改变则可以用来对派生类进行特殊化:
  • 将子程序下移到派生类中。
  • 将成员下移派生类中。
  • 将构造函数下移到派生类中。

将特殊代码提取为派生类 如果某类中的一部分代码仅仅被其部分实例所使用,应该把这部分特殊的代码放到其派生类中。

将相似的代码结合起来放置到基类中 如果两个派生类中有相似的代码,将这些代码结合起来并放到基类中。

5.类接口的重构

将成员函数放到另一个类中 在目标类中创建一个新的成员函数,然后从原类中将函数体移到目标类中。然后再旧的成员函数中调用新的成员函数。

将一个类变成两个 如果一个类同时具备两种或更多的截然不同的功能,请把这个类转化为多个类,使得每个类完成一种明确定义的功能。

删除类 如果某个类无所事事,就应该把该类的代码放到与所完成功能关系更为密切的另一个类中,然后把这个类删掉。

去除委托关系 有时类A调用了类B和类C,而实际上类A只应该调用类B,而B类应该调用类C。在这种情况下就应当考虑A对B的接口抽象是否合适。如果应该由B负责调用C,那么就应该只有B调用C。

去掉中间人 如果存在类A调用类B,类B调用类C的情况,有时让类A直接调用类C会更好。是否应当去掉类B,取决于怎么做才能最好地维护类B接口的完整性。

用委托代替继承 如果某类需要用到另一个类,但又打算获取该类接口更多的控制权,那么可以让基类成为原派生类的一个成员,并公开它的一组成员函数,以完成一种内聚的抽象。

用继承代替委托 如果某个类公开了委托类(成员类)所有成员函数,那么该类应该从委托类继承而来,而不是使用该类。

引入外部的成员函数 如果一个客户类需要被调用类的某个额外的成员函数,而你又无法去修改被调用类,那么可以通过在客户类(client class)中创建新成员函数的方式来提供此功能。

引入扩展类 如果一个类需要多个额外的成员函数,你同样无法修改该类,你可以创建一个新类。该类包括了原类的功能以及新增加的功能。要实现这点,你既可通过原类派生新类然后添加新的成员函数,也可以将原类进行包装,使新类调用所需要的成员函数。

对暴露在外的成员变量进行封装 如果数据成员是公用的,请将其改为私用,然后通过成员函数来访问该数据成员的值。

对于不能修改的类成员,删除相关的Set()成员函数

隐藏那些不会在类之外被用到的成员函数

封装不会使用的成员函数 如果发现自己往往只使用类接口的一部分,那么就为类创建新的接口,仅仅把那些必须的成员函数暴露给类的外部,需要注意,新的接口应该为类提供一致的抽象。

合并那些实现非常类似的基类和派生类 如果派生类并未提供更多的特殊化,那么就应该把它合并会基类。

6.系统级重构

为无法控制的数据创建明确的索引源 有时,你需要让特定系统来维护数据,而在其他需要使用该数据的对象中,你却无法方便或一致地访问这些数据。常见的例子如在GUI控件中维护的数据。在这样的情况下,你需要创建一个类,由该类里映射GUI控件中的数据,然后让GUI控件和其他代码将此类作为该数据的明确来源。

将单向的类联系改为双向的类联系 如果你有两个类,且它们各自需要用到对方的功能,但仅有一个类能访问另一个类。这时就应该将对两个类进行修改,使其相互调用。

将双向的类联系改为单向的类联系 如果有两个类,彼此都知道对方,但实际上只有一个类需要访问另一个类。这时就应该只让那个有实际需要的类能访问另一个类,而另一个类无法访问该类。

用Factory Method方式而不是简单地构造函数 在需要基于类型码创建对象,或者希望使用引用对象而非值对象的时候,应当使用Factory Method(函数)。

用异常取代错误处理代码,或者做相反方向的变换

安全的重构

与其将分解一个正常工作的系统比作替换水槽里面的塞子,倒不如把它看成是替换大脑中的一根神经。如果我们把软件维护称为“软件脑部外科手术”,工作起来会不会要轻松一些?
——Gerald Weinberg

保存初始代码 在开始重构之前,要保证你还能回到代码的初始状态。

重构的步伐请小些 有的重构的步伐比其他重构更大,到底什么能算成是一次重构并不明确。因此请把重构的步伐放小些,这样才能理解所做修改对程序的全部影响。

同一时间只做一项重构 有的重构会比其他的重构更为复杂。除非是对那些最为简单的重构,否则请在同一时间只做一项重构,在进入下一项重构之前,对代码重新编译并测试。

把要做的事情一条条列出来

设置一个停车场 在某次重构的路途上,你可能会发现你需要进行另一次重构。正在着手这次新的重构时,或许又发现第三个重构会给程序带来很多好处。为了处理这些并不需要立即对付的修改工作,你最好设置一个“停车场”,把你需要在未来某个时间进行而现在可以先放在一边的修改工作列出来。

多使用检查点 在重构的时候,很容易出现代码没有按照设想正常运行的情况。除了保存初始代码外,在重构中还应在多个地方设置检查点。这样一来,即使你编码时钻进了死胡同,你仍然可以让程序回到正常工作的状态。

利用编译器警告信息

重新测试 应该把重新测试作为检查所修改代码工作的补充。当然,这点要取决于从一切开始你是否就有一套优秀的测试用例。

增加测试用例 除了重新运行过去做过的那些测试,还应该增加新的单元测试来检验新引入的代码。如果重构是的一些测试用例已经过时,那么就删除这些用例。

检查对代码的修改 如果说在第一次运行程序的时候检查代码是必需的,那么在接下来的修改工作中,时刻关注代码则更为重要。当代码修改行数从1增加到5的时候,改错的可能性大大增加。在这之后,随着行数的增加,出错的几率可是逐渐降低了。
程序员对于很小的修改常常不以为然。他们不会用纸和笔来推敲程序,也不会让其他人来检查代码。有时甚至根本不会运行这些代码来验证修改工作的正确性。

根据重构风险级别来调整重构方法 对于那些有一定风险的重构,谨慎才能避免出错。务必一次只处理一项重构。除了完成通常要做的编译检查和单元测试之外,还应该让其他人来检查你的重构工作,或是针对重构采用结对编程。

不宜重构的情况

不要只实现一部分功能,并指望将来的重构能完成它。——John Manzo
重构是一剂良药,但不是包治百病的灵丹妙药。
不要把重构当做先写后改的代名词 重构最大的问题在于被滥用。程序员们有时会说自己是在重构,而实际上他们所完成的工作仅仅是对无法运行的代码修修补补,希望能让程序跑起来。重构的含义是在不影响程序行为的前提下改进可运行的代码。那些修补破烂代码的程序员们不是在重构,而是在拼凑代码(hacking)。

避免用重构代替重写 有时,代码所需要的不是细微修改,而是直接一脚踢出门外,这样你就可以全部重新开始。如果发现自己处于大规模的重构之中,就应该问问自己是否应该把这部分代码推倒重来,重新设计,重新开发。

重构策略

对任何特定程序都能带来好处的重构方法本应是无穷无尽的。和其他编程行为一样,重构同样受制于收益递减定律,同样也符合80/20法则。在斟酌哪种重构方法最为重要的时候,不妨考虑一下下面这些建议。

收益递减规律是指其他投入固定不变时,连续地增加某一种投入,所新增的产出最终会减少的规律。该规律另一种等价的说法是:超过某一水平之后边际投入的边际产出下降。
二八定律又名80/20定律、帕累托法则(定律)也叫巴莱特定律、最省力的法则、不平衡原则等,是19世纪末20世纪初意大利经济学家巴莱多发现的。他认为,在任何一组东西中,最重要的只占其中一小部分,约20%,其余80%尽管是多数,却是次要的,因此又称二八定律。

在增加子程序时进行重构 在增加子程序时,检查一下相关的子程序是否都被合理地组织起来了。如果没有,那么就重构这些子程序。

在添加类的时候进行重构 添加一个类往往会使已有代码中的问题浮出水面。

在修补缺陷的时候进行重构 如果你在修补缺陷中有了一些心得体会,请把它运用到改善其他易于产生相似错误的代码上。

关注易于出错的代码 有的模块更容易出错,健壮性远逊于其他模块。尽管绝大部分人对这部分富于挑战性代码的自然反应都会是敬而远之,但集中处理这样的代码将是最为有效的重构策略。

关注高度复杂的模块 另一种方法就是关注最为复杂的模块。一项经典研究表明,当做维护的程序员们把改善代码的精力放在那些最为复杂的模块上时,程序的质量会有显著提升。

在维护环境下,改善你手中正在处理的代码 未予修改的代码是没有必要进行重构的。但如果你正在维护某部分代码,请确保代码在离开你的时候比来之前更健康。

定义清楚干净代码和拙劣代码之间的边界,然后尝试把代码移过这条边界 “现实世界”通常会比你想象的更加混乱。这种状态或是源于复杂的业务规则,或是来自软硬件接口。对那些古董系统而言,常见的麻烦就是人们会要求那些拙劣编写的产品代码自始至终都能工作下去。

  1. 真实世界混乱不堪并不等于你的代码也得同样糟糕。将你的系统看做理想世界、混乱的真实世界,以及从前者到后者的接口的结合。
  2. 改善产品代码的策略之一就是在拿到拙劣的遗产代码时对其重构,由此使其告别混乱不堪的真实世界。
    在你处理这一系统时,你可以设法把代码移过“真实世界接口”,转移到更为有序的理想世界中。在处理一个旧系统时,几乎整个系统可能都是那些写得非常糟糕的代码拼凑起来的。当你对付某段混乱的代码的时候,一种屡试不爽的办法是使这些代码靠近当前的代码规范,例如使用含义明确的变量名等,从而有效地将这部分代码引入到理想世界中。一次次这样处理下去,代码的基础质量就能迅速提升。

Key Points

  • 修改是程序一生都要面对的事情,不仅包括最初的开发阶段,还包括首次发布之后。
  • 在修改中软件的质量要么改进,要么恶化。软件演化的首要法则就是代码演化应当提升程序的内在质量。
  • 重构成功之关键在于程序员应学会关注那些标志着代码需要重构的众多的警告或“代码臭味”。
  • 重构成功另一要素是程序员应当掌握大量特定的重构方法。
  • 重构成功的最后要点在于要有安全重构策略。一些重构方法会比其他重构方法要好。
  • 开发阶段的重构是提升程序质量的最佳时机,因为你可以立刻让刚刚产生的改变梦想变成现实。请珍惜这些开发阶段的天赐良机!