跟踪隐藏的重复代码






4.33/5 (2投票s)
跟踪隐藏的重复代码
众所周知,重复代码的存在会对软件开发和维护产生负面影响。事实上,一个主要的缺点是当一个重复代码实例被修改以修复错误或添加新功能时,其对应的实例也必须同时修改。
导致重复代码最常见的原因是复制/粘贴操作,在这种情况下,源代码在两个或多个地方完全相同。这种做法在许多文章、书籍和网站中都不被提倡,但有时并不容易实践这些建议,而且像往常一样,在现实世界中存在许多限制。这是一个故事,展示了其中一种可能的限制。
几年前,一位朋友在做开发时遇到一个 bug 需要解决。经过调查,他发现一个函数 (FA
) 中的一些代码行必须复制/粘贴到另一个函数 (FB
) 中。他第一个想法是将这段代码抽象成一个公共函数,并从另外两个函数中调用它。技术上这非常简单,但问题是他没有权限修改 FA
函数。他必须通知他的经理,经理会联系 FA
函数的维护者,确保修改不会引起任何问题,然后才能获得修改 FA
函数的权限。这个过程非常耗时。最终,他选择了简单的解决方案:复制/粘贴方法。
有许多工具可以检测这类克隆代码,CCFinderX 是一个有趣的开源工具。CCFinderX 是一个代码克隆检测器,可以检测用 Java、C/C++、COBOL、VB、C# 编写的源文件中的代码克隆(重复的代码片段)。它允许用户自定义预处理器,并提供基于指标的交互式分析。
使用合适的工具可以轻松检测复制/粘贴操作产生的重复代码,但有些情况下克隆代码的检测并不那么直观。
隐藏的重复代码
情况 1:修改过的复制/粘贴代码
如前所述,复制/粘贴代码的主要问题在于,当一个重复代码实例被修改时,其对应的实例也必须同时修改。不幸的是,并非总是如此,重复代码实例会变得不同。
为了避免这类隐藏的重复代码,请不要犹豫使用像 CFinderX
这样的工具来发现重复代码实例,即使没有时间重构代码,至少也可以通过添加注释来标记它们。当开发人员尝试修改一个重复代码实例时,这个操作非常有用,他会注意到其他地方也有相同的代码。然而,如果开发人员不知情,他只会在一个地方修改它,未来将很难检测到修改过的重复代码。
情况 2:相似的功能
复制/粘贴操作并非重复代码的唯一来源,另一个原因是实现了相似的功能。
以下是来自 wikipedia 的对第二种重复代码来源的简要描述:
需要程序中其他部分非常相似的功能,而开发人员独立编写的代码与现有代码非常相似。研究表明,这种独立重写的代码通常在语法上并不相似。
跟踪隐藏的重复代码
对于不完全相同的重复代码,没有工具能提供可靠的结果,它们只能报告可疑的重复代码,而开发者有责任检查这是否真的是克隆代码,还是仅仅是误报。
每个工具都使用特定的算法来跟踪这类重复代码,我们没有测试过这些工具,但我认为其中大多数工具至少值得检查一次,它们可能会提供一些有趣的结果,帮助你改进代码的设计和实现,就像我们稍后在这篇文章中将要发现的那样。
在我们的案例中,我们将讨论 NDepend 工具引入的一个算法。它包括定义使用相同成员(即调用相同的方法、读取相同字段、写入相同字段)的方法集。我们将这些集合称为“可疑集合”。可疑集合按使用的相同成员数量排序。
CppDepend 也实现了这个算法,作为 CppDepend Power-Tool。Power-Tools 是一组基于 CppDpend.API
的开源工具。Power-Tools 的源代码可以在 $CppDependInstallPath$\ CppDepend.PowerTools.SourceCode\ CppDepend.PowerTools.sln 中找到。
让我们通过使用 CppDepend PowerTools 在 Irrlicht 3D 引擎代码库中搜索重复代码来发现这个算法的效率。
案例研究:Irrlicht 3D 引擎
Irrlicht 引擎是一个开源的高性能实时 3D 引擎,用 C++ 编写。它完全跨平台,使用 D3D、OpenGL 和自己的软件渲染器。
以下是两个检测到的可疑重复代码:
1. 完全相同的代码重复
在这种情况下,检测到 18 个方法使用了相同的 3 个方法,读取了相同的 2 个字段,并写入了相同的 9 个字段。
检查这些方法的源代码后,它们涉及完全相同的代码复制,然而,其他工具在检测这类重复代码方面更有效,当涉及完全相同的代码克隆时,该算法没有附加价值。
2. 相似的功能
这是第二个可疑的重复代码,它涉及四个方法使用相同的 11 个方法,读取相同的 6 个字段,并写入相同的 2 个字段。
检查这四个方法的源代码后,代码并不完全相同,但它们实现了一个独特的布局算法。在这种情况下,我建议进行因子化(重构)。
为了更好地解释这种情况,这里是涉及重复代码的类之间的关系:
OnSetConstants
在 IShaderConstantSetCallBack
接口中声明,并由所有派生类实现。这四个实现都具有相同的布局算法,在这种情况下,模板方法模式是重构现有实现的一个好方案。
当在许多 C++ 开源项目中测试该算法时,我们非常惊讶地发现很多重复代码与这种情况相似,而模板方法模式却很少使用。
结论
跟踪重复代码对于改进项目的实现和设计都非常有益。幸运的是,存在许多工具可以检测克隆代码,建议定期执行其中一个工具,并至少标记重复的实例。