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

第2章 重构原则

2.1 何谓重构

第一个定义是名词形式

重构(名词):对软件内部结构的一种调整,目的是在不改变软件可观察行为的前提下,提高其可理解性,降低其修改成本。

“重构”的另一个用法是动词形式

重构(动词):使用一系列重构手法,在不改变软件可观察行为的前提下,调整其结构。

我的定义还需要向两方面扩展。首先,重构的目的是使软件更容易被理解和修改。你可以在软件内部做很多修改,但必须对软件可观察的外部行为只造成很小的变化,或不造成变化。与之形成对比的是性能优化。和重构一样,性能优化通常不会改变组件的行为(除了执行速度),只会改变其内部结构。但是两者出发点不同:性能优化往往使代码较难理解,但为了得到所需的性能你不得不那么做。

我要强调的第二点是:重构不会改变软件可观察的行为——重构之后软件功能一如以往。

两顶帽子

使用重构技术开发软件时,你把自己的时间点分配给两种截然不同的行为:添加新功能,以及重构。添加新功能时,你不应该修改既有代码,只管添加新功能。通过测试(并让测试正常运行),你可以衡量自己的工作进度。重构时你就不能再添加新功能,只管改进程序结构。此时你不应该添加任何测试,只在绝对必要(用以处理接口变化)时才修改测试。

2.2 为何重构

重构改进软件设计

如果没有重构,程序的设计会逐渐腐败变质。代码结构的流失是累积性的。经常性的重构可以帮助代码维持自己该有的形态。

完成同样一件事,设计不良的程序往往需要更多代码,这常常是因为代码在不同的地方使用完全相同的语句在做同样的事。因此改进设计的一个重要方向就是消除重复代码。 如果消除重复代码,你就可以确定所有事物和行为在代码中只表述一次,这正式优秀设计的根本。

重构使软件更容易理解

除了计算机外,你的源码还有其他读者:几个月后可能会有另一位程序员尝试读懂你的代码并做一些修改。

很多时候那个未来的开发者就是我自己。

这种可理解性还有另一方面的作用。我利用重构来协助我理解不熟悉的代码。每当看到不熟悉的代码,我必须试着理解其用途。

Ralph Johnson把这种“早期重构”描述为“擦掉窗户上的污垢,使你看得更远”。研究代码时我发现,重构把我带到更高的理解层次上。如果没有重构,我达不到这种层次。

重构帮助找到bug

对代码的理解,可以帮助我找到bug。

Kent Beck经常形容自己的一句话:“我并不是一个伟大的程序员,我只是个有一些优秀习惯的好程序员。”重构能帮助我更有效地写出强健的代码。

重构提高编程速度

前面的一切都归结到这最后一点:重构帮助你更快速地开发程序。

良好的设计是快速开发的根本。

2.3 何时重构

重构应该随时随地进行。

三次法则

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

事不过三,三则重构。

添加功能时重构

最常见的重构时机就是我想给软件添加新特性的时候。此时,重构的直接原因往往是为了帮助我理解需要修改的代码——这些代码可能是别人写的,也可能是我自己写的。

在这里,重构的另一个原动力是:代码的设计无法帮助我轻松添加我所需要的特性。

修补错误时重构

调试过程中运用重构,多半是为了让代码更具可读性。

复审代码时重构

很多公司都会做常规的代码复审,因为这种活动可以改善开发状况。这种活动有助于在开发团队中传播知识,也有助于较有经验的开发者把知识传递给比较欠缺经验的人,并帮助很多人理解大型软件系统中的更多部分。代码复审对于编写清晰代码也很重要。

重构可以帮助我复审别人的代码。

重构还可以帮助代码复审工作得到更具体的结果。不仅获得建议,而且其中许多建议能够立刻实现。

2.4 怎么对经理说

在复审过程中使用重构就是一个不错的方法。大量研究结果显示,技术复审是减少错误、提高开发速度的一条重要途径。

当然,很多经理嘴巴上说自己“质量驱动”,其实更多是“进度驱动”。这种情况下我会给他们一个较有争议的建议:不要告诉经理!

软件开发者都是专业人士。对于快速创造软件,重构可带来巨大帮助。受进度驱动的经理要我尽可能快速完事,至于怎么完成,那就是我的事了。我认为最快的方式就是重构,所以我就重构。

间接层和重构——Kent Beck
大多数重构都为程序引入了更多间接层。
间接层是一柄双刃剑。每次把一个东西分成两份,你就需要多管理一个东西。如果某个对象委托另一个对象,后者又委托另一对象,程序会愈加难以阅读。
基于这个观点,你会希望尽量减少间接层。
别急,伙计!间接层的某些价值:
- 允许逻辑共享
- 分开解释意图和实现
- 隔离变化
- 封装条件逻辑
重构游戏最常见的变量:你如何看待你自己的程序。找出一个缺乏“间接层利益”之处,在不修改现有行为的前提下,为它加入一个间接层。现在你获得了一个更有价值的程序,因为它有较高的质量,让我们在明天(未来)受益。
请将这个方法与“小心翼翼的事前设计”做个比较。推测性设计总是试图在任何一行代码诞生之前就先让系统拥有所有优秀质量,然后程序员将代码塞进这个强健的骨架中就行了。这个过程的问题在于:太容易猜错。
还有一种比较少见的重构游戏:找出不值得的间接层,并将它拿掉。这种间接层常以中介函数形式出现。你本来希望在不同地点共享它,或让它表现出多态性,最终却只在一处用到。

(***笔记注解:***感觉上面文章翻译很垃圾,重构游戏原文我推测肯定是gaming译作"博弈",讨论的是程序员需要权衡的利弊问题)

2.5 重构的难题

数据库

重构经常出问题的一个领域就是数据库。绝大多数商用程序都与它们背后的数据库结构紧密耦合在一起,这也是数据库结构如此难以修改的原因之一。另一个原因是数据迁移(migration)。就算你非常小心地将系统分层,将数据库结构和对象模型间的依赖降至最低,但数据库结构的改变还是让你不得不迁移所有的数据,这可能是件漫长而繁琐的工作。

在非对象数据库中,解决这个问题的方法之一就是:在对象模型和数据库模型之间插入一个分隔层,这就可以隔离两个模型各自的变化。升级某一模型时无需同时升级另一模型,只需升级上述的分隔层即可。这样的分隔层会增加系统复杂度,但可以给你带来很大的灵活度。如果你同时拥有多个数据库,或如果数据库模型较为复杂使你难以控制,那么即使不进行重构,这分隔层也是很重要的。

你无需一开始就插入分隔层,可以在发现对象模型变得不稳定时再产生它,这样你就可以为你的改变找到最好的平衡点。

对于开发者而言,对象数据库既有帮助也有妨碍。

修改接口

对于接口要特别谨慎——如果接口被修改了,任何事情都可能发生。

一直对重构带来困扰的一件事就是:许多重构方法的确会修改接口。像Rename Method这么简单的重构方法所做的一切就是修改接口。

只有当需要修改的接口被那些"找不到,即使找到也不能修改"的代码使用时,接口的修改才会成为问题。如果情况真是如此,我就会说:这个接口是个已发布接口(published interface)——比公开接口(public interface)更进一步。接口一旦发布,你就再也没法仅仅修改调用者而能够安全地修改接口了。

如何面对那些必须修改“已发布接口”的重构方法?

如果重构方法改变了已发布接口,你必须同时维护新旧两个接口,直到所有用户都有时间对这个变化做出反应。 请尽量这么做:让旧接口调用新接口。 你还应该使用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文化中一个重要元素。

第1 重构,第一个案例 1 1.1 起点 1 1.2 重构的第一步 7 1.3 分解并重组statement() 8 1.4 运用多态取代与价格相关的条件逻辑 34 1.5 结语 52 第2 重构原则 53 2.1 何谓重构 53 2.2 为何重构 55 2.3 何时重构 57 2.4 怎么对经理说 60 2.5 重构的难题 62 2.6 重构设计 66 2.7 重构与性能 69 2.8 重构起源何处 71 第3 代码的坏味道 75 3.1 Duplicated Code(重复代码) 76 3.2 Long Method(过长函数) 76 3.3 Large Class(过大的类) 78 3.4 Long Parameter List(过长参数列) 78 3.5 Divergent Change(发散式变化) 79 3.6 Shotgun Surgery(霰弹式修改) 80 3.7 Feature Envy(依恋情结) 80 3.8 Data Clumps(数据泥团) 81 3.9 Primitive Obsession(基本类型偏执) 81 3.10 Switch Statements(switch惊悚现身) 82 3.11 Parallel InheritanceHierarchies(平行继承体系) 83 3.12 Lazy Class(冗赘类) 83 3.13 Speculative Generality(夸夸其谈未来性) 83 3.14 Temporary Field(令人迷惑的暂时字段) 84 3.15 Message Chains(过度耦合的消息链) 84 3.16 Middle Man(中间人) 85 3.17 Inappropriate Intimacy(狎昵关系) 85 3.18 Alternative Classes with Different Interfaces(异曲同工的类) 85 3.19 Incomplete Library Class(不完美的库类) 86 3.20 Data Class(纯稚的数据类) 86 3.21 Refused Bequest(被拒绝的遗赠) 87 3.22 Comments(过多的注释) 87 第4 构筑测试体系 89 4.1 自测试代码的价值 89 4.2 JUnit测试框架 91 4.3 添加更多测试 97 第5 重构列表 103 5.1 重构的记录格式 103 5.2 寻找引用点 105 5.3 这些重构手法有多成熟 106 第6 重新组织函数 109 6.1 Extract Method(炼函数) 110 6.2 Inline Method(内联函数) 117 6.3 Inline Temp(内联临时变量) 119 6.4 Replace Temp with Query(以查询取代临时变量) 120 6.5 Introduce Explaining Variable(引入解释性变量) 124 6.6 Split Temporary Variable(分解临时变量) 128 6.7 Remove Assignments to Parameters(移除对参数的赋值) 131 6.8 Replace Method with Method Object(以函数对象取代函数) 135 6.9 Substitute Algorithm(替换算法) 139 第7 在对象之间搬移特性 141 7.1 Move Method(搬移函数) 142 7.2 Move Field(搬移字段) 146 7.3 Extract Class(炼类) 149 7.4 Inline Class(将类内联化) 154 7.5 Hide Delegate(隐藏“委托关系”) 157 7.6 Remove Middle Man(移除中间人) 160 7.7 Introduce Foreign Method(引入外加函数) 162 7.8 Introduce Local Extension(引入本地扩展) 164 第8 重新组织数据 169 8.1 Self Encapsulate Field(自封装字段) 171 8.2 Replace Data Value with Object(以对象取代数据值) 175 8.3 Change Value to Reference(将值对象改为引用对象) 179 8.4 Change Reference to Value(将引用对象改为值对象) 183 8.5 Replace Array with Object(以对象取代数组) 186 8.6 Duplicate Observed Data(复制“被监视数据”) 189 8.7 Change Unidirectional Association to Bidirectional(将单向关联改为双向关联) 197 8.8 Change Bidirectional Association to Unidirectional(将双向关联改为单向关联) 200 8.9 Replace Magic Number with Symbolic Constant(以字面常量取代魔法数) 204 8.10 Encapsulate Field(封装字段) 206 8.11 Encapsulate Collection(封装集合) 208 8.12 Replace Record with Data Class(以数据类取代记录) 217 8.13 Replace Type Code with Class(以类取代类型码) 218 8.14 Replace Type Code with Subclasses(以子类取代类型码) 223 8.15 Replace Type Code with State/Strategy(以State/Strategy取代类型码) 227 8.16 Replace Subclass with Fields(以字段取代子类) 232 第9 简化条件表达式 237 9.1 Decompose Conditional(分解条件表达式) 238 9.2 Consolidate Conditional Expression(合并条件表达式) 240 9.3 Consolidate Duplicate Conditional Fragments(合并重复的条件片段) 243 9.4 Remove Control Flag(移除控制标记) 245 9.5 Replace Nested Conditional with Guard Clauses(以卫语句取代嵌套条件表达式) 250 9.6 Replace Conditional with Polymorphism(以多态取代条件表达式) 255 9.7 Introduce Null Object(引入Null对象) 260 9.8 Introduce Assertion(引入断言) 267 第10 简化函数调用 271 10.1 Rename Method(函数改名) 273 10.2 Add Parameter(添加参数) 275 10.3 Remove Parameter(移除参数) 277 10.4 Separate Query from Modifier(将查询函数和修改函数分离) 279 10.5 Parameterize Method(令函数携带参数) 283 10.6 Replace Parameter with Explicit Methods(以明确函数取代参数) 285 10.7 Preserve Whole Object(保持对象完整) 288 10.8 Replace Parameter with Methods(以函数取代参数) 292 10.9 Introduce Parameter Object(引入参数对象) 295 10.10 Remove Setting Method(移除设值函数) 300 10.11 Hide Method(隐藏函数) 303 10.12 Replace Constructor with Factory Method(以工厂函数取代构造函数) 304 10.13 Encapsulate Downcast(封装向下转型) 308 10.14 Replace Error Code with Exception(以异常取代错误码) 310 10.15 Replace Exception with Test(以测试取代异常) 315 第11 处理概括关系 319 11.1 Pull Up Field(字段上移) 320 11.2 Pull Up Method(函数上移) 322 11.3 Pull Up Constructor Body(构造函数本体上移) 325 11.4 Push Down Method(函数下移) 328 11.5 Push Down Field(字段下移) 329 11.6 Extract Subclass(炼子类) 330 11.7 Extract Superclass(炼超类) 336 11.8 Extract Interface(炼接口) 341 11.9 Collapse Hierarchy(折叠继承体系) 344 11.10 Form Tem Plate Method(塑造模板函数) 345 11.11 Replace Inheritance with Delegation(以委托取代继承) 352 11.12 Replace Delegation with Inheritance(以继承取代委托) 355 第12 大型重构 359 12.1 Tease Apart Inheritance(梳理并分解继承体系) 362 12.2 Convert Procedural Design to Objects(将过程化设计转化为对象设计) 368 12.3 Separate Domain from Presentation(将领域和表述/显示分离) 370 12.4 Extract Hierarchy(炼继承体系) 375 第13 重构,复用与现实 379 13.1 现实的检验 380 13.2 为什么开发者不愿意重构他们的程序 381 13.3 再论现实的检验 394 13.4 重构的资源和参考资料 394 13.5 从重构联想到软件复用和技术传播 395 13.6 小结 397 13.7 参考文献 397 第14 重构工具 401 14.1 使用工具进行重构 401 14.2 重构工具的技术标准 403 14.3 重构工具的实用标准 405 14.4 小结 407 第15 总结 409
一直很喜欢重构这本书,但是由于自己记性不太好,书看过之后其中的方法总是记不住,于是想如果有电子版的重构书就好了,工作中遇到重构的问题可以随时打开查阅。在网上搜索了许久,发现重构这本书有英文chm版本的,而中文版的电子书只有扫描的PDF版本,用起来非常不方便。于是萌生想做一本重构工具书的想法,本来打算自己重新将重构书的内容再整理归类一下,后来发现原书的目录编排就很适合做工具书,包括坏味道分类,重构手法归类等,都有了一个比较系统的整理。因此,我利用空余时间制作了这样的一本中文的chm版重构,希望对大家有所帮助,也算对中国软件业做出一点小小的贡献。 本书基本上是取自”重构”中文版一书的内容,但格式上参照的是chm英文版的格式,还有一些格式小修改,比如第一重构前后代码对比。因为时间匆促,个人能力有限,本书难免存在一些缺漏,如果大家发现有问题,随时可以给我发邮件,我会尽快更新错误的内容。 最后再次感谢几位大师 Martin Fowler、Kent Beck等,还有翻译的侯捷和熊节先生,为我们带来这么精彩的一本书。谢谢。 免责声明:本书仅供个人学习研究之用,不得用于任何商业目的,不得以任何方式修改本作品,基于此产生的法律责任本人不承担任何连带责任。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值