我们应该重构我们正在处理的文件吗?

2020-05-15 07:49:08

在编程时,请始终遵循驻扎规则:始终使代码库比发现代码库时更健康。

美国童子军有一条简单的规则,那就是我们可以适用于我们的职业。让营地比你发现的更干净。

我们中的许多人都认同上述福勒和马丁引述的态度。这种态度假设我们现在正在处理的代码很快就会再次更改,当这种情况发生时,我们将收获重构的好处。

这是另一种普遍的态度:我们没有足够的时间进行重构。1个。

这些态度是相关的:就我们希望让未来的工作更容易2而言,上面的露营规则可能会导致关于哪些代码需要重构的次优决策。这些次优的决策可能会使我们构建软件的速度变慢,这意味着-除了其他事情-我们重构的时间更少。3个。

这种次优决策多久发生一次?答案取决于当前编辑和未来编辑之间的链接距离有多近。我已经声称这里的链接是脆弱的,并建议了另一种思考我们应该重构哪些代码的方式,但在这里我测量了时间t的差异(diff=更改的行数)和时间t+1的差异之间的联系。换句话说,这篇文章是关于差异在多大程度上跨月和季度顺序相关的。

在这里,我只讨论一个repo:react.js repo。这是我选择的第一个,而且它碰巧是一个差异没有很强序列相关性的。在react.js repo中,所有源文件不同的1个月自相关性的中位数是-.098。这意味着,如果您在Reaction Repo中编辑一个随机文件,并对其进行重构,希望有人在一个月内从中受益,那么您的赌注就错了。如果Reaction repo代表了我们倾向于使用的代码库,那么我们应该重新考虑如何决定重构哪些代码。

在第一部分中,我对分析Reaction Repo时发现的结果进行了细分。在下一节中,我将回顾一下用于此分析的源代码,这样您就可以确保我没有完全胡说八道,这样您就可以在您自己的代码库上运行它,看看露营规则对您的服务有多好。最后,我对这一分析的含义提出了一些警告,并对我们可能会提出的进一步问题提出了一些建议。

在进入具体结果之前,我想澄清一下它们的格式。我从一系列文件及其差异开始,这些文件和差异在过去12个月中被分成不同的存储桶。例如,以下是反应回购列表的子集:

Packages/Reaction-DevTools-CORE/src/backend.js:0 0 0 284 2 0 0 0 16 0 0 0 Packages/Reaction-DevTools-CORE/src/editor.js:0 0 0 190 0 0 0 4 0packages/react-dom/src/client/ReactDOMHostConfig.js:0884177 43 24 34 68 30 15 53279405。

此文件的1个月滞后自相关性将较低,因为1个月内更改的行数并不能告诉您该文件下个月将更改多少行代码。

当我按季度而不是按月分组时,情况也类似。相同的文件如下所示:

Packages/Reaction-DevTools-CORE/src/backend.js:0 286 160 Packages/Reaction-DevTools-CORE/src/editor.js:0 190 0 4packages/react-dom/src/client/ReactDOMHostConfig.js:1061 101 113 737。

注意,我过滤掉了json文件、jest快照文件和index.js文件。

结果以框图和胡须图的形式显示在Reaction Repo中的1686个文件上的这些自相关。这使我们能够很容易地看到所有文件的相关性的中位数和所有文件的自动相关性的传播情况,这将帮助我们避免被离群值误导。

直方图是使用HIST R函数及其默认参数绘制的。

首先,所有文件的自动关联的中位数仅为-.0984。这意味着50%的文件自相关系数不超过此值。这再次表明,重构一个随机文件,希望下个月有人会从您的工作中受益,这不是一个好的赌注。

我们中那些对良好的相关系数看起来像什么缺乏强烈直觉的人(老实说,我也是其中之一),只需查看一堆随机文件的时间序列数据,就可以大致了解这个数字所传达的信息:

装置/光纤调试器/src/description beFibers.js:0111 0 0 0 0src/devtools/views/Profiler/CommitFilterModalContext.js:0 35 0 0 0 src/DevTools/视图/组件/元素.js:118233 0 0 0 0scripts/eslint-rules/__tests__/no-production-logging-test.internal.js:0 0 0 234 111 0 0 0 Packages/Shared/ReactTypes.js:0 114 90 20 7 10 5 12 19 3 64 7。

请注意,虽然有些文件会持续看到大量更改,但大多数文件只是更改,并且有相当长的几个月没有更改。当然,这意味着该文件的序列相关性不是很强。

我想强调的第二件事是:这里有很多异常值(558)。因此,文件间差异的自相关有很大的可变性。这种可变性挑战了这样一种想法,即我们总是通过重构我们接触到的文件来进行最佳押注。

这是季度序列相关性。这次是117个异常值。75%的文件的相关值为-.083或更小。

同时考虑每月和季度序列相关性的动机是为了解决这样的反对意见,即差异的每月时间片太薄,无法理解不同时间的差异之间的关系。正如您所看到的,相关性变得稍微好了一些,但不会好太多。

如果季度的时间片仍然太薄,这仍然意味着我们需要重新考虑我们的重构选择。如果通常需要3个月以上的时间才能从重构中获益,那么我们必须权衡一下重构的决定和更直接的收益,我们可以通过做其他事情来实现,比如重构将更频繁地更改或更快地发布特性的文件。

首先,我编写了一个bash脚本,要求git提供去年回购中每个文件的差异,并将这些差异分成每个月的存储桶。git diff--numstat和git log--format=format:%H--BEFORE$BEFORE--AFTER$AFTER在这里特别有用。以下是bash脚本4的亮点:

#!/bin/bash月份=(';2019年5月-2019年6月-2019年6月-2019年7月';2019年7月-2019年8月-2019年8月';2019年8月-2019年9月-2019年9月';';9月-2019年9月-2019年10月-2019年10月';2019年10月-2019年11月-2019年11月';&。2019年12月至2020年1月:2020年1月至2020年2月至2020年2月至2020年3月至2020年3月至2020年4月至2020年5月)#.ECHO";月线-增行-删除路径";${MONTS[@]}";Do After=$(Split_Month_Pair 1";$Month_Pair";)Beach=$(Split_Month_Pair 2";$Month_Pair";)First=$(First_Commit_of_Month";$After";";$Beast";)Last=$(Last_Commit_of_Month";$After";";$Beaved";);然后继续执行文件fit--no-pager diff--diff-filter=ACDM--numstat$first$last|grep";.js$&34;|#过滤掉奇怪的文件,如快照xargs-i{}echo";$After:$之前{}";完成。

例如,如果您在Reaction Repo上运行此命令,则会得到如下所示的输出:

月行-增行-删除路径2019年5月:2019年6月7 6 shell/dev/src/devtools.js5月-2019年6月:2019年6月12 6 shell/utils.jsMay-2019年6月:2019年6月116 0 src/__tests__/__snapshots__/inspectedElementContext-test.js.snap.June-2019:July-2019 7 0 packages/create-subscription/npm/index.jsJune-2019:July-2019 23 0 packages/create-subscription/package.jsonJune-2019:July-2019 4890 Packages/Create-Subscription。/src/__tests__/createSubscription-test.internal.js.Dec-2019:Jan-2020 2 2 packages/react-interactions/events/src/dom/ContextMenu.jsDec-2019:Jan-2020 2 2 packages/react-interactions/events/src/dom/Drag.jsDec-2019:Jan-2020 152 69 packages/react-interactions/events/src/dom/Focus.js.April-2020:May-2020 1 0 scripts/rollup/validate/eslintrc.rn.jsApril-2020:May-2020 1 0。scripts/rollup/validate/eslintrc.umd.jsApril-2020:May-2020 19 19脚本/Shared/inlinedHostConfigs.js。

从这里,很容易将其作为数据框导入到R中,并将添加的行和删除的行合并到Total diff列中:

接下来,我们构建一个从文件到每月差异时间序列的映射。以下是输出:

Packages/Reaction-DevTools-CORE/src/backend.js:0 0 0 284 2 0 0 0 16 0 0 0 Packages/Reaction-DevTools-CORE/src/editor.js:0 0 0 190 0 0 0 4 0packages/react-dom/src/client/ReactDOMHostConfig.js:0884177 43 24 34 68 30 15 53279 405packages/react-dom/src/client/ReactDOMInput.js:0426 0 0 0 13 8 0 16 0packages/react-dom/src/events/SyntheticWheelEvent.js:0 43 0 0 0 18 0 0 0。

然后,我们循环遍历映射中的所有键,并构建自相关计算矩阵,其中行是文件,列是各种滞后(最多6个)的自相关:

auto_correlations<;-list()for(k in key(Files_To_Time_Series)){diff_time_Series<;-unlist(files_to_time_Series[[k]])auto_corr<;-acf(diff_time_Series,lag.max=1,lot=false)$acf auto_correlations<;-list.append(auto_correlations,auto_corr)}acf_Matrix<。-t(array(unlist(AUTO_CORATRATIONS),dim=c(2,Length(AUTO_CORATRATIONS)。

从这里,我们可以使用简单的调用来计算我们在结果部分中涵盖的所有统计数据。

这种分析远非完美。在本节中,我将讨论对它的一些限制和反对意见。在此过程中,我指出了这些警告和反对意见提出的未来方向。

重构的好处并不局限于使未来的更改更容易。有时,重构会使当前任务变得更容易。正如肯特·贝克所说。

对于每个所需的更改,使更改简单(警告:这可能很难),然后进行简单的更改。

-肯特·贝克(@KentBeck)2012年9月25日。

当重构仅仅因为它使当前的工作更容易而被证明是正当的时,我们就应该重构。例如,有时重构可以帮助我们理解要更改的代码。上述分析并不挑战这种重构的价值。在我们呼吁重构的未来好处的情况下,上述数据表明6我们应该更加谨慎。

我在这里只看过一个回购,它是一个图书馆项目。库可能与应用程序具有不同的编辑模式。我希望与应用程序相比,库中的编辑更具序列相关性,但这可能是完全错误的。我宁愿让我为这个分析编写的源代码更具可读性和可重用性,这样人们就可以只在他们的repo上运行分析,而不是试图就编辑不同类型项目的模式做出更有说服力的一般性声明。如果发现有些项目类型的编辑序列相关性很强,那就太好了!营地规则将很好地服务于这些项目。

我在这里选择了计算不同的大小,这可能会使自相关看起来比我简单地计算文件是否被编辑更糟糕。在React回购的情况下,如果我们忽略差异大小,则每月序列相关性的中位数是相同的,但使用这种计数方式的数据的可变性要小得多(267个异常值对558个)。在这种特殊情况下,计数方法之间没有太大区别,而且通常不清楚哪一种计数方法严格来说更好。如果我们只计算文件是否被编辑过,我们就会丢失一些关于差异有多大的信息,这似乎对决定文件将来是否被编辑很重要。

这里一个明显的反对意见是,即使将来没有人编辑该文件,重构后的文件也可能是有益的。即使文件在将来只是被读取,重构也可能是有用的。这里有两样东西。首先,就查看文件与编辑文件相关而言,这一反对意见缺乏说服力。其次,由于查看文件与编辑文件并不相关,因此在弄清楚如何做出更好的重构决策方面,我们还有另一个有趣的方向要追求。我们不在这里推测读取和编辑文件之间的关系。天知道我们已经造了多少事件跟踪工具用于其他目的。让我们构建一个跟踪读取源文件频率的系统。如果有人构建了这个,并显示文件视图是连续相关的,我会欢迎这是向前迈出的一步。

如果我们始终如一地遵循营地规则,我们最终会不会得到一个可以接受的代码库?

另一个明显的反对意见是重申营地规则的动机,即从理论上讲,如果我们总是稍微改进碰巧接触到的代码,我们就会慢慢走向接触最多的文件最容易更改的状态。这可能会发生,但如果我们提前知道哪些重构是最有用的,而不是希望最终得到接触最多的文件是原始的,我们会过得更好。更成熟的行业并不依赖这种一厢情愿的想法。取而代之的是,他们有复杂的技术来决定将他们的时间投入到哪里。

例如,迪士尼不会告诉他们的技术人员对他们碰巧接触到的每一次游乐设施、油炸锅和AV系统进行预防性维护。取而代之的是,他们根据统计信息猜测这些机器何时会出现故障,并权衡现在进行预防性维护的成本与他们可能采取的一切简化操作的措施。作为软件工程师,我们应该有这样的工具和实践。我已经在这个方向上提出了一些建议,但我更感兴趣的是看到更多关于我们如何朝着这个方向前进的讨论,而不是推动我的特定解决方案。

简单的例子:管理层不会催你工作。您恰好有足够的时间来完成当前正在处理的票据,所以您在工作和提交代码时稍微清理了一下,感觉自己就像一个真正的童子军程序员。然后,无论出于什么原因,您在处理下一个功能时都会落后。您意识到您使用的是代码库的一部分,a)频繁更改,b)实现业务最重要的特性。不幸的是,您在工作时没有时间重构此代码。你哀叹管理层给你的重构时间太少了。[返回]。

我拒绝将数据视为复数名词。这很奇怪,几十年来,人们一直在说,它可以是单数的。例如,请参阅样式元素。[返回]