重构精髓:十六字心法
Last updated
Was this helpful?
Last updated
Was this helpful?
上一步替换常量、改变函数签名的过程中,有没有同学一上来就将整个构造函数或者as()
方法的签名替换掉?如果这么操作,在你一口气把 10+个函数的引用点修改过来之前,软件将一直处于不可工作的状态。这节内容,我们要向你介绍一种更精细的做法,也就是《重构》一书里反复展演的重构手法:如何让代码在重构的过程一直保持可工作。
这套手法背后的原理,ThoughtWorks 的王健将之总结为一句十六字心法,那就是:
旧的不变
新的创建
一步切换
旧的再见
什么?你说心法太难懂?那下面我们以替换as()
方法签名为例(as(String targetUnit)
-> as(Unit targetUnit)
)来理解一下这个过程:
“旧的不变,新的创建”,指的是对于你要重构的编程元素(这里是指as()
方法的String unit
字符串变量),先不去做直接的修改或替换,也不直接删除它,而是先创建一份新的拷贝,并传入你期望的参数类型。做完以后,运行一下测试,因为新函数并没有任何调用点,所以此时测试应该都能够正常通过。
创建完新方法后,你需要将旧方法内的实现直接委托给新方法:
做完这步后,同样需要运行一下测试。由于新函数内仍然是原来的方法体,而旧方法的对外接口并没有改变,因此测试仍然不太应该挂掉。如果挂了,你应该能很快地撤销,并发现问题所在,修改以后再运行测试。
这一步的作用是建立一个中间层:对旧方法的引用点仍然不改变,因此你不需要一下改变方法的所有调用点。这一步的作用很关键,因为它创建了一种“新旧共存”的机制,这使得你能逐步完成替换,随之带来的变化是:你可以在重构的任意一步中停止,同时保证系统仍然处于可用的状态。
事实上,在做完这步后,你已经可以提交代码,然后再慢慢地一步步将旧方法的引用点切换到新的方法上去,系统在重构的整个过程中将仍然保持可用的状态。
接下来,我们要在temp_as
中使用Unit
类型的temp_unit
取代原来的unit
,可以在as()
方法中完成这个调整:
做完这步以后,运行测试。测试应该也不会挂,因为我们只是新增了一个尚未被使用到的变量。接下来,我们再把这个变量在temp_as
中用上:
运行测试。
搭建好新旧函数之间的桥梁以后,下一步要做的是找到所有的引用点并一一替换。我们在老的as()
上使用快捷键cmd + B
查找它的所有引用点如下:
好家伙,还真不少,引用点一共有 11 个。但不要紧,因为我们已经搭建好了新旧方法的桥梁,可以慢慢替换。首先找到第一处引用点,将它替换为直接调用temp_as()
,运行测试。测试应该能完全通过。
对于剩余的引用点,我们如法炮制,直至把所有的as(String unit)
都替换成为temp_as(String unit, Units temp_unit)
。这样,我们就完成了“替换as()
方法参数类型”的重构。后续as()
方法仍然在其他地方使用了unit
参数,对这些地方的替换,就留给你去练习了。
这里也就显示出真正的重构技巧与一般的刀砍斧劈之间的差别了:我们重构的每一步都以极小的步子前进,随时都能保证测试通过、系统正常。更重要的是,这个过程是随时可以停止的,在真实的项目中,你可能没有大段的时间重构,可能会不时出现优先级更高的事情打断手头的工作。有了随时停止的能力,你就可以将宏大的重构目标分解成许多细小的动作,日拱一卒,哪怕每天空闲的时候花上 5 分钟重构、提交,最终也能安全地、聚沙成塔地完全大的重构目标。
完成所有的引用点替换后,IDE 随即也会提示我们:as(String unit)
已经没有引用点了。此时,我们就可以把这个函数安全地删除,运行一下测试。测试应该依然能通过,我们也完成了一次简单的重构。
最后,我们把那个临时的方法名temp_as(Unit temp_unit)
重新命名回来,为它重新正名:as(Unit unit)
,此时,我们甚至可以将它改成更加表意的as(Unit target)
。
这节的信息量有点大,所以,我录了一个视频,你可以放松地看一下,直观感受一下这个心法的实操过程。