博客 阅读时间 5 分钟

追踪 C++ 代码库中隐藏的重复代码

分享本文
Tracking the hidden duplicate code in a C++ code base.

众所周知,重复代码的存在会对软件开发和维护产生负面影响。确实,一个主要的弊端是:当某一处重复代码因为修复缺陷或添加新功能而被修改时,它的其他副本也必须同时修改。

重复代码最常见的成因是复制粘贴操作——源代码在两个或更多地方完全相同。许多文章、书籍和网站都不提倡这种做法。然而,遵循这些建议并不总是那么容易,开发者往往会选择省事的方案:复制粘贴。

有许多工具可以检测这种克隆代码;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 个字段。

clone5

检查这些方法的源代码后,我们发现它们确实包含完全相同的重复代码。不过,其他工具更适合检测这类重复;在检测完全克隆方面,我们的算法并不能提供额外的价值。

2. 相似的功能

下面是第二个疑似重复:它涉及四个方法,它们使用相同的 11 个方法,读取相同的 6 个字段,写入相同的 2 个字段。

clone6

检查这四个方法的源代码后,我们发现代码并不完全相同。然而,它们实现的是同一个布局算法,所以在这种情况下,我建议进行重构。

为了更好地解释这个案例,下面是涉及重复代码的各个类之间的关系:

clone7

OnSetConstants 在 IShaderConstantSetCallBack 接口中声明,并由所有派生类实现。全部四个实现都采用相同的布局算法;在这种情况下,模板方法模式(template method pattern)是重构现有实现的好方案。

当我们在许多开源 C++ 项目上测试这个算法时,我们非常惊讶地发现:许多重复代码的情况都与这个例子相似,而模板方法模式却很少被使用。

结语

追踪重复代码对于改进项目的实现和设计都非常有用。幸运的是,有许多工具可以检测克隆代码;定期运行其中一种工具,并至少给重复实例加上标记,是个好主意。

分享本文