### 重构起源何处?
我曾经努力想找出重构(refactoring) 一词的真正起源,但最终失败了。优秀程序员肯定至少会花一些时间来清理自己的代码。这么做是因为,他们知道简洁的代码比杂乱无章的代码更容易修改,而且他们知道自己几乎无法一开始就写出简洁的代码。
重构不止如此。本书中我把重构看做整个软件开发过程的一个关键环节。最早认识重构重要性的两个人是Ward Cunningham 和 Kent Beck,他们早在1980s之前就开始使用Smalltalk,那是个特别适合重构的环境。Smalltalk是一个十分动态的环境,你可以很快写出极具功能的软件。Smalltalk的「编译/连结/执行」周期非常短,因 此很容易快速修改代码。它是面向对象,所以也能够提供强大工具,最大限度地将修改的影响隐藏于定义良好的接口背后。Ward和Kent努力发展出一套适合这类环境的软件开发过程〔如今Kent把这种风格叫作极限编程[Beck,XP])。他们意识到:重构对于提高他们的生产力非常重要。从那时起他们就一直在工作中运用重构技术,在严肃而认真的软件项目中使用它,并不断精炼这个程序。
Ward和Kent的思想对Smalltalk社群产生了极大影响,重构概念也成为Smalltalk文化中的一个重要元素。Smalltalk社群的另一位领袖是Ralph Johnson,伊利诺斯大 学乌尔班纳分校教授,著名的「四巨头」[3][Gang of Four]之一。Ralph最大的兴趣之一就是开发软件框架(framework)。他揭示了重构对于灵活高效框架的开发帮助。
[3]译注:Ralph Johnson和另外三位先生Erich Gamma,Richard Helm, John Vissides合写了软件开发界驰名的《Design Patterns》,人称四巨头(Gang of Four)。
Bill Opdyke是Ralph的博士研究生,对框架也很感兴趣。他看到重构的潜在价值,并看到重构应用于Smalltalk之外的其他语言的可能性。他的技术背景是电话交换系统的开发。在这种系统中,大量的复杂情况与时俱增,而且非常难以修改。Bill的博士研究就是从工具构筑者的角度来看待重构。通过研究,Bill发现:在C++ framework开发项目中,重构很有用。他也研究了极有必要的「语义保持性 (semantics-preserving)重构」及其证明方式,以及如何以工具实现重构。时至今日,Bill的博士论文[Opdyke]仍然是重构领域中最有价值、最丰硕的研究成果。此外他为本书撰写了第13章。
我还记得1992年OOPSLA大会上见到Bill的情景。我们坐在一间咖啡厅里,讨论当时我正为保徤业务构筑的一个概念框架(conceptual framework)中的某些工作。Bill跟我谈起他的研究成果,我还记得自己当时的想法:『有趣,但并非真的那么重要。』唉,我完全错了。
John Brant 和 Don Roberts 将重构中的「工具」构想发扬光大,开发了 一个名为「重构浏览器」(Refactoring Browser)的Smalltalk重构工具。他们撰写了本书第14 章,其中对重构工具做了更多介绍。
那么,我呢?我一直有清理代码的倾向,但从来没有想到这会有那么重要。后来我和Kent一起做了个项目,看到他使用重构手法,也看到重构对生产性能和产品质量带来的影响。这份体验让我相信:重构是一门非常重要的技术。但是,在重构的学习和推广过程中我遇到了挫折,因为我拿不出任何一本书给程序员看,也没有任何一位专家打算写出这样一本书。所以,在这些专家的帮助下,我写下了这本书。
**优化一个薪资系统**
—— Rich Carzaniti
将 Chrysler Comprehensive Compensation(克莱斯勒综合薪资系统)交给GemStone公司之前,我们用了相当长的时间开发它。开发过程中我们无可避免地发现程序不够快,于是找了Jim Haungs——GemSmith中的一位好手——请他帮我们优化这个系统。
Jim先用一点时间让他的团队了解系统运作方式,然后以GemStone的ProfMonitor特性编写出一个性能量测工具,将它插入我们的功能测试中。这个工具可以显示系统产生的对象数量,以及这些对象的诞生点。
令我们吃惊的是:创建量最大的对象竟然是字符串,其中最大的工作量则是反复产生12,000-byte的字符串。这很特别,因为字符串实在太大了,连GemStone惯用的垃圾回收设施都无法处理它。由于它是如此巨大,每当被创建出来,GemStone都会将它分页(paging)至磁盘上。也就是说字符串的创建竟然用上了I/O子系统(译注:分页机制会动用I/O),而每次输出记录时都要产生字样的字符串三次!
我们的第一个解决办法是把一个12,000-bytes字符串缓存(cached)起来,这可解决一大半问题。后来我们又加以修改,将它直接写入一个file stream,从而避免产生字符串。
解决了「巨大字符串」问题后,Jim的量测工具又发现了一些类似问题,只不过字符串稍微小一些:800-bytes,500-bytes...等等,我们也都对它们改用file stream,于是问题都解决了。
使用这些技术,我们稳步提高了系统性能。开发过程中原本似乎需要1,000小时以上才能完成的薪资计算,实际运作时只花40小时。一个月后我们把时间缩短到18小时。正式投入运转时只花12小时。经过一年的运行和改善后,全部计算只需9小时。
我们最大的改进就是:将程序放在多处理器(multi-processor)计算机上,以多线程(multiple threads)方式运行。最初这个系统并非按照多线程思维来设计,但由于代码有良好分解(well factored),所以我们只花三天时间就让它得以同时运行多个线程了。现在,薪资的计算只需2小时。
在Jim提供工具使我们得以在实际操作中量度系统性能之前,我们也猜测过问题所在。但如果只靠猜测,我们需要很长的时间才能试出真正的解法。真实的量测指出了一个完全不同的方向,并大大加快了我们的进度。
- 译序 by 侯捷
- 译序 by 熊节
- 序言
- 前言
- 章节一 重构,第一个案例
- 起点
- 重构的第一步
- 分解并重组statement()
- 运用多态(Polymorphism)取代与价格相关的条件逻辑
- 结语
- 章节二 重构原则
- 何谓重构
- 为何重构
- 「重构」助你找到臭虫(bugs)
- 何时重构
- 怎么对经理说?
- 重构的难题
- 重构与设计
- 重构与性能(Performance)
- 重构起源何处?
- 章节三 代码的坏味道
- Duplicated Code(重复的代码)
- Long Method(过长函数)
- Large Class(过大类)
- Long Parameter List(过长参数列)
- Divergent Change(发散式变化)
- Shotgun Surgery(散弹式修改)
- Feature Envy(依恋情结)
- Data Clumps(数据泥团)
- Primitive Obsession(基本型别偏执)
- Switch Statements(switch惊悚现身)
- Parallel Inheritance Hierarchies(平行继承体系)
- Lazy Class(冗赘类)
- Speculative Generality(夸夸其谈未来性)
- Temporary Field(令人迷惑的暂时值域)
- Message Chains(过度耦合的消息链)
- Middle Man(中间转手人)
- Inappropriate Intimacy(狎昵关系)
- Alternative Classes with Different Interfaces(异曲同工的类)
- Incomplete Library Class(不完美的程序库类)
- Data Class(纯稚的数据类)
- Refused Bequest(被拒绝的遗贈)
- Comments(过多的注释)
- 章节四 构筑测试体系
- 自我测试代码的价值
- JUnit测试框架
- 添加更多测试
- 章节五 重构名录
- 重构的记录格式
- 寻找引用点
- 这些重构准则有多成熟
- 章节六 重新组织你的函数
- Extract Method(提炼函数)
- Inline Method(将函数内联化)
- Inline Temp(将临时变量内联化)
- Replace Temp with Query(以查询取代临时变量)
- Introduce Explaining Variable(引入解释性变量)
- Split Temporary Variable(剖解临时变量)
- Remove Assignments to Parameters(移除对参数的赋值动作)
- Replace Method with Method Object(以函数对象取代函数)
- Substitute Algorithm(替换你的算法)
- 章节七 在对象之间搬移特性
- Move Method(搬移函数)
- Move Field(搬移值域)
- Extract Class(提炼类)
- Inline Class(将类内联化)
- Hide Delegate(隐藏「委托关系」)
- Remove Middle Man(移除中间人)
- Introduce Foreign Method(引入外加函数)
- Introduce Local Extension(引入本地扩展)
- 章节八 重新组织数据
- Self Encapsulate Field(自封装值域)
- Replace Data Value with Object(以对象取代数据值)
- Change Value to Reference(将实值对象改为引用对象)
- Replace Array with Object(以对象取代数组)
- Replace Array with Object(以对象取代数组)
- Duplicate Observed Data(复制「被监视数据」)
- Change Unidirectional Association to Bidirectional(将单向关联改为双向)
- Change Bidirectional Association to Unidirectional(将双向关联改为单向)
- Replace Magic Number with Symbolic Constant(以符号常量/字面常量取代魔法数)
- Encapsulate Field(封装值域)
- Encapsulate Collection(封装群集)
- Replace Record with Data Class(以数据类取代记录)
- Replace Type Code with Class(以类取代型别码)
- Replace Type Code with Subclasses(以子类取代型别码)
- Replace Type Code with State/Strategy(以State/strategy 取代型别码)
- Replace Subclass with Fields(以值域取代子类)
- 章节九 简化条件表达式
- Decompose Conditional(分解条件式)
- Consolidate Conditional Expression(合并条件式)
- Consolidate Duplicate Conditional Fragments(合并重复的条件片段)
- Remove Control Flag(移除控制标记)
- Replace Nested Conditional with Guard Clauses(以卫语句取代嵌套条件式)
- Replace Conditional with Polymorphism(以多态取代条件式)
- Introduce Null Object(引入Null 对象)
- Introduce Assertion(引入断言)
- 章节十一 处理概括关系
- Pull Up Field(值域上移)
- Pull Up Method(函数上移)
- Pull Up Constructor Body(构造函数本体上移)
- Push Down Method(函数下移)
- Push Down Field(值域下移)
- Extract Subclass(提炼子类)
- Extract Superclass(提炼超类)
- Extract Interface(提炼接口)
- Collapse Hierarchy(折叠继承关系)
- Form Template Method(塑造模板函数)
- Replace Inheritance with Delegation(以委托取代继承)
- Replace Delegation with Inheritance(以继承取代委托)
- 章节十二 大型重构
- 这场游戏的本质
- Tease Apart Inheritance(梳理并分解继承体系)
- Convert Procedural Design to Objects(将过程化设计转化为对象设计)
- Separate Domain from Presentation(将领域和表述/显示分离)
- Extract Hierarchy(提炼继承体系)
- 章节十三 重构,复用与现实
- 现实的检验
- 为什么开发者不愿意重构他们的程序?
- 现实的检验(再论)
- 重构的资源和参考资料
- 从重构联想到软件复用和技术传播
- 结语
- 参考文献
- 章节十四 重构工具
- 使用工具进行重构
- 重构工具的技术标准(Technical Criteria )
- 重构工具的实用标准(Practical Criteria )
- 小结
- 章节十五 集成
- 参考书目