重构的本质:为产品功能服务的代码优化艺术

本文深入探讨了代码重构的核心原则,强调重构应该始终围绕产品功能展开,而非为了重构而重构。作者通过生动的比喻和实际案例,分享了如何通过重构提升代码质量、团队效率和产品交付速度的实践经验。

重构的本质:为产品功能服务

作者: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。
问你的队友“有没有人已经写过类似我即将编写的代码?”会带来更高的生产力和更少的冗余。

Refactoring – Onwards to becoming an expert developer 的基础说:
2017年9月25日下午12:31

[…] 博客链接:http://www.codesimplicity.com/post/refactoring-is-about-features/ […]

软件重新工程和重构的5个商业好处 - Skelia :: Skelia 说:
2018年2月2日凌晨2:59

[…] 《代码简洁性》作者Max Kanat-Alexander在他的文章中建议,在开始添加新功能之前先花时间整理系统,总体花费的时间会更少。[…]

重构你的设计系统 - Cocopine Web Solutions 说:
2018年4月25日凌晨4:36

[…] 重构是关于功能的 […]

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