何谓重构
重构”这个词既可以用作名词也可以用作动词。
重构 (名词):对软件内部结构的一种调整,目的是在不改变软件可观察行为的前提下,提高其可理解性,降低其修改成本。
重构(动词):使用一系列重构手法,在不改变软件可观察行为的前提下,调整其结构。
所以,可能会花一两个小时进行重构(动词),其间会使用几十个不同的重构(名词)。重构的关键在于运用大量微小且保持软件行为的步骤,一步步达成大规模的修改。每个单独的重构要么很小,要么由若干小步骤组合而成。因此,在重构的过程中,的代码很少进入不可工作的状态,即便重构没有完成,也可以在任何时刻停下来。如果有人说他们的代码在重构过程中有一两天时间不可用,基本上可以确定,他们在做的事不是重构。
重构与性能优化有很多相似之处:两者都需要修改代码,并且两者都不会改变程序的整体功能。两者的差别在于其目的:重构是为了让代码“更容易理解,更易于修改”。这可能使程序运行得更快,也可能使程序运行得更慢。在性能优化时,只关心让程序运行得更快,最终得到的代码有可能更难理解和维护,对此有心理准备。
两顶帽子
KentBeck提出了“两顶帽子”的比喻。使用重构技术开发软件时,时间被分配给两种截然不同的行为:添加新功能和重构。添加新功能时,不应该修改既有代码,只管添加新功能。通过添加测试并让测试正常运行,可以衡量自己的工作进度。重构时就不能再添加功能,只管调整代码的结构。此时不应该添加任何测试(除非发现有先前遗漏的东西),只在绝对必要(用以处理接口变化)时才修改测试。
为何重构
重构绝对不是所谓的“银弹”。可以帮你始终良好地控制自己的代码。重构是一个工具,它可以(并且应该)用于以下几个目的。
重构改进软件的设计
如果没有重构,程序的内部设计(或者叫架构)会逐渐腐败变质。当人们只为短期目的而修改代码时,他们经常没有完全理解架构的整体设计,于是代码逐渐失去了自己的结构。程序员越来越难通过阅读源码来理解原来的设计。代码结构的流失有累积效应。越难看出代码所代表的设计意图,就越难保护其设计,于是设计就腐败得越快。经常性的重构有助于代码维持自己该有的形态。
重构使软件更容易理解
所谓程序设计,很大程度上就是与计算机对话:编写代码告诉计算机做什么事,而它的响应是按照的指示精确行动。一言以蔽之,所做的就是填补“想要它做什么”和“告诉它做什么”之间的缝隙。编程的核心就在于“准确说出想要的”。然而别忘了,除了计算机外,源码还有其他读者:几个月之后可能会有另一位程序员尝试读懂的代码并对其做一些修改。们很容易忘记这这位读者,但他才是最重要的。计算机是否多花了几个时钟周期来编译,又有什么关系呢?如果一个程序员花费一周时间来修改某段代码,那才要命呢——如果他理解了的代码,这个修改原本只需一小时。
重构帮助找到bug
如果对代码进行重构,就可以深入理解代码的所作所为,并立即把新的理解反映在代码当中。搞清楚程序结构的同时,也验证了自己所做的一些假设,于是想不把bug揪出来都难。重构能够帮助更有效地写出健壮的代码。
重构提高编程速度
最后,前面的一切都归结到了这一点:重构帮更快速地开发程序。听起来有点儿违反直觉。当谈到重构时,人们很容易看出它能够提高质量。改善设计、提升可读性、减少bug,这些都能提高质量。但花在重构上的时间,难道不是在降低开发速度吗?
当跟那些在一个系统上工作较长时间的软件开发者交谈时,经常会听到这样的故事:一开始他们进展很快,但如今想要添加一个新功能需要的时间就要长得多。他们需要花越来越多的时间去考虑如何把新功能塞进现有的代码库,不断蹦出来的bug修复起来也越来越慢。代码库看起来就像补丁摞补丁,需要细致的考古工作才能弄明白整个系统是如何工作的。这份负担不断拖慢新增功能的速度,到最后程序员恨不得从头开始重写整个系统。
但有些团队的境遇则截然不同。他们添加新功能的速度越来越快,因为他们能利用已有的功能,基于已有的功能快速构建新功能。
两种团队的区别就在于软件的内部质量。需要添加新功能时,内部质量良好的软件让可以很容易找到在哪里修改、如何修改。良好的模块划分使只需要理解代码的一小部分,就可以做出修改。如果代码很清晰,引入bug的可能性就会变小,即使引入了bug,调试也会容易得多。理想情况下,的代码库会逐步演化成一个平台,在其上可以很容易地构造与其领域相关的新功能。
把这种现象称为“设计耐久性假说”:通过投入精力改善内部设计,们增加了软件的耐久性,从而可以更长时间地保持开发的快速。还无法科学地证明这个理论,所以说它是一个“假说”。但的经验,以及在职业生涯中认识的上百名优秀程序员的经验,都支持这个假说。
20年前,行业的陈规认为:良好的设计必须在开始编程之前完成,因为一旦开始编写代码,设计就只会逐渐腐败。重构改变了这个图景。现在们可以改善已有代码的设计,因此们可以先做一个设计,然后不断改善它,哪怕程序本身的功能也在不断发生着变化。由于预先做出良好的设计非常困难,想要既体面又快速地开发功能,重构必不可少。
何时重构
三次法则
Don Roberts 给了一条准则:第一次做某件事时只管去做;第二次做类似的事会产生反感,但无论如何还是可以去做;第三次再做类似的事,你就应该重构。正如老话说的:事不过三,三则重构。
预备性重构:让添加新功能更容易
重构的最佳时机就在添加新功能之前。在动手添加新功能之前,会看看现有的代码库,此时经常会发现:如果对代码结构做一点微调,的工作会容易得多。
帮助理解的重构:使代码更易懂
需要先理解代码在做什么,然后才能着手修改。这段代码可能是写的,也可能是别人写的。一旦需要思考“这段代码到底在做什么”,就会自问:能不能重构这段代码,令其一目了然?可能看见了一段结构糟糕的条件逻辑,也可能希望复用一个函数,但花费了几分钟才弄懂它到底在做什么,因为它的函数命名实在是太糟糕了。这些都是重构的机会。
重构带来的帮助不仅发生在将来——常常是立竿见影。会先在一些小细节上使用重构来帮助理解,给一两个变量改名,让它们更清楚地表达意图,以方便理解,或是将一个长函数拆成几个小函数。当代码变得更清晰一些时,就会看见之前看不见的设计问题。如果不做前面的重构,可能永远都看不见这些设计问题,因为不够聪明,无法在脑海中推演所有这些变化。RalphJohnson说,这些初步的重构就像扫去窗上的尘埃,使们得以看到窗外的风景。在研读代码时,重构会引领获得更高层面的理解,如果只是阅读代码很难有此领悟。有些人以为这些重构只是毫无意义地把玩代码,他们没有意识到,缺少了这些细微的整理,他们就无法看到隐藏在一片混乱背后的机遇。
有计划的重构和见机行事的重构
并不专门安排一段时间来重构,而是在添加功能或修复bug的同时顺便重构。这是我自然的编程流的一部分。不管是要添加功能还是修复bug,重构对我当下的任务有帮助,而且让我未来的工作更轻松。这是一件很重要而又常被误解的事:重构不是与编程割裂的行为。
何时不应该重构
只有当我需要理解其工作原理时,对其进行重构才有价值。另一种情况是,如果重写比重构还容易,就别重构了。这是个困难的决定。如果不花一点儿时间尝试,往往很难真实了解重构一块代码的难度。决定到底应该重构还是重写,需要良好的判断力与丰富的经验。