第2章 重构原则
2.1 何谓重构
第一个定义是名词形式
重构(名词):对软件内部结构的一种调整,目的是在不改变软件可观察行为的前提下,提高其可理解性,降低其修改成本。
“重构”的另一个用法是动词形式
重构(动词):使用一系列重构手法,在不改变软件可观察行为的前提下,调整其结构。
我的定义还需要向两方面扩展。首先,重构的目的是使软件更容易被理解和修改。你可以在软件内部做很多修改,但必须对软件可观察的外部行为只造成很小的变化,或不造成变化。与之形成对比的是性能优化。和重构一样,性能优化通常不会改变组件的行为(除了执行速度),只会改变其内部结构。但是两者出发点不同:性能优化往往使代码较难理解,但为了得到所需的性能你不得不那么做。
我要强调的第二点是:重构不会改变软件可观察的行为——重构之后软件功能一如以往。
两顶帽子
使用重构技术开发软件时,你把自己的时间点分配给两种截然不同的行为:添加新功能,以及重构。添加新功能时,你不应该修改既有代码,只管添加新功能。通过测试(并让测试正常运行),你可以衡量自己的工作进度。重构时你就不能再添加新功能,只管改进程序结构。此时你不应该添加任何测试,只在绝对必要(用以处理接口变化)时才修改测试。
2.2 为何重构
重构改进软件设计
如果没有重构,程序的设计会逐渐腐败变质。代码结构的流失是累积性的。经常性的重构可以帮助代码维持自己该有的形态。
完成同样一件事,设计不良的程序往往需要更多代码,这常常是因为代码在不同的地方使用完全相同的语句在做同样的事。因此改进设计的一个重要方向就是消除重复代码。 如果消除重复代码,你就可以确定所有事物和行为在代码中只表述一次,这正式优秀设计的根本。
重构使软件更容易理解
除了计算机外,你的源码还有其他读者:几个月后可能会有另一位程序员尝试读懂你的代码并做一些修改。
很多时候那个未来的开发者就是我自己。
这种可理解性还有另一方面的作用。我利用重构来协助我理解不熟悉的代码。每当看到不熟悉的代码,我必须试着理解其用途。
Ralph Johnson把这种“早期重构”描述为“擦掉窗户上的污垢,使你看得更远”。研究代码时我发现,重构把我带到更高的理解层次上。如果没有重构,我达不到这种层次。
重构帮助找到bug
对代码的理解,可以帮助我找到bug。
Kent Beck经常形容自己的一句话:“我并不是一个伟大的程序员,我只是个有一些优秀习惯的好程序员。”重构能帮助我更有效地写出强健的代码。
重构提高编程速度
前面的一切都归结到这最后一点:重构帮助你更快速地开发程序。
良好的设计是快速开发的根本。
2.3 何时重构
重构应该随时随地进行。
三次法则
Don Roberts给了我一条准则:第一次做某件事时只管去做;第二次做类似的事会产生反感,但无论如何还是可以去做;第三次再做类似的事,你就应该重构。
事不过三,三则重构。
添加功能时重构
最常见的重构时机就是我想给软件添加新特性的时候。此时,重构的直接原因往往是为了帮助我理解需要修改的代码——这些代码可能是别人写的,也可能是我自己写的。
在这里,重构的另一个原动力是:代码的设计无法帮助我轻松添加我所需要的特性。
修补错误时重构
调试过程中运用重构,多半是为了让代码更具可读性。
复审代码时重构
很多公司都会做常规的代码复审,因为这种活动可以改善开发状况。这种活动有助于在开发团队中传播知识,也有助于较有经验的开发者把知识传递给比较欠缺经验的人,并帮助很多人理解大型软件系统中的更多部分。代码复审对于编写清晰代码也很重要。
重构可以帮助我复审别人的代码。
重构还可以帮助代码复审工作得到更具体的结果。不仅获得建议,而且其中许多建议能够立刻实现。
2.4 怎么对经理说
在复审过程中使用重构就是一个不错的方法。大量研究结果显示,技术复审是减少错误、提高开发速度的一条重要途径。
当然,很多经理嘴巴上说自己“质量驱动”,其实更多是“进度驱动”。这种情况下我会给他们一个较有争议的建议:不要告诉经理!
软件开发者都是专业人士。对于快速创造软件,重构可带来巨大帮助。受进度驱动的经理要我尽可能快速完事,至于怎么完成,那就是我的事了。我认为最快的方式就是重构,所以我就重构。
(***笔记注解:***感觉上面文章翻译很垃圾,重构游戏原文我推测肯定是gaming译作”博弈”,讨论的是程序员需要权衡的利弊问题)
2.5 重构的难题
数据库
重构经常出问题的一个领域就是数据库。绝大多数商用程序都与它们背后的数据库结构紧密耦合在一起,这也是数据库结构如此难以修改的原因之一。另一个原因是数据迁移(migration)。就算你非常小心地将系统分层,将数据库结构和对象模型间的依赖降至最低,但数据库结构的改变还是让你不得不迁移所有的数据,这可能是件漫长而繁琐的工作。
在非对象数据库中,解决这个问题的方法之一就是:在对象模型和数据库模型之间插入一个分隔层,这就可以隔离两个模型各自的变化。升级某一模型时无需同时升级另一模型,只需升级上述的分隔层即可。这样的分隔层会增加系统复杂度,但可以给你带来很大的灵活度。如果你同时拥有多个数据库,或如果数据库模型较为复杂使你难以控制,那么即使不进行重构,这分隔层也是很重要的。
你无需一开始就插入分隔层,可以在发现对象模型变得不稳定时再产生它,这样你就可以为你的改变找到最好的平衡点。
对于开发者而言,对象数据库既有帮助也有妨碍。
修改接口
对于接口要特别谨慎——如果接口被修改了,任何事情都可能发生。
一直对重构带来困扰的一件事就是:许多重构方法的确会修改接口。像Rename Method这么简单的重构方法所做的一切就是修改接口。
只有当需要修改的接口被那些”找不到,即使找到也不能修改”的代码使用时,接口的修改才会成为问题。如果情况真是如此,我就会说:这个接口是个已发布接口(published interface)——比公开接口(public interface)更进一步。接口一旦发布,你就再也没法仅仅修改调用者而能够安全地修改接口了。
如何面对那些必须修改“已发布接口”的重构方法/p>
如果重构方法改变了已发布接口,你必须同时维护新旧两个接口,直到所有用户都有时间对这个变化做出反应。 请尽量这么做:让旧接口调用新接口。 你还应该使用Java提供的deprecation(不建议使用)设施,将旧接口标记为deprecated。
这个过程的一个好例子就是Java容器类(集合类, Collection classes)。Java 2 的新容器取代了原先一些容器。
我们有另一个选择:不要发布接口。 代码所有权
不要过早发布接口,请修改你的代码所有权政策,使重构更顺畅。
Java还有一种特别的接口修改:在throws子句中添加一个异常。
难以通过重构手法完成的设计改动
先想象重构的情况。
何时不该重构
有时候既有代码太混乱,重构它还不如重新写一个来的简单。作出这种决定很困难,我承认我也没有什么好准则可以判断何时应该放弃重构。
重写(而非重构)的一个清楚讯 就是:现有代码根本不能正常运作。
一个折中方法就是:将“大块头软件”重构为封装良好的小型组件。然后你就可以逐一对组件做出“重构或重建”的决定。
另外,如果项目已近最后期限,你也应该避免重构。
2.6 重构与设计
重构肩负一项特殊使命:它和设计彼此互补。
极限编程
2.7 重构与性能
三种编写快速软件的方法。其中最严格的的是时间预算法,这通常只用于性能要求极高的实时系统。
第二种方法是持续关注法。这种方法要求任何程序员在任何时间做任何事时,都要设法保持系统的高性能。
第三种性能提升法就是利用上述90%统计数据。你编写构造良好的程序,不对性能投以特别的关注,直至进入性能优化阶段——那通常是在开发后期。一旦进入该阶段,你再按照某个特定程序来调整程序性能。
“发现热点、去除热点”
2.8 重构起源何处
重构(refactoring)
最早认识重构重要性的两个人是Ward Cunningham和Kent Beck,他们早在20世纪80年代就开始使用Smalltalk,那是一个特别适合重构的环境。
Ward和Kent的思想对Smalltalk 群产生了极大影响,重构概念也成为Smalltalk文化中一个重要元素。
声明:本站部分文章及图片源自用户投稿,如本站任何资料有侵权请您尽早请联系jinwei@zod.com.cn进行处理,非常感谢!