何谓重构
重构(名词):对软件内部结构的一种调整,目的是在不改变软件可观察行为的提下,提高其可理解性,降低其修改成本。
重构(动词):使用一系列重构手法,在不改变软件可观察行为的前提下,调整其结构。
重构的关键在于运用大量微小且保持软件行为的步骤,一步步达成大规模的修改。每个单独的重构要么很小,要么由若干小步骤组合而成。
两顶帽子
Kent Beck 提出了“两顶帽子”的比喻。
使用重构技术开发软件时,我把自己的时间分配给两种截然不同的行为:添加新功能和重构。
添加新功能时,我不应该修改既有代码,只管添加新功能。通过添加测试并让测试正常运行,我可以衡量自己的工作进度。
重构时我就不能再添加功能,只管调整代码的结构,此时我不应该添加任何测试(除非发现有先前遗漏的东西),只在绝对必要(用以处理接口变化)时才修改测试。
为何重构
重构是一个工具,它可以(并且应该)用于以下几个目的。
- 重构改进软件的设计。 如果没有重构,程序的内部设计(或者叫架构)会逐渐腐败变质。
- 重构使软件更容易理解。
- 重构帮助找到bug 。 如果对代码进行重构,我就可以深入理解代码的所作所为,并立即把新的理解反映在代码当中。搞清楚程序结构的同时,我也验证了自己所做的一些假设,于是想不把bug 揪出来都难。
- 重构提高编程速度。
何时重构
- 预备性重构:让添加新功能更容易重构的最佳时机就在添加新功能之前。
- 帮助理解的重构:使代码更易懂。
- 捡垃圾式重构:如果每次经过这段代码时都把它变好一点点,积少成多,垃圾总会被处理干净。重构的妙处就在于,每个小步骤都不会破坏代码—所以,有时一块垃圾在好几个月之后才终于清理干净,但即便每次清理并不完整,代码也不会被破坏。
- 有计划的重构:如果团队过去忽视了重构,那么常常会需要专门花一些时间来优化代码库,以便更容易添加新功能。
- 长期重构:每当有人靠近“重构区”的代码,就把它朝想要改进的方向推动一点。这个策略的好处在于,重下会破坏代码—每次小改动之后,整个系统仍然照常工作。例如,如果想替换一个正在使用的库,可以先引入一层新的抽象,使其兼容新旧两个库的接口。一旦调用方已经完全改为使用这层抽象,替换下面的库就会容易得多。
- Review代码时重构。
何时不应该重构
只有当我需要理解其工作原理时,对其进行重构才有价值。
如果我看见一块凌乱的代码,但并不需要修改它,那么我就不需要重构它。如果丑陋的代码能被隐藏在一个API之下,我就可以容忍它继续保持丑陋。
另一种情况是,如果重写比重构还容易,就别重构了。
重构的挑战
- 延缓新功能开发。
有一种情况确实需要权衡取舍。有时会看到一个(大规模的)重构很有必要进行,而马上要添加的功能非常小,这时我会更愿意先把新功能加上,然后再做这次大规模重构。
我们之所以重构,因为它能让我们更快,添加功能更快,修复更快。一定要随时记住这一点,与别人交流时也要不断强调这一点。重构应该总是由经济利益驱动。
- 代码所有权。
代码所有权的边界会妨碍重构,因为一旦我自作主张地修改,就一定会破坏使用者的程序。这不会完全阻止重构,我仍然可以做很多重构,但确实会对重构造成约束。
- 分支。
很多团队采用这样的版本控制实践:每个团队成员各自在代码库的一条分支上工作,进行相当大量的开发之后,才把各自的修改合开回主线分支,从而与整个团队分享。这样的特性分支有其缺点。在隔离的分支上工作得越久,将完成的工作集成(integrate)回主线就会越困难。
- 测试。
不会改变程序可观察的行为,这是重构的一个重要特征。
这里的关键就在于“快速发现错误”。要做到这一点,我的代码应该有一套完备的测试套件,并且运行速度要快,否则我会不愿意频繁运行它。也就是说,绝大多数情况下,如果想要重构,我得先有可以自测试的代码。
自测试代码与持续集成紧密相关—我们仰赖持续集成来及时捕获分支集成时的语义冲突。自测试代码是极限编程的另一个重要组成部分,也是持续交付的关键环节。
缺乏测试的问题可以用另一种方式来解决。如果我的开发环境很好地支持自动化重构,我就可以信任这些重构,不必运行测试。这时即便没有完备的测试套件,我仍然可以重构,前提是仅仅使用那些自动化的、一定安全的重构手法。
- 遗留代码。
重构可以很好地帮助我们理解遗留系统。但我们绕不开关底的恶龙:遗留系统多半没测试。如果你面对一个庞大而又缺乏测试的遗留系统,很难安全地重构清理它。
我能给出的最好建议就是买一本《修改代码的艺术》,照书里的指导来做。一言以蔽之,它建议你先找到程序的接缝,在接缝处插入测试,如比将系统置于测试覆盖之下。你需要运用重构手法创造出接缝一这样的重构很危险,因为没有测试覆盖,但这是为了取得进展必要的风险。
- 数据库。
数据库重构的关键也是小步修改并且每次修改都应该完,这样每次迁移之后系统仍然能运行。由于每次迁移涉及的修改都很小,写起来应该容易;将多个迁移串联起来,就能对数据库结构及其中存储的数据做很大的调整。
很多时候,数据库重构最好是分散到多次生产发布来完,这样即便某次修改在生产数据库上造成了问题,也比较容易回滚。
重构与性能
虽然重构可能使软件运行更慢,但它也使软件的性能优化更容易。除了对性能有严格要求的实时系统,其他任何情况下“编写快速软件”的秘密就是:先写出可调优的软件,然后调优它以求获得足够的速度。