11.6 简化影响结构示意图
这本书讲的是如何令遗留代码更易对付,所以列举的很多例子都有一种“泼出去的水”的感觉,泼出去的水当然是收不回来的,同样,对于早已写成的遗留代码你也别指望能够从头来过了。不过,我希望能够借此机会展示一些能够从影响示意图中看出来的非常有用的东西。它能影响你以后编写代码的方式。
还记得CppClass的影响示意图吗(见图11-11)?

图11-11 CppClass的影响结构示意图
看起来这幅影响结构图有点“散”。两块数据(一个是declarations集合,一个是单个declaration)对好几个不同的方法都有影响。我们可以在这几个方法中选出一个或几个来进行测试。最佳选择就是getInterface,因为它对declarations对象的使用比较全面一点。有些东西我们能够通过getInterface方法轻易感知到的,却并不能同样容易地通过getDeclaration或getDeclarationCount感知到。如果要描述CppClass的话,我并不介意只对getInterface编写测试,但这么一来就很遗憾,不能覆盖到getDeclaration和getDeclarationCount了。然而,如果getInterface的实现像下面这样呢?
public String getInterface(String interfaceName, int [] indices) {
String result = "class " + interfaceName + " {\npublic:\n";
for (int n = 0; n < indices.length; n++) {
Declaration virtualFunction = getDeclaration(indices[n]);
result += "\t" + virtualFunction.asAbstract() + "\n";
}
result += "};\n";
return result;
}
这里的改动很小:getInterface在内部使用了getDeclaration。因而我们的影响结构图便从图11-12变成了图11-13。

图11-12 CppClass的影响结构示意图

图11-13 修改后的CppClass的影响结构示意图
这只是一个小小的改动,然而带来的影响却是显著的。getInterface方法现在在内部使用了getDeclaration方法,所以在测试getInterface的同时也就连带测试了getDeclaration。
在消除了一点点的代码重复之后,我们往往能够得到一张“终点”更少的影响结构图。而后者则往往能够令你的测试决策更容易。
影响和封装
关于面向对象,一个常常被人们挂在嘴边的好处便是封装。我在向人们展示本书中的一些解依赖技术时,他们常常会指出许多解依赖技术都破坏了封装性。没错,的确如此。
封装的重要性毋庸置疑,然而更重要的是它的重要性背后的原因。封装有助于我们对代码进行推测。跟封装不佳的代码相比,理解封装良好的代码所需要跟踪的路径更少。例如,假设我们给一个构造函数新添一个参数来达到解依赖的目的(参数化构造函数),则在推测影响的时候就可能需要多考虑一条路径了。没错,打破封装会令代码中的影响推测变得更难,然而若是最终我们能够给出具有很好的说明和阐释作用的测试,情况就恰恰相反了。因为一旦一个类有了测试用例,就可以使用这些测试用例来更为直接地进行影响推测了。如果对代码的行为有任何问题,都可以去编写新的测试。
实际上封装与测试覆盖也并不总是冲突的,只不过,当它们真的发生冲突时,我会倾向于选择测试覆盖。通常它能够帮助我实现更多的封装。
而且封装本身也并不是最终目的,而是帮助理解代码的工具。
当需要确定在何处编写测试时,很重要的一点就是要弄清修改会带来哪些影响。为此我们得进行影响推测。这种推测可以是非正式的,也可以稍微严密一些,借助一点草图来进行。但最重要的就是要知道这些工夫花得都是值得的。在特别错综复杂的代码当中,要想将测试安置到位,这是我们所能依赖的为数不多的几项技能之一。







