【笔记】《重构:改善既有代码的设计》第2章-重构原则

第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文化中一个重要元素。

来源:SquareSquareHe

声明:本站部分文章及图片转载于互联网,内容版权归原作者所有,如本站任何资料有侵权请您尽早请联系jinwei@zod.com.cn进行处理,非常感谢!

上一篇 2019年3月21日
下一篇 2019年3月21日

相关推荐