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

本文深入探讨了重构的真正意义,强调重构应始终服务于产品功能开发。通过实际案例和比喻,阐述了如何通过重构提升代码质量、加快开发速度,并避免过度工程化的问题。

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

是的,给草坪浇水很好,但让我们先灭火吧。

  • 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
[…] 重构是关于功能的 […]

留下回复取消回复


联系 关于 书:《理解软件》 书:《代码简单性》

输入你的邮箱… 订阅

Max Kanat-Alexander
6月27日
我目前认为,AI代理可以成功生成的输出数量和质量取决于:

  1. 模型的质量。
  2. 代理的质量。
  3. 输入的质量(如提示或其他上下文)。
  4. 代理可以独立运行的确定性、客观验证的质量。
    我目前认为,除非你是模型开发者,否则最重要的部分是“确定性、客观验证”。
    简单来说,如果你告诉AI做某事,它需要某种方式能够自行验证它做对了。在软件中,这通常意味着运行测试、linter等,如果系统做错了就会失败。
    这意味着你的测试和验证工具越好,从模型获得的输出就越好。这不仅仅是测试数量的问题。它们必须是好的测试,具有智能断言和良好的错误消息。
    这也意味着代理成功涉及思考如何将任务分解成可以各自通过自动化测试、linter等客观验证的单独部分。
    作为说明,输入的质量也很重要并在我们控制之下。如果我们编写更好的文档和更清晰的代码,代理会做得更好。令人惊讶的是,几乎一切帮助人类编写代码的东西也帮助代理。
    阅读更多2010分享

Max Kanat-Alexander
6月4日
技术债务有价值的想法大多是一个神话。当你做出糟糕的软件工程决策时,它会在几小时、几天或几周内减慢你的速度。那是上限。许多人认为需要几个月或几年才能看到在软件设计上偷工减料的影响,但这根本不是真的。它几乎立即开始。做正确事的成本通常是几个小时,也许一天,而且你几乎总是立即收回那段时间。也就是说,做对通常花费与做错相同的时间。“等等,什么?怎么可能?我们偷工减料是为了节省时间!”结果几乎总是它根本不会节省你的时间!它使处理变更更加混乱。它使代码审查时间更长。它在测试期间以更混乱的方式失败,需要更长时间调试。它几乎从不节省你的实际时间。
偶尔,你路径上有些“石头”如此巨大,你根本无法移动它。没有人应该花三个月设计一个新工具,只是为了发布一个一次性的、一天的功能。但那是技术债务决策中极小极小的一部分。
这变得更疯狂,因为如果你在开发过程中一切都做对(你总是重构系统,使其看起来像是为完成当前工作而设计的),那么一切始终保持相对容易。但如果你偷工减料,一切都会变得越来越困难,以至于你生成没有人能移动的“石头”。复杂性不是某种线性的东西,你可以后来投入线性时间修复。你绝对可以陷入一种如此糟糕的境地,以至于公司中没有人有时间或专业知识修复它。
这一切感觉如此无辜:“让我们偷工减料吧,它会帮助我们满足这个截止日期”(一个通常是虚构的截止日期,你可以推迟几周而没有任何后果)。“很难弄清楚如何做对,我们可以推迟吗?”然后砰,很快你发现自己陷入了一个沼泽,一切都很困难,“我们不知道为什么!”
我最后留给你一件事:当我直接编码的每一天,我对此完全 uncompromising。我基本上无法忍受糟糕的代码——我必须重构它,否则我无法工作。不知何故,在我整个职业生涯的那部分中,我从未错过一个截止日期。
阅读更多9342分享

Max Kanat-Alexander
5月30日
我估计,我每看到或被告知一万条坏建议,才会收到一条好建议。互联网充满坏建议,因为文明充满坏建议。文明充满坏建议,因为全球的教育大多不教人们思考、研究或确定什么是真实的。相反,它教人们记忆和重复事实,其中大多数是不重要、不真实或无用的。
所以总的来说,我们不应该对互联网(尤其是社交媒体)充满不重要、不真实或无用的信息感到惊讶。并非全部如此——我在网上学到了大量非常有用的东西。有大量有价值的内容。我会说,人们对事物的看法,无论是在报纸、博客文章、论坛还是其他什么地方,很少具有那种价值。
那么要点是什么?嗯,最好有一些方法在开始依赖或向他人重复之前确定某事是否重要或有用的。最简单的工具是说:“它有效吗?”这条数据是否可靠地帮助你完成某事?它100%有效,还是更少?
对于许多帖子,你会发现:这些数据根本无助于你做任何事。
阅读更多219分享

Max Kanat-Alexander
5月29日
你对系统所做的任何更改在推出时都会收到某些人的负面反馈。这不是因为每次更改都有问题,而是因为用户习惯了系统的工作方式,他们不喜欢它改变了。他们可能会批评新的用户界面,或者有一些关于新东西“糟糕”的情感争论,但通常他们真的只是对任何更改感到不安。这不是什么不寻常的事;它存在于几乎所有人中。人们往往对在一段时间内愿意经历多少变化有一定的胃口,如果你超出了那个范围,他们会感到不安。
重要的是要认识到人们的反馈是简单的“变化厌恶”还是关于有价值事物的真实反馈。有几种方法可以判断:

  1. 变化厌恶通常持续3到10天。如果你在推出更改后的前3到10天内收到用户的反馈,并且该反馈不是由大量用户提供的,那么值得考虑用户的反应是否只是变化厌恶。
  2. 变化厌恶反馈通常是情感化的。它可能是侮辱性的。它可能表达为只是一种意见(“新菜单的颜色很丑”)而不是事实(“我测量过,新工具比旧工具慢10倍”)。
    管理变化时,你要避免的是制造如此多的变化厌恶以至于引起反抗。反抗基本上看起来像如此多的人愤怒以至于他们去找你的管理层并阻止你的工作。如有疑问,向更少的人推出更小的变化,以更慢的扩展速度。随着时间的推移,你将学会可以推出多少变化,多快。永远不要进行“大爆炸”发布,让所有用户同时经历 massive change。
    现在,所有这些 said,永远不要将所有反馈归因于“变化厌恶”。通常人们确实有合法的反馈。如果许多人提供相同的事实反馈,你查看后认为他们的反馈有道理,并且你认为修复它会改进产品,那很可能不是变化厌恶。另外,说“这只是变化厌恶”并不意味着你应该完全忽略反馈。你至少应该承认它,让人们知道他们被听到了。这确实意味着你不应该与那个人争论或试图与他们讲道理,因为他们正在产生情感反应,你试图用“逻辑”说服他们不会帮助任何人。如果你认为是变化厌恶,你承认他们,而他们只是继续与你争吵,有时你应该忽略他们并继续做你的工作。如果他们在3天后带着更合理的论点回来,那么你知道这不只是变化厌恶,而且他们可能那时更有帮助。
    阅读更多234分享

加载更多

© 2025 版权所有。由 The Fox 提供支持。 管理同意联系 关于 书:《理解软件》 书:《代码简单性》

转到顶部

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