# 章节十五 集成
by Kent Beck
现在,你已经拥有了七巧板的每一块:你已经了解了重构的基础,知道了重构的分类,还实践了所有这些重构。同时,你已经很擅长测试了,所以你不再畏首畏尾。 于是你可能想:「我已经知道如何重构了。」不,还没有。
前面列出的技术仅仅是一个起点,是你登堂入室之前的大门。如果没有这些技术,你根本无法对运行中的程序进行任何设计上的改动。有了这些技术,你仍然做不到,但起码可以开始尝试了。
这些技术如此精彩,可它们却仅仅是个开始,这是为什么?答案很简单:因为你还 不知道何时应该使用它们、何时不应该使用、何时开始、何时停止、何时前进、何 时等待。使重构能够成功的,不是前面各自独立的技术,而是这种节奏。
当你真正懂得这一切时,你又是怎么知道的呢?当你开始冷静下来,你就会知道,自己已经真正「得道」了。那时候你将有绝对的自信:不论别人留下的代码多么杂 乱无章,你都可以将它变好,好到足以进行后续的开发。
不过,大多数时候,「得道」的标志是:你可以自信地停止重构。在重构者的整场表演中,「停止」正是压轴大戏。一开始你为自己选择一个大目标,例如「去掉一 堆不必要的subclass」。然后你开始向着这个目标前进,每一步都走得小而坚定,每一步都有备份,保证能够回头。好的,你离目标愈来愈近,愈来愈近,现在只剩两个函数需要合并,然后就将大功告成。
就在此时,意想不到的事情发生了:你再也无法前进一步。也许是因为时间太晚, 你太疲倦;也许是因为一开始你的判断就出错,实际上不可能去掉所有subclass; 也许是因为没有足够的测试来支持你。总而言之,你的自信灰飞烟灭了,你无法再自信满满地跨出下一步。你认为自己应该没把任何东西搞乱,但也无法确定。这是该停下来的时候了。如果代码已经比重构之前好,那么就把它集成到系统中, 发布你的成果。如果代码并没有变好,就果断放弃这些无用的工作,回到起始点。 然后,为自己学到一课而高兴,为这次重构没能成功而抱憾。那么,明天怎么办?
明天,或者后天,或者下个月,甚至可能明年,灵感总会来的。为了等待进行一项重构的后一半所需的灵感,我最多曾经等过九个月。你可能会明白自己错在哪里, 也可能明白自己对在哪里,总之都能使你想清楚下一个步骤如何进行。然后,你就可以像最初一样自信地跨出这一步。也许你羞愧地想:『我太笨了,竟然这么久都没想到这一步。』大可不必,每个人都是这样的。
这有点像在悬崖峭壁上的小径行走:只要有光,你就可以前进,虽然谨慎却仍然自信;但是一旦太阳下山,你就应该停止前进;夜晚你应该睡觉,并且相信明天早晨太阳仍旧升起。
这听起来似乎有点神秘而模糊,近乎清谈玄想。从感觉上来说,的确如此,因为这 是一种全新的编程方式。当你真正理解重构之后,系统的整个设计对你来说,就像源码文件中的字符那样可以随心所欲地操控。你可以直接感受到整个设计,可以清楚看到如何将设计变得更灵活,也可以看到如何修改它:这里修改一点,于是这样表现;那里修改一点,于是那样表现。
但是,从另一个角度来说,这也并非那么地祌秘而模糊。重构是一种可以学习的技术,你可以从本书读得并学习它的各个组成。然后,只要把这些技术集成在一起并使之完善,就可以从一个全新角度看待软件开发。
正如我所说,这是一种可以学习的技术。那么,应该如何学习呢?
-
随时挑一个目标。某个地方的代码开始发臭了,你就应该将问题解决掉。你 应该朝目标前进,达成目标后就停止。你之所以重构,不是为了探索真善美(至少不全是〕,而是为了让你的系统更容易被人理解,为了防止程序变得散乱。
-
没把握就停下来。朝目标前进的过程中,可能会有这样的时候:你无法证明自己所做的一切能够保持程序原本的语义。此时你就应该停下来。如果代码已经改善了一些,就发布你的成果;如果没有,就撤销所有修改。
-
学习原路返回。重构的原则不好学,而且很容易遗失准头。就连我自己,也 经常忘记这些原则。我有时会连续做两、三项甚至四项重构,而没有每次执行测试用例(test cases)。当然那是因为我完全相信,即使没有测试的帮助,我也不会出 错。于是我就放手干了。然后,「砰」的一声,某个测试失败,我却无法找到究竟哪一次修改造成了这个问题。
这时候你一定很愿意就地调试,试图从麻烦中脱身。毕竟,不管怎么说,一开始所有测试都能够正常运行,现在要让它们再次正常运行,会困难到哪里去?停!你的重构己经失控了,如果继续向前走,你根本不可能知道如何夺回控制权。你应该回到最近一个没有出错的状态,然后逐一重复刚才做过的重构项,每次重构之后一定要运行所有测试。
站着说话不腰疼,以上一切听起来似乎显而易见。当你出错的时候,使系统极大简化的一个方案也许已经近在咫尺,这时候要你停下来回到起点,不啻是最痛苦的事情。但是,现在,趁你头脑还清楚的时候,请想一想:如果你第一次重构用 了一小时,重复它只需十分钟就够了,所以如果你退回原点,十分钟之内一定能够再次达到现在的进度。但如果你继续前进,调试所需时间也许是五秒种,也许是两小时。
当然,我现在说这些,也是看人挑担不吃力,实际做起来困难得多。我个人曾经因为没有遵循这条建议,花了四个小时进行三次尝试。我失控、放弃、慢慢前进、再次失控、再重复……真是痛苦的四个小时。这不是有趣的事,所以你需要帮助。
- 二重奏。和别人一起重构,可以收到更好的效果。两人结伴,对于任何一种软件开发都有很多好处,对于重构也不例外。重构时,小心谨慎按部就班的态度是有好处的。如果两人结伴,你的搭档能够帮助你一步一步前进,你也能够帮助他 (她)。重构时,时刻留意远景目标是有好处的。如果两人结伴,你的搭档可能看到你没看到的东西,能想到你没想到的事情。重构时,明智结束是有好处的。如果你的搭档不知道你在干什么,那就意味你肯定也不知道自己在干什么,此时你就应该结束重构。最重要的是,重构时,拥有绝对自信是绝对有好处的。如果两人结伴,你的搭档能够给你温柔的鼓励,让你不致于灰心丧气。
与搭档协同工作的另一方面就是交谈。你必须讲出你所想做的事,这样你们两个才能朝着同一个方向努力。你得把你正在做的事情讲出来,这样你的搭档才有可能指出你的错误。你得把刚才做过的事情讲出来,这样下次遇到同样情况时你才能做得更好。所有这些交谈都有助于你更清楚了解如何让个别的重构项适应整个重构节 奏。
即使你已经在你的重构目标(代码〕中工作了好几年,一丝一缕了然于胸,但只要发现其中臭味,以及消除臭味的重构手法,你就有可能看到程序的另一种可能。你也许会想立刻挽起袖子,把你看到的所有问题都解决掉。不,不要这么莽撞。没有 一位经理愿意听到他的开发成员说「我们要停工三个月来清理以前的代码」。而且开发人员本来也就不应该这样做。大规模的重构只会带来灾难。
你面前的代码也许看起来混乱极了,不要着急,一点一点慢慢地解决这些问题。当你想要添加新功能时,用上几分钟时间把代码整理一下。如果首先添加一些测试能使你对整理工作更有信心,那就那么做,它们会回报你的努力。如果在添加新代码之前进行重构,那么添加新代码的风险将大大降低。重构可以使你更好理解代码的作用和工作方式,这使得新功能的添加更容易。而且重构之后代码的质量也会大大提高,下次你再有机会处理它们的时候,肯定会对目前所做的重构感到非常满意。
永远不要忘记「两顶帽子」。重构时你总会发现某些代码并不正确。你绝对相信自己的判断,因此想马上把它们改正过来。啊,顶住诱惑,别那么做。重构时你的目标之一就是保持代码的功能完全不变,既不多也不少。对于那些需要修改的东西,列个清单把它们记录下来(通常我在计算器旁边放一张索引卡),需要添加或修改 的测试用例(test cases)、需要进行的其他重构、需要撰写的文档、需要画的图…… 都暂时记在卡上。这样就不会忘掉这些需要完成的工作。千万别让这些工作打乱你 手上的工作。重构完成之后,再去做这些事情不迟。
- 译序 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 )
- 小结
- 章节十五 集成
- 参考书目