1. 重构导论

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


一、什么是重构?
重构是在不改变软件 可观察行为的前提下 改善其内部结构重构是一种经千锤百炼形成的有条不紊的程序整理 方法,可以最大限度地减少整理过程中引入错误的几率。
本质上说,重构就是在代码写好之后改进它的设计。

如果你发现自己需要为程序添加一个特性,而代码结构使你无法很方便地达成目的,那就先重构那个程序,使特性的添加比较容易进行,然后再添加特性。

1. 重构的第一步
每当我们要进行重构的时候,第一个步骤永远相同:为即将修改的代码建立一组 可靠的测试环境(如果没有的话)。 好的测试是重构的根本

2. 使用重构策略
分割大方法为 小方法,把小方法提取到它应该存在的地方(类或对象中)。
“单一职责原则”“一个类只应有一个变化的原因”等等都在说明 "小"而美。

二、 重构原则
首先:重构的目的是使软件更容易被理解和修改。
其次:重构不会改变可观察的行为。


Kent beck有个“两顶帽子”比喻。使用重构技术开发软件时,你把自己的时间分配给两种截然不同的行为: 添加新功能,以及重构。添加新功能时,不应该修改既有代码,只管添加新功能。通过测试,可以衡量自己的工作进度。重构时,不能再添加功能,只管改进程序结构。此时,不应该添加任何测试,只在绝对必要时才修改测试。 软件开发过程中,可能会经常变换帽子,交叉进行。

2.1 为何重构
1. 重构改进软件设计
如果没有重构,程序的设计会 逐渐腐 败变质。当人们只为短期目的,或是在完全理解整体设计之前,就贸然修改代码,程序将逐渐失去自己的结构,程序员愈来愈难通过阅读源码而理解原来的设计。重构很像是在整理代码,你所做的就是让所有东西回到应处的位置上。 代码结构的流失是累积性的。愈难看出代码所代表的设计意图,就愈难保护其中设计,于是该设计就腐 败得愈快。经常性的重构可以帮助代码维持自己该有的形态 。

完成同样一件事,设计不良的程序往往需要更多代码,这常常是因为代码在不同的地方使用完全相同的语句做同样的事。因此改进设计的一个重要方向就是 消除重复代码。这个动作的重要性在于方便未来的修改。代码量减少并不会使系统运行更快,因为这对程序的运行轨迹几乎没有任何明显影响。然而代码量减少将使未来可能的程序修改动作容易得多。代码愈多,正确的修改就愈困难,因为有更多代码需要理解。你在这儿做了点修改,系统却不如预期那样工作 ,是因为你没有修改另一处,那儿的代码做着几乎完全一样的事情,只是所处环境略有不同。如果消除重复代码,你就可以确定所有事物和行为在代码中只表述一次,这正是优秀设计的根本。 重复是万恶之源。

2. 重构使软件更容易理解
所谓程序设计,很大程度上就是与计算机交谈:你编写代码告诉计算机做什么事,它的响应则是精确按照你的指示行动。你得及时填补"想要它做什么"和"告诉它做什么"之间的缝隙.这种编程模式的核心就是"准确说出我所要的"。除了计算机外 ,你的源码还有其他读者:几个月之后可能会有另一位程序员尝试读懂你的代码并做一些修改.我们很容易忘记这第二位读者,但他才是最重要的。计算机是否多花了几个小时来编译,又有什么关系呢?如果一个程序员花费一周时间来修改某段代码,那才要命呢,如果他理解了你的代码,这个修改原本只需一小时。 软件的维护成本远大于初次开发成本。重构可以减少成本。

3. 重构帮助找到bug

4. 重构帮助提高编程速度
良好设计是维持软件开及速度的根本。重构可以帮助你更快速地开发软件,因为它阻止系统腐 败变质,它甚至还可以提高设计质量.

2.2 何时重构
三次法则:
引用
DonRoberts给了一条准则:第一次做某件事时只管去做:第二次做类似的事会产生反感 ,但无论如何还是可以去做 :第三次再做类似的事,你就应该重构.


1. 添加功能时重构
最常见的重构时机就是我想给软件添加新特性的时候.此时,重构的直接原因往往是为了帮助我理解需要修改的代码一这些代码可能是别人写的,也可能是自己写的。无论何时,只要我想理解代码所做的事, 我就会问自己 :是否能对这段代码进行重构,使我能更快地理解它。然后我就会重构。之所以这么做,部分原因是为了让我下次再看这段代码时容易理解,但最主要的原因是:如果在前进过程中把代码结构理清,我就可以从中理解更多东西 .

在这里,重构的另一个原动力是 : 代码的设计无法帮助我轻松添加我所需要的特性。我看着设计,然后对自己说"如果用某种方式来设计, 添加特性会简单得多."

这种情况下我不会因为自己过去的错误而懊恼一一我 用重构来弥补它。之所以这么做,部分原因是为了让未来增加新特性时能够更轻松一些,但最主要的原因还是: 我发现这是最快捷的途径。 重构是一个快速流畅的过程, 一旦完成重构,新特性的添加就会更快速、更流畅。

2. 修补错误时重构
调试过程中运用重构,多半是为了让代码更具可读性。 当我看着代码并努力理解它的时候,我用重构帮助加深自己的理解。我发现以这种程序来处理代码,常常能够帮助我找出bug.

3. 复审代码时重构(code review)
很多公司都会做常规的代码复审,因为这种活动可以改善开发状况.这种活动有助于在开发团队中传播知识,也有助于让较有经验的开发者把知识传递给比较欠缺经验的人,并帮助更多人理解大型软件系统中的更多部分。代码复审对于编写清晰代码也很重要。我的代码也许对我 自己来说很清晰,对他人则不然 。这是无法避免的,因为要让开发者设身处地为那些不熟悉自己所做所为的人着想,实在太困难了。代码复审也让更多人有机会提出有用的建议, 毕竟我在一个星期之内能够想出的好点子很有限.如果能得到别人的帮助,我的生活会滋润得多 ,所以我总是期待更多复审.

2.3 重构与设计
重构肩负一项特殊使命:它和设计彼此互补。初学编程的时候,我埋头就写程序,浑浑噩噩地进行开发。然而很快我便发现,事先做好设计可以让我节省返工的高昂成本。于是我很快加强这种"预先设计"风格。许多人都把设计看做软件开发的关键环节,而把编程看做只是机械式的低级劳动。他们认为设计就像画工程图而编码就像施工。但是你要知道,软件和机器有着很大的差异: 软件的可塑性更强,而且完全是思想产品。正如 Alistair Cockburn所说: "有了设计,我可以思考得更快,但是其中充满小漏洞。"
有一种观点认为:重构可以取代预先设计。这意思是你根本不必做任何设计,只管按照最初想法开始编码,让代码有效运作,然后再将它重构成型。事实上这种 办法真的可行。我的确看过有入这么傲,最后获得设计良好的软件。极限编程 [Beck,XP]的支持者极力提倡这种办法。

尽管如上所言,只运用重构也能收到效果,但这并不是最有效的途径。是的, 就连极限编程的爱好者们也会进行预先设计。他们会使用 CRC卡或类似的东西来检 验各种不同想法,然后才得到第一个可被接受的解决方案,然后才能开始编码,然后才能重构。关键在于:重构改变了预先设计的角色。如果没有重构 ,你就必须保证预先做出的设计正确无误,这个压力太大了。这意味如果将来需要对原始设计做任何修改,代价都将非常高昂。因此你需要把更多时间和精力放在预先设计上,以避免日后修改。

如果你选择重构,问题的重点就转变了。你仍然做预先设计,但是不必一定找出正确的解决方案。此刻的你只需要得到一个足够合理的解决方案就够了。你很肯定地知道,在实现这个初始解决方案的时候,你对问题的理解也会逐渐加深,你可能会察觉最佳解决方案和你当初设想的有些不同。只要有重构这把利器在手,就不成问题,因为重构让日后的修改成本不再高昂。

这种转变导致一个重要结果: 软件设计向简化前进了一大步。过去未曾运用重 构时,我总是力求得到灵活的解决方案。任何一个需求都让我提心吊胆地猜疑:  在系统的有生之年,这个需求会导致怎样的变化?由于变更设计的代价非常高昂 ,所以我希望建造 一个足够灵活、 足够牢靠的解决方案,希望它能承受我所能预见的所有需求变化。问题在于要建造一个灵活的解决方案,所需的成本难以估算。灵活的解决方案比简单的解决方案复杂许多,所以最终得到的软件通常也会更难维护一一 虽然它在我预先设想的方向上的确是更加灵活。就算幸运地走在预先设想的方向上, 你也必须理解如何修改设计。如果变化只出现在一两个地方,那不算大问题。然而变化其实可能出现在系统各处.如果在所有可能的变化出现地点都建 立起灵活性 ,整个系统的复杂度和维护难度都会大大提高。 当然,如果最后发现所有这 些灵活性 都毫无必要,这才是最大的失败 .你知道, 这其中肯定有些灵活性的确派不 上用场,但你却无法预测到底是哪些派不上用场。为了获得自 己想要的灵活性,你不得不加入比实际需要更多的灵活性.

有了重构,你就可以通过一条不同的途径来应付变化带来的风险.你仍需要思考潜在的变化,仍旧需要考虑灵活的解决方案。但是你不必再逐一实现这些解决方案,而是应该问问自己"把 一个简单的解决方案重构成这个灵活的方案有多大难度? "如果答案是"相当容易" (大多数时候都如此).那么你就只需实现目前的简单方案就行了.

重构可以带来更简单的设计,同时又不损失灵活性,这也降低了设计过程的难度,减轻了设计阻力。一旦对重构带来的简单性有更多感受,你甚至可以不必再预先思考前述所谓的灵活方案,一旦简要它,你总有足够的信心去重构。是的,当下只管建 造可运行的最简化系统,至于灵活而复杂的设计,多数时候你都不会需要它。

参考:
《重构-改善既有代码的设计》 Martin Fowler著

猜你喜欢

转载自zoroeye.iteye.com/blog/2193313
1.