重构的难题
虽然我坚决认为你应该尝试一下重构,获得它所提供的利益,但与此同时,你也应该时时监控其过程,注意寻找重构可能引入的问题。请让我们知道你所遭遇的问题。随着对重构的了解日益增多,我们将找出更多解决办法,并清楚知道哪些问题是真正难以解决的。
数据库
重构经常出问题的一个领域就是数据库。绝大多数商用程序都与它们背后的数据库结构紧密耦合在一起,这也是数据库结构如此难以修改的原因之一。另一个原因是数据迁移(migration)。就算你非常小心地将系统分层,将数据库结构和对象模型间的依赖降至最低,但数据库结构的改变还是让你不得不迁移所有数据,这可能是件漫长而烦琐的工作。
在非对象数据库中,解决这个问题的办法之一就是:在对象模型和数据库模型之间插入一个分隔层,这就可以隔离两个模型各自的变化。升级某一模型时无需同时升级另一模型,只需升级上述的分隔层即可。这样的分隔层会增加系统复杂度, 但可以给你带来很大的灵活度。如果你同时拥有多个数据库,或如果数据库模型较为复杂使你难以控制,那么即使不进行重构,这分隔层也是很重要的。
你无需一开始就插入分隔层,可以在发现对象模型变得不稳定时再产生它,这样你就可以为你的改变找到最好的平衡点。
对开发者而言,对象数据库既有帮助也有妨碍。某些面向对象数据库提供不同版本的对象之间的自动迁移功能,这减少了数据迁移时的工作景,但还是会损失一定时间。如果各数据库之间的数据迁移并非自动进行,你就必须自行完成迁移工作, 这个工作量可是很大的。这种情况下你必须更加留神类中的数据结构变化。你仍然可以放心将类的行为转移过去,但转移字段时就必须格外小心。数据尚未被转移前你就得先运用访问函数造成“数据己经转移”的假象。一旦你确定知道数据应该放在何处,就可以一次性地将数据迁移过去。这时唯一需要修改的只有访问函数,这也降低了错误风险。、
修改接口
关于对象,另一件重要事情是:它们允许你分开修改软件模块的实现和接口。 你可以安全地修改对象内部实现而不影响他人,但对于接口要特别谨慎——如果接口被修改了,任何事情都有可能发生。
简言之,如果重构手法改变了已发布接口,你必须同时维护新旧两个接口,直到所有用户都有时间对这个变化做出反应。幸运的是,这不太困难。你通常都有办法把事情组织好,让旧接口继续工作。请尽量这么做:让旧接口调用新接口。当你要修改某个函数名称时,请留下旧函数,让它调用新函数。千万不要复制函数实现, 那会让你陷入重复代码的泥淖中难以自拔。你还应该使用Java提供的deprecation (不建议使用)设施,将旧接口标记为deprecated。这么一来你的调用者就会注意到它了。
“保留旧接口”的办法通常可行,但很烦人。起码在一段时间里你必须构造并维护一些额外的函数。它们会使接口变得复杂,使接口难以使用。还好我们有另一个选择:不要发布接口。。发布接口很有用,但也有代价。所以除非真有必要,不要发布接口。这吋能意味需要改变你的代码所有权观念,让每个人都可以修改别人的代码,以适应接口的改动。以结对编程的方式完成这一切通常是个好主意。
不要过早发布接口。请修改你的代码所有权政策,使重构更顺畅。
JavaiE有一种特别的接口修改:在throws子句中增加一个异常。这并不是对函数签名的修改,所以你无法以委托的办法隐藏它;但如果用户代码不做出相应修改, 编译器不会让它通过。
难以通过重构手法完成的设计变动
这种情况下我的办法就是:先想象重构的情况。考虑候选设计方案时,我会问自己:将某个设计重构为另一个设计的难度有多大?如果看上去很简单,我就不必太担心选择是否得当,于是我就会选最简单的设计,哪怕它不能覆盖有冇潜在需求也没关系。。但如果预先看不到简单的重构办法,我就会在设计上投入更多力气。不过我发现,后一种情况很少出现。
何时不该重构
重写(而非重构)的一个清楚讯号就是:现有代码根本不能正常运作。你可能只是试着做点测试,然后就发现代码中满是错误,根本无法稳定运作。记住,重构之前,代码必须起码能够在大部分情况下正常运作。
一个折中办法就是:将“大块头软件”重构为封装良好的小型组件。然后你就可以逐一对组件做出“重构或重建”的决定。这是一个颇有希望的办法,但我还没有足够数据,所以也无法写出好的指导原则。对于一个重要的遗留系统,这肯定会是一个很好的方向。
另外,如果项目已近最后期限,你也应该避免重构。在此时机,从重构过程赢得的生产力只有在最后期限过后才能体现出来,而那个时候已经为时晚矣。
如果项目已经非常接近最后期限,你不应该再分心于重构,因为已经没有时间了。不过多个项目经验显示:重构的确能够提高生产力。如果最后你没有足够时间, 通常就表示你其实早该进行重构。