重构的本质:以功能为导向的代码优化实践

本文深入探讨了重构在软件开发中的核心原则,强调重构应始终服务于产品功能目标,而非单纯为了代码整洁。通过实际案例和比喻,阐述了如何平衡重构与功能开发,避免过度工程化,并分享了提高团队协作效率的实用技巧。

重构的本质:以功能为导向

作者:Max Kanat-Alexander
发布日期:2017年5月2日

在书中我曾提到一个观点,但后来仍需多次向人们强调,因此我想再深入探讨一下。当你清理代码时,始终是在为产品服务。重构本质上是一个组织过程(这里的“组织”不是指“与业务相关”,而是指“使事物有序”)。也就是说,你通过整理代码来实现某些功能。

如果你仅仅为了重构而重构,重构就会背上坏名声。人们会认为你在浪费时间,你会失去信誉,你的经理或同事会阻止你继续工作。所谓“仅仅为了重构而重构”,指的是查看一段与你实际工作无关的代码,说“我不喜欢这个设计方式”,然后在不影响系统功能的情况下调整设计部分。这就像房子着火时还在浇水草坪。如果你的代码库像我见过的大多数代码库一样,“房子着火”可能都是一个恰当的比喻。即便如此,如果情况没那么糟,关键是你专注于不需要关注的东西。你可能觉得自己在重组代码方面做得很好,而且可能确实如此,但浇水草坪的目的是让房子前的草坪更美观。如果你的重构与当前产品或系统功能目标无关,那么你实际上只是在重新排列一些没人使用、涉及或关心的东西,没有取得任何实际成果。

那么你应该做什么?通常,你应该选择一个想要实现的功能,并找出可以通过重构来简化实现的部分。或者,你找到代码中经常被修改的区域,并在该区域进行一些重组。这会让他人欣赏你的工作。不仅如此,他们之所以会欣赏,是因为你在做有效的事情。但获得对你所做工作的赞赏——或至少某种形式的礼貌认可——可以帮助鼓励你继续,显示其他人开始关心你的工作,并有望帮助在整个公司传播良好的开发实践。

是否有时候你会处理一个与你必须做的工作没有直接关系的重构项目?有时,你会重构与你的目标间接相关的东西。有时,当你开始处理一个特别复杂的问题时,就像在海滩上捡石头以到达底部的沙子。你试图移动一块石头,却发现首先必须移动另一块石头。然后你发现那块石头靠着一块大巨石,而巨石周围有石头阻止它被移动,等等。

因此,在合理范围内,你必须处理阻止你进行重构的问题。如果这些问题变得足够大,你需要一名专职工程师来解决这些问题——特别是那些阻止重构本身的问题。(例如,也许你的代码依赖或其构建系统如此复杂,以至于没人能在任何地方移动代码,如果这是一个足够大的问题,可能需要一个人花费数月的工作。)当然,理想情况下,你永远不会陷入问题如此之大以至于个人无法在正常工作中移动它们的情况。实现这一目标的方法是遵循增量开发和设计的原则,并始终让系统看起来像是为完成当前工作而设计的。

但假设你像世界上大多数软件项目一样没有这样做,那么你现在处于某种糟糕的境地,需要从系统埋下的石头堆中挖出来。我不会为此感到难过,主要是因为感到难过并不能真正完成任何事情。与其感到难过或困惑,你需要做的是拥有某种系统,让你能够逐步解决问题,并从当前位置达到更好的状态。这比在开发过程中保持系统良好设计要复杂得多,但它是可以做到的。清理复杂代码库的关键原则是始终以功能为导向进行重构。

看,问题是你有这座“石头”山。你有一个像着火的房子,只不过房子有几座山那么大,而且一直在着火。你需要找出你实际需要的“山”或“房子”的部分,并通过一系列小步骤将其整理好以便“使用”。这个比喻并不完美,因为火灾是暂时的、危险的、危及生命的。它也会比你清理的速度更快地摧毁东西。但有时代码库实际上处于这种状态——它变坏的速度比变好的速度更快。这是另一个原则:你的第一个目标是让系统进入一个随时间变好而不是变坏的状态。

这些实际上是相同的原则,尽管它们听起来完全不同。怎么会这样?因为你让代码库随时间变好而不是变坏的方法是让人们在他们添加功能之前重构他们即将添加功能的代码。

你看一段代码。假设它是生成公司员工名单的代码。你必须添加一个新功能来按雇佣日期排序列表。你阅读代码,但无法理解变量名的含义。因此,在添加新功能之前,你要做的第一件事是进行一个单独的、自包含的更改,以改进变量名。完成后,你仍然无法理解代码,因为它都在一个包含1000行代码的函数中。因此,你将其拆分成几个函数。也许现在它足够好了,你觉得添加新的排序功能会很简单。但如果你在使用面向对象语言,你可能想在继续之前将这些函数更改为设计良好的对象。这完全取决于你——基本点是你应该让事情变得更好,而且它们变好的速度应该比变坏的速度更快。至于你走多远,这是一个判断点。你必须平衡你需要在前进功能目标上取得进展的事实,以及你不能永远重构代码的事实。

通常,我会为我的代码设置一些边界,比如“我不会为了完成这个功能而重构项目之外的任何东西”,或者“我不会在发布这个功能之前等待编程语言本身的更改”。但在我的边界内,我努力做好工作。我尝试将边界设置得尽可能宽,而不会陷入无法实际开发功能的情况。通常,这既是一个时间边界,也是一个“代码库范围”(比如,离我的代码库多远)的边界——时间部分通常是最重要的,比如“我不会花三个月的时间来开发一个两天的功能”。但即便如此,我倾向于在重构上花费时间,特别是当我第一次在代码库中这样做时,它是一个新事物,而且整个事情非常混乱。

这引出了另一点——尽管你可能认为重构然后开发功能会花费更多时间,但根据我的经验,总体时间通常更少或相同。这里的“总体”包括你花费在调试、回滚发布、发送错误修复、为复杂系统编写测试等所有时间。在不重构的情况下在复杂系统中编写功能可能看起来更快,有时确实如此,但大多数时候,如果你在开始添加新功能之前先做好系统整理工作,总体时间会更少。这不仅仅是理论上的——我已经多次证明这一点。实际上,当我这样做时,我的团队比使用更好工具处理新代码库的团队更快完成项目。(也就是说,另一支团队本应能够超越我们,但我们持续以产品为导向进行重构,总是更快发布,并且在功能方面实际上领先,两个项目上的开发人员数量大致相同,处理非常相似的功能。)

我用来决定何时“完成”重构特定代码段的另一点是,我认为其他人将能够清楚地看到我设计的模式,并从此以后按照该模式维护代码。有时我必须编写一小段文档来描述系统的预期设计,以便人们遵循,但总的来说,我的理论(这真的只是一个理论——我还没有足够的证据)是,如果我设计一段代码足够好,它不应该需要一段文档来描述它应该如何设计。从阅读代码中应该可以看出它是如何设计的,并且在该设计中添加新功能的方式应该如此明显,以至于没人会以其他方式这样做。显然,完美实现这一目标是不可能的,但这是软件设计中的一个普遍真理:没有完美的设计,只有更好的设计。

因此,这是你知道自己在“自行车棚讨论”或过度工程化或在弄清楚如何重构某物上花费太多时间的另一种方式——你试图让它“完美”。它不会“完美”,因为没有“完美”。只有“为其目的做得很好”。也就是说,如果不理解代码设计的目的,你甚至无法真正判断设计是否良好。一种设计可能适用于一个目的,另一种设计可能适用于另一个目的。是的,有通用库,但即使那也是一个目的。最好的通用库是通过实际代码库的实验设计的,你可以验证它们很好地服务于特定目的。当你重构时,想法是将设计从当前不适合目的的设计更改为适合该代码段当前目的的设计。这并不是关于重构的全部知识,但这是一个很好的基本原则。

简而言之,重构是一个组织过程,你通过它来实现生产。如果你在重构时没有朝着生产方向前进,你会遇到各种麻烦。我甚至无法告诉你所有会出错的事情,但它们会发生。另一方面,如果你只是尝试生产一个系统而从不重组它,你会陷入如此混乱的境地,以至于生产变得困难或不可能。因此,这两件事都必须做——你必须生产一个产品,并且你必须以快速、可靠、简单和良好的方式组织系统以生产产品。如果你忽略组织,你将无法得到你想要的产品,如果你忽略生产,那么首先进行重构就完全没有理由。

是的,浇水草坪很好,但让我们先扑灭一些火灾。

  • Max

评论

steven gordon 说:
2017年5月4日上午9:32
我基本同意。
然而,我有过糟糕的经历,开发人员基于单一用例重构到设计模式。作者写道:“我用来决定何时‘完成’重构特定代码段的另一点是,我认为其他人将能够清楚地看到我设计的模式,并从此以后按照该模式维护代码。”
我更喜欢在代码模式的第一个潜在实例上更早停止,以便让代码库在其他地方出现类似代码,然后我可以重构到一个可以被所有这些地方利用的模式。重构涌现代码创建的设计模式更易理解(给定多次调用)并可证明可重用。
这仍然支持主要前提。重构到可重用设计模式是由类似代码的第N次新出现触发的(N = 3?)。

David Coleman 说:
2017年7月1日上午7:26
我喜欢N=3。在此之前,你没有足够的知识来可靠地构建可重用的东西(除非你正在构建广泛使用的API)。

Max Kanat-Alexander 说:
2017年7月26日下午9:40
N=3不可靠。修改系统的开发人员不知道有三个实例。你并不总是唯一修改系统的开发人员,因此你拥有数据没有帮助。使用N=3意味着你实际上在鼓励代码重复。我想我在http://www.codesimplicity.com/post/two-is-too-many/的评论中详细讨论过这一点。

  • Max

Steven Gordon 说:
2017年8月4日上午8:35
代码库应由整个团队共享。允许只有N个团队成员熟悉代码的孤岛会将你的巴士数减少到N。
问你的队友“有人已经写过我即将编写的代码吗?”会导致更高的生产力和更少的冗余。

The basis from Refactoring – Onwards to becoming an expert developer 说:
2017年9月25日下午12:31
[…] 博客链接:http://www.codesimplicity.com/post/refactoring-is-about-features/ […]

5 Business Benefits of Software Reengineering and Refactoring - Skelia :: Skelia 说:
2018年2月2日凌晨2:59
[…] “代码简单性”作者Max Kanat-Alexander在他的文章中建议,在开始添加新功能之前先做好系统整理工作会花费更少的时间 […]

Refactoring Your Way to a Design System - Cocopine Web Solutions 说:
2018年4月25日凌晨4:36
[…] 重构是关于功能的 […]

comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计