机会主义重构

2011年11月1日

从我开始谈论和撰写重构之初,人们就问我如何将其纳入更广泛的软件开发流程中。软件开发生命周期中是否应该有重构阶段,迭代中应该将多少比例用于重构任务,我们应该如何确定谁应该负责重构工作?虽然有些地方需要进行一些计划内的重构工作,但我更倾向于鼓励将重构作为一种机会主义的活动,在任何需要清理代码的时间和地点进行,由任何人进行。

这意味着,任何时候,只要有人看到一些代码不够清晰,他们就应该抓住机会当场修复它,或者至少在几分钟内修复它。这种机会主义重构通常被称为遵循露营规则——始终让代码处于比你发现它时更好的状态。如果团队中的每个人都这样做,他们每天都会对代码库的健康做出小的、定期的贡献。

这种机会可能出现在实现某些新功能或修复错误的各个阶段。一种是准备性重构,在你开始实现某些东西之前,你发现如果现有类的 API 结构不同,这项任务会更容易。你先把它重构到你认为应该的样子,然后开始添加你的功能。

当你添加功能时,你意识到你添加的一些代码与一些现有代码存在重复,所以你需要重构现有代码来清理。这种对代码的持续关注很重要——但请记住,你应该只在测试通过时才进行重构。

你可能已经让某些东西可以工作了,但你意识到如果改变与现有类的交互方式会更好。在你认为自己完成之前,抓住机会去做这件事。

有时,当你正在做其他事情的时候,你会看到一个机会。与其打断你现在的思路,不如把它记下来,等你准备好了再回来处理它。不要把它放太久,在同一天回来,在你完成最后一点之前。

有些人反对这种重构,认为它占用了开发有价值功能的时间。但重构的全部意义在于,它使代码库更容易使用,从而使团队能够更快地增加价值。如果你不花时间抓住机会进行重构,那么代码库会逐渐退化,你将面临进度缓慢的困境,以及与赞助商就重构迭代进行艰难对话的局面。

这里确实存在掉进兔子洞的危险,因为当你修复了一件事,你又发现了另一件事,以及另一件事,不知不觉中你就深陷泥潭。熟练的机会主义重构需要良好的判断力,你需要决定何时收手。你希望代码比你发现它时更好,但也可以等到下次访问时再把它变成你真正想要的样子。如果你总是让事情变得更好一点,那么反复的应用将会产生巨大的影响,而且集中在经常访问的区域——而这些区域正是干净代码最有价值的地方。像编程的大多数方面一样,这个决定需要深思熟虑。

机会主义重构的一个特点是,它可以触及你正在处理的代码库的任何部分。你可能在一个类中完成了大部分工作,但在代码库中完全不同区域的另一个类中发现了问题。这种局部性的缺乏不应该阻止你现在就进行更改。人们常常倾向于将代码库中其他部分的更改留到改天再做——但改天往往遥遥无期。

重构确实依赖于一套良好的回归测试套件,如果你认为你即将触及应用程序中测试比应有水平弱的部分,那么谨慎行事是明智的。在这种情况下,请记住,如果你能在不偏离主题太远的情况下添加一两个额外的测试,这是完全合理的。我还发现,故意制造一个错误,看看测试是否能捕捉到它,可以让你了解你的安全网有多好。

我对任何会导致机会主义重构产生摩擦的开发实践持谨慎态度,比如强烈的代码所有制或使用功能分支。这实际上是我使用功能分支的主要担忧。通常,当人们使用功能分支时,他们不鼓励进行机会主义重构,因为它会使合并变得更加困难[1]——特别是当分支存活时间超过几天时。

我的感觉是,大多数团队都没有进行足够的重构,所以关注任何阻碍人们进行重构的因素非常重要。为了帮助解决这个问题,请注意任何时候你感到不鼓励进行小型重构,那些你确定只需要一两分钟就能完成的重构。任何这样的障碍都是一种应该引起讨论的气味。所以,记下这种不鼓励,并与团队提出。至少应该在下一次回顾会议上讨论这个问题。

从一开始,我一直认为重构是你持续做的事情,就像输入 if 语句一样,是编程中不可分割的一部分。然而,关于重构有一个常见的误解,那就是它需要事先计划好。当然,计划内的重构工作是有其意义的,甚至可以留出一两天时间来处理一个棘手的代码块,这个代码块已经困扰了每个人好几个月了。但是,一个善于重构的团队几乎不需要计划重构,而是将重构视为持续不断的小调整流,这些调整使项目保持在设计耐力假说的快乐曲线上。

延伸阅读

我的关于重构工作流程的信息平台讨论了将重构纳入你的工作的不同方法。

Ron Jeffries 想出了一个可爱的视觉化来描述如何通过混乱的代码逐步重构,以及为什么你的待办事项列表中不应该有重构任务。

技术债务的比喻非常适用于这些问题。

注释

1: 现代工具有所帮助,但仍然会被语义冲突绊倒。