重构是为功能服务:提升代码质量与开发效率的关键策略

本文深入探讨重构在软件开发中的核心作用,强调重构应始终服务于产品功能目标,而非为了重构而重构。通过实际案例和原则分析,帮助开发者避免过度工程化,提升代码可维护性和团队协作效率。

重构是为功能服务

Code Simplicity
Code Simplicity 重构是为功能服务
2017年5月2日 by Max Kanat-Alexander

我在书中提到过一个观点,但之后不得不多次向人们强调,因此我想再稍微强调一下。

当你清理代码时,你总是在为产品服务。重构本质上是一个组织过程(不是“组织”指“与业务相关”的定义,而是指“使事物有序”的定义)。也就是说,你进行整理是为了能够做某事。

当你开始仅仅为了重构而重构时,重构就会名声不佳。人们开始认为你在浪费时间,你失去信誉,你的经理或同事会阻止你继续工作。

当我说“仅仅为了重构而重构”时,我指的是查看一段与你实际工作无关的代码,说“我不喜欢这个设计方式”,然后在不影响系统功能的情况下移动设计部分。这就像在你的房子着火时给草坪浇水。如果你的代码库像我见过的大多数代码库一样,“你的房子着火了”可能甚至是一个恰当的类比。即便如此,如果情况没有那么糟糕,关键是你专注于不需要关注的东西。你可能觉得你在重组代码方面做得很好,而且可能确实如此,但给草坪浇水的目的是让房子前面有一片漂亮的草坪。如果你的重构与当前产品或系统的功能目标无关,那么你实际上没有完成任何事,只是在重新排序一些没有人使用、参与或关心的事情。

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

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

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

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

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

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

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

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

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

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

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

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

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

-Max

分享 点击在Facebook上分享(在新窗口中打开) Facebook
点击在LinkedIn上分享(在新窗口中打开) LinkedIn
点击在Hacker News上分享(在新窗口中打开) Hacker News
点击在Reddit上分享(在新窗口中打开) Reddit
点击在Threads上分享(在新窗口中打开) Threads
点击在X上分享(在新窗口中打开) X

7条评论 留下回复

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
[…] Kanat-Alexander,“代码简单性”的作者,在他的文章中建议,在开始添加新功能之前先做好系统整理工作总时间会更少。[…]

回复

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

回复
留下回复取消回复

联系 关于 书籍:理解软件 书籍:代码简单性
输入你的邮箱… 订阅
© 2025 版权所有。由The Fox提供支持。
管理同意
为了提供最佳体验,我们使用像cookies这样的技术来存储和/或访问设备信息。同意这些技术将允许我们处理诸如浏览行为或本网站上的唯一ID等数据。不同意或撤回同意,可能会对某些特性和功能产生不利影响。

功能性的
功能性的
始终活跃
技术存储或访问严格对于使订阅者或用

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