众所周知,重复代码的存在会对软件开发和维护产生负面影响。确实,一个主要的弊端是:当某一处重复代码因为修复缺陷或添加新功能而被修改时,它的其他副本也必须同时修改。
重复代码最常见的成因是复制粘贴操作——源代码在两个或更多地方完全相同。许多文章、书籍和网站都不提倡这种做法。然而,遵循这些建议并不总是那么容易,开发者往往会选择省事的方案:复制粘贴。
有许多工具可以检测这种克隆代码;CCFinderX就是其中一款有趣的开源工具。CCFinderX 是一个代码克隆检测器,可检测用 Java、C/C++、COBOL、VB 和 C# 编写的源文件中的代码克隆(重复的代码片段)。它支持用户自定义预处理器,并提供交互式的、基于度量的分析。
使用合适的工具,可以轻松检测由复制粘贴产生的重复代码;然而,在某些情况下,克隆代码并不容易检测出来。
隐藏的重复代码情况一:被修改过的复制粘贴代码。
如上所述,复制粘贴代码的主要问题在于:当某一处重复代码被修改时,它的其他副本必须同时修改。遗憾的是,情况并非总是如此——重复代码的各个实例会逐渐变得不同。
为了避免这种隐藏的重复代码,请毫不犹豫地使用 CCFinderX 之类的工具来发现重复代码实例;如果您没时间重构代码,至少给它们加上注释标记。当开发者试图修改某一处重复代码时,这会非常有用:他们会被告知同样的代码还存在于别处。然而,如果开发者不知道存在重复,他们可能只修改其中一处,之后要检测出这种被修改过的重复代码就会变得非常困难。
情况二:相似的功能
复制粘贴并不是重复代码的唯一来源;另一个来源是相似功能的独立实现。
下面是维基百科对这第二类重复代码来源的简要描述:
Functionality that is very similar to that in another part of a program is required and a developer independently writes code that is very similar to what exists elsewhere. Studies suggest, that such independently rewritten code is typically not syntactically similar.追踪隐藏的重复代码
当重复代码并不完全相同时,没有任何工具能给出可靠的结果:它只能报告潜在的重复代码,而判断它到底是真正的克隆代码还是误报,是开发者的责任。
每种工具都使用特定的算法来追踪这类重复代码。我们没有测试所有这些工具,但我认为它们大多至少值得一试:它们可能会给出有趣的结果,帮助您改进代码的设计和实现——正如我们将在本文后面看到的。
在我们的案例中,我们将使用这样一种算法:定义“使用相同成员的方法集合”,即调用相同方法、读取相同字段、写入相同字段的方法集合。我们把这些集合称为可疑集合(suspect-set)。可疑集合按共用的成员数量排序。
CppDepend把这个算法实现为一个 CppDepend Power-Tool。Power-Tools 是一组基于 CppDepend.API 的开源工具。Power-Tools 的源代码可以在$CppDependInstallPath$\CppDepend.PowerTools.SourceCode\CppDepend.PowerTools.sln中找到。
让我们看看这个算法在 Irrlicht 3D 引擎代码库中查找重复代码的效果如何。
案例研究:Irrlicht 3D 引擎
Irrlicht引擎是一个用 C++ 编写的开源、高性能实时 3D 引擎。它是完全跨平台的。
下面是检测到的两个可疑重复代码的例子:
1. 完全相同的重复代码
在这个案例中,检测到的 18 个方法使用相同的 3 个方法,读取相同的 2 个字段,写入相同的 9 个字段。

检查这些方法的源代码后,我们发现它们确实包含完全相同的重复代码。不过,其他工具更适合检测这类重复;在检测完全克隆方面,我们的算法并不能提供额外的价值。
2. 相似的功能
下面是第二个疑似重复:它涉及四个方法,它们使用相同的 11 个方法,读取相同的 6 个字段,写入相同的 2 个字段。

检查这四个方法的源代码后,我们发现代码并不完全相同。然而,它们实现的是同一个布局算法,所以在这种情况下,我建议进行重构。
为了更好地解释这个案例,下面是涉及重复代码的各个类之间的关系:

OnSetConstants 在 IShaderConstantSetCallBack 接口中声明,并由所有派生类实现。全部四个实现都采用相同的布局算法;在这种情况下,模板方法模式(template method pattern)是重构现有实现的好方案。
当我们在许多开源 C++ 项目上测试这个算法时,我们非常惊讶地发现:许多重复代码的情况都与这个例子相似,而模板方法模式却很少被使用。
结语
追踪重复代码对于改进项目的实现和设计都非常有用。幸运的是,有许多工具可以检测克隆代码;定期运行其中一种工具,并至少给重复实例加上标记,是个好主意。
