优秀的开发人员知道何时以及如何重构 Rails 代码

已发表: 2022-03-11

大规模重构:你为什么要做这样的事情?

如果它没有坏,就不要修理它。

这是一个众所周知的短语,但正如我们所知,大多数人类技术进步都是由那些决定修复未损坏的东西的人取得的。 尤其是在软件行业,人们可能会争辩说,我们所做的大部分工作都是修复未损坏的部分。

修复功能、改进 UI、提高速度和内存效率、添加功能:这些都是很容易看出它们是否值得做的活动,然后我们作为经验丰富的 Rails 开发人员会争论或反对将时间花在这些活动上。 但是,有一个活动,大部分都属于灰色地带——标准重构,尤其是大规模代码重构。

大规模重构这个术语值得解释。 究竟什么可以被认为是“大规模”会因情况而异,因为这个术语有点模糊,但我认为任何显着影响的东西不仅仅是几个类,或者不仅仅是一个子系统,它的接口是“大。” 另一方面,任何隐藏在单个类接口后面的 Rails 重构肯定是“小”的。 当然,两者之间有很多灰色地带。 最后,相信你的直觉,如果你害怕这样做,那么它可能是“大”的。

根据定义,重构不会产生任何可见的功能,您无法向客户展示任何内容,也没有可交付成果。 充其量它们可能会产生小的速度和内存使用改进,但这不是主要目标。 有人可能会说,主要目标是您满意的代码。 但是因为你正在以这样一种方式重新排列代码,它会在整个代码库中产生深远的影响,所以有可能所有的地狱都会崩溃并且会出现问题。 这当然是我们提到的恐惧的来源。 你有没有向你的代码库介绍过一个新人,在他们询问了一段特别有组织的代码之后,你的回答是这样的:

Yeeeaahh,这是当时有意义的遗留代码,但规范发生了变化,现在修复它太贵了?

也许你甚至给了他们一个非常严肃的表情,并告诉他们不要动它,不要碰它。

在计划如何重构 Rails 代码时,您可能需要一个复杂的图表才能开始。

问题是,“我们为什么要这样做?” 是一个自然的,可能和这样做一样重要......

问题是,“我们为什么要这样做?” 这是一个自然的过程,并且可能与重构过程一样重要,因为您经常必须说服其他人允许您将昂贵的时间花在重构上。 因此,让我们考虑一下您想要这样做的情况以及获得的好处:

性能改进

从可维护性的角度来看,您对代码的当前组织感到满意,但它仍然会导致性能问题。 优化当前设置的方式太难了,而且更改将非常脆弱。

这里只有一件事要做,那就是广泛地分析它。 运行基准并估计您将获得多少,然后尝试估计它将如何转化为具体收益。 有时您甚至可能意识到建议的代码重构并不值得。 其他时候,你会有冷硬的数据来支持你的案子。

架构改进

也许架构还可以,但有些过时,或者每次接触代码库的那部分时,它都太糟糕了,你会畏缩。 它运行良好且快速,但添加新功能很痛苦。 重构的商业价值就在于这种痛苦。 “痛苦”还意味着重构过程将花费更长的时间来添加新功能,甚至可能更长。

并且可以获得好处。 估算一些示例功能的成本/收益,无论是否有您提议的大重构。 说明在系统开发过程中,类似的差异将适用于现在和将来永远触及系统该部分的大多数即将推出的功能。 您的估计可能是错误的,因为它们经常出现在软件开发中,但它们的比率可能会在大致范围内。

使其保持最新状态

有时代码最初编写得很好。 你对它非常满意。 它速度快、内存效率高、可维护且与规范完全一致。 原来。 但是随后规格发生变化,业务目标发生变化,或者您从最终用户那里了解到一些新的东西,这会使您最初的假设无效。 代码仍然运行良好,您仍然对此感到非常高兴,但是当您在最终产品的上下文中查看它时,有些事情会很尴尬。 事物被放置在稍有错误的子系统中,或者属性位于错误的类中,或者某些名称可能不再有意义。 他们现在正在履行一个在商业术语中完全不同的角色。 然而,仍然很难证明任何类型的大规模 Rails 重构是合理的,因为所涉及的工作将与任何其他示例一样规模化,但好处却不那么明显。 当您考虑它时,维护它甚至不是那么难。 你只需要记住,有些东西实际上是另外一回事。 您只需要记住,A 实际上意味着 B,而 A 上的属性 Y 实际上与 C 相关。

这就是真正的好处。 在神经心理学领域,有许多实验表明我们的短期或工作记忆只能容纳 7+/-2 个元素,其中之一是 Sternberg 实验。 当我们研究一门学科时,我们从基本元素开始,最初,当我们考虑更高层次的概念时,我们必须考虑它们的定义。 例如,考虑一个简单的术语“盐渍 SHA256 密码”。 最初,我们必须保留“salted”和“SHA256”的工作记忆定义,甚至可能保留“哈希函数”的定义。 但是一旦我们完全理解了这个术语,它只占用一个内存槽,因为我们直观地理解它。 这就是为什么我们需要充分理解较低层次的概念才能对较高层次的概念进行推理的原因之一。 对于我们项目的特定术语和定义也是如此。 但是,如果我们每次讨论代码时都必须记住翻译到真正含义的翻译,那么该翻译就会占用另一个宝贵的工作记忆槽。 它会产生认知负担,并使通过代码中的逻辑进行推理变得更加困难。 反过来,如果更难推理,则意味着我们更有可能忽略重要的一点并引入错误。

并且不要忘记更明显的副作用。 在与我们的客户或任何熟悉正确业务术语的人讨论更改时,很有可能会产生混淆。 加入团队的新人必须熟悉业务术语以及代码中的对应术语。

我认为这些原因非常有说服力,并且在许多情况下证明了重构的成本是合理的。 不过,请注意,可能有很多边缘情况,您必须使用最佳判断来确定何时以及如何进行重构。

归根结底,大规模重构是好的,原因与我们许多人喜欢开始一个新项目的原因相同。 你看着那个空白的源文件,一个勇敢的新世界开始在你的脑海中盘旋。 这一次你做对了,代码会很优雅,它的布局会很漂亮,而且快速、健壮且易于扩展,最重要的是,每天工作都会很愉快。 重构,无论规模大小,都可以让您重新获得那种感觉,为旧代码库注入新的活力并偿还技术债务。

最后,最好是由计划驱动重构,以便更容易实现某个新功能。 在这种情况下,重构将更加集中,并且重构所花费的大量时间也将通过更快地实现特性本身而立即得到回报。

准备

确保您的测试覆盖率在您可能接触的所有代码库区域中都非常好。 如果您发现某些部分没有很好地覆盖,请先花一些时间提高测试覆盖率。 如果您根本没有测试,那么您应该首先花时间创建这些测试。 如果您无法创建合适的测试套件,请专注于验收测试并尽可能多地编写,并确保在重构​​时编写单元测试。 从理论上讲,您可以在没有良好测试覆盖率的情况下进行代码重构,但这需要您进行大量手动测试并且经常这样做。 这将花费更长的时间并且更容易出错。 最终,如果您的测试覆盖率不够好,那么执行大规模 Rails 重构的成本可能会很高,遗憾的是,您应该考虑根本不这样做。 在我看来,这是自动化测试的一个好处,但没有得到足够的重视。 自动化测试允许你经常进行重构,更重要的是,更大胆地进行重构。

一旦你确定你的测试覆盖率是好的,就该开始绘制你的更改了。 起初你不应该做任何编码。 您需要粗略地绘制出所有涉及的更改并通过代码库跟踪所有后果,并将所有这些知识加载到您的脑海中。 您的目标是准确了解为什么要更改某些内容以及它在代码库中扮演的角色。 如果你只是因为它们看起来需要改变或者因为某些东西坏了而这似乎可以修复它而偶然改变它,那么你可能最终会走入死胡同。 新代码似乎可以工作,但不正确,现在您甚至无法记住您所做的所有更改。 在这一点上,您可能需要放弃在大规模代码重构方面所做的工作,实际上您已经浪费了时间。 因此,请花点时间探索代码,以了解您将要进行的每项更改的后果。 它最终会得到丰厚的回报。

您将需要重构过程的帮助。 你可能更喜欢别的东西,但我喜欢一张简单的白纸和一支笔。 我首先在论文的左上角写下我想要进行的初始更改。 然后我开始寻找所有受变更影响的地方,并将它们写在最初的变更下。 在这里使用您的判断很重要。 最终,纸上的笔记和图表是为你自己准备的,所以选择最适合你记忆的风格。 我写出简短的代码片段,它们下方带有项目符号,许多箭头指向其他此类注释,指示直接依赖它的事物(实箭头)或间接依赖它(虚线箭头)。 我还用速记标记注释箭头,以提醒我在代码库中注意到的一些特定事物。 请记住,在接下来的几天内,您只会在执行计划中的更改时回到这些笔记,使用非常简短和隐秘的提醒是完全可以的,这样它们会占用更少的空间并且更容易在纸上布局. 有几次我在 Rails 重构后的几个月里清理我的办公桌,我发现了其中一篇论文。 这完全是胡言乱语,我完全不知道那张纸上的任何东西是什么意思,除了它可能是由一个发疯的人写出来的。 但我知道在我处理这个问题时,那张纸是必不可少的。 此外,不要认为您需要写出每一个更改。 您可以将它们分组并以不同的方式跟踪详细信息。 例如,在您的主要论文上,您可以注意到您需要“将所有出现的 Ab 重命名为 Cd”,然后您可以通过几种不同的方式跟踪细节。 您可以将它们全部写在一张单独的纸上,您可以计划再次对所有出现的它执行全局搜索,或者您可以简单地将所有需要更改的源文件留在您选择的编辑器中并在你完成更改规划后记下回顾它们。

当您绘制出初始更改的后果时,由于它的性质是大规模的,您很可能会确定其他更改本身会产生进一步的后果。 对它们也重复分析,注意所有相关的变化。 根据更改的大小,您可以将它们写在同一张纸上或选择一张新的空白纸。 在规划更改时要尝试和做的一件非常重要的事情是尝试确定可以实际停止分支更改的边界。 您希望将重构限制为最小的合理、四舍五入的更改集。 如果您看到某个点可以停止并保持原样,即使您认为它应该被重构,即使它在概念上与您的其他更改相关,也要这样做。 完成这一轮代码重构,彻底测试,部署并返回更多。 您应该积极寻找这些点,以使更改的大小保持可控。 当然,一如既往地做出判断。 很多时候,我想通过添加一些代理类来进行一些接口转换来切断重构过程。 我什至开始实现它们,因为我意识到它们的工作量与将重构推得更远一点到“自然停止”(即几乎不需要代理代码)点一样多。 然后我回溯,恢复我最后的更改并重构。 如果这听起来有点像绘制未知领域的地图,那是因为我觉得它是,除了领域地图只是二维的。

执行

完成重构准备后,就该执行计划了。 确保您的注意力集中并确保没有分心的环境。 在这一点上,我有时甚至会完全关闭互联网连接。 问题是,如果你准备充分,在你旁边的纸上做一套很好的笔记,你的注意力就会集中起来! 您通常可以通过这种方式快速完成更改。 理论上,大部分工作都是在准备期间预先完成的。

一旦你真正在重构代码,请注意那些做一些非常具体的、可能看起来像坏代码的奇怪代码。 也许它们是糟糕的代码,但实际上它们通常是在处理一个奇怪的极端案例,该案例是在调查生产中的错误时发现的。 随着时间的推移,大多数 Rails 代码会长出“毛发”或“疣”,它们会处理奇怪的极端情况错误,例如,这里可能需要 IE6 的奇怪响应代码或处理奇怪计时错误的条件。 它们对于大局并不重要,但仍然是重要的细节。 理想情况下,如果不尝试首先覆盖它们,它们会被单元测试明确覆盖。 我曾经负责将一个中型应用程序从 Rails 2 移植到 Rails 3。我对代码非常熟悉,但它有点混乱,需要考虑很多更改,所以我选择重新实现。 实际上,这并不是真正的重新实现,因为这绝不是明智之举,但我从一个空白的 Rails 3 应用程序开始,我将旧应用程序的垂直切片重构为新应用程序,大致使用所描述的过程。 每次我完成一个垂直切片时,我都会检查旧的 Rails 代码,查看每一行并仔细检查它是否在新代码中有对应的部分。 我基本上是在挑选所有旧代码“头发”并将它们复制到新代码库中。 最后,新的代码库解决了所有的极端情况。

确保足够频繁地执行手动测试。 它既会迫使你在重构过程中寻找自然的“中断”,这将允许你测试系统的一部分,也会让你确信你没有破坏任何你没想到会在过程中破坏的东西.

包起来

完成 Rails 代码重构后,请务必最后一次检查所有更改。 查看整个差异并检查它。 很多时候,你会注意到你在重构开始时遗漏的一些细微的东西,因为你没有现在所拥有的知识。 这是大规模重构的一个很好的好处:您可以更清楚地了解代码组织,尤其是如果您最初没有编写它。

如果可能的话,也请一位同事对其进行审查。 他甚至不必特别熟悉代码库的确切部分,但他应该对项目及其代码有大致的了解。 以全新的眼光看待这些变化会大有帮助。 如果您绝对无法让其他开发人员查看它们,那么您将不得不假装自己是其中的一个。 睡个好觉,并以全新的心态回顾它。

如果你缺乏质量保证,你也必须戴上那顶帽子。 再次,休息一下,远离代码,然后回来执行手动测试。 您刚刚经历了相当于带着一堆工具进入杂乱的电线柜并将其全部整理出来,可能切割和重新布线的东西,因此需要比平时更加​​小心。

最后,享受您的劳动成果,考虑到所有计划中的更改,这些更改现在将更加清洁和易于实施。

你什么时候不做?

虽然定期执行大规模重构以保持项目代码的新鲜和高质量有很多好处,但它仍然是一项非常昂贵的操作。 也有不建议这样做的情况:

您的测试覆盖率很差

如前所述:非常差的测试覆盖率可能是一个大问题。 使用您自己的判断,但在短期内专注于提高覆盖率,同时开发新功能并执行尽可能多的本地化小规模重构可能会更好。 一旦您决定冒险并对代码库的较大部分进行排序,这将对您有很大帮助。

重构不是由新特性驱动的,代码库很长时间没有改变

我使用过去时而不是故意说“代码库不会改变”。 从经验来看(我的意思是多次出错),你几乎永远不能依赖你的预测来判断代码库的某个部分何时需要更改。 所以,做下一件最好的事情:回顾过去,假设过去会重演。 如果某些东西很长时间没有更改,那么您现在可能不需要更改它。 等待这种变化出现并进行其他工作。

你时间紧迫

维护是项目生命周期中最昂贵的部分,重构使其成本更低。 任何企业都绝对有必要使用重构来减少技术债务,从而使未来的维护成本更低。 否则就有进入恶性循环的危险,在这个循环中添加新功能变得越来越昂贵。 我希望这是不言而喻的。

也就是说,大规模重构在需要多长时间方面是非常非常不可预测的,你不应该半途而废。 如果出于任何内部或外部原因,您的时间紧迫,并且您不确定能否在该时间范围内完成,那么您可能需要放弃重构。 压力和压力,尤其是时间诱导类型,会导致较低的集中度,这对于大规模重构是绝对必要的。 努力从你的团队那里获得更多的“支持”,为它留出时间,并在你有时间的时候查看你的日历。 它没有必要是一个连续的时间段。 当然,您还有其他问题需要解决,但这些休息时间不应超过一两天。 如果是这样,您将不得不提醒自己您自己的计划,因为您将开始忘记您对代码库的了解以及您停止的确切位置。

结论

我希望我给了你一些有用的指导,并让你相信在某些情况下进行大规模重构的好处,我敢说是必要的。 这个话题非常模糊,当然这里所说的一切都不是确定的事实,具体情况会因项目而异。 我试图提供建议,在我看来,这是普遍适用的,但与往常一样,请考虑您的具体情况并利用您自己的经验来适应其具体挑战。 祝重构顺利!