0

作为维护大量遗留代码的一部分,我们需要更改部分设计,主要是使其更具可测试性(单元测试)。我们需要解决的问题之一是组件之间的现有接口。两个组件之间的接口是一个只包含静态方法的类。

简化示例:

class ABInterface {

    static methodA();
    static methodB();
    ...
    static methodZ();
};

该接口由组件 A 使用,因此不同的方法可以使用 ABInterface::methodA() 来准备一些输入数据,然后调用组件 B 中的适当函数。

现在我们出于各种原因尝试重新设计这个界面:

  • 扩展我们的单元测试覆盖率——我们需要解决组件之间的这种依赖关系,并且要引入存根/模拟

  • 这些组件之间的接口与原始设计不同(即,用于组件间 i/f 的许多新功能是在此接口类之外创建的)。

  • 代码很旧,随着时间的推移发生了很大变化,需要重构。

更改不应对系统的其余部分造成破坏。我们试图限制在生产代码中留下许多测试所需的工件。性能非常重要,在重新设计后应该没有(或非常少)降级。代码是 C++ 中的 OO。

我正在寻找一些想法采取什么方法。关于如何有效地做到这一点的任何建议?

4

3 回答 3

1

最简单的答案是将具有静态接口的旧库包装在外观中,然后重构代码以调用新外观而不是旧库。这个新的楔子应该允许替换库以进行单元测试。首先在一个方法上测试它,只是为了看看如何实现它。

真正困扰我的是当一个问题被“性能问题”污染时。我们都编写过性能关键代码,没有人故意建议编写性能不佳的代码。我发现这些“担忧”通常来自那些谴责每一个变化的反对者,并且真的不知道为什么某个变化会或不会表现良好。请记住,唯一有效的性能证明来自测试。运行性能测试并建立基线。进行更改。在新代码中再次运行性能测试。展示实际影响。只有这样,您才能对变更的实际影响做出决定。除非另有说明,否则您绝不应允许对周期的吹毛求疵支配您的设计。

听起来您项目中的重要人物是一位老 C 程序员*,他很久以前就听到有人大声说“让 C++ 快速运行的唯一方法是使用静态方法”。问题是他仍然相信。20 年前,大嘴可能是对的,但是编译器和优化器在这 20 年里有了很大的改进。所以试试你的改变。

如果您使用现代编译器,优化器无论如何都会删除目标代码中的额外取消引用,这意味着您根本不会增加任何运行时影响。

如果性能至关重要,但您没有进行性能测试,或者您没有使用现代编译器,或者您没有调整所有发布构建优化(例如,使用 Profile Guided Optimization)那么在担心额外抽象层的性能之前,您需要处理更大的工程问题。

  • 注意:我也是一个老 C 程序员,在 20 年前也曾经说过类似的愚蠢的话。不同之处在于,我了解到一些优化比其他优化要重要得多,而且新的编译器非常擅长自己解决大多数问题。我过早地“优化”事物的尝试通常以维护成本高昂的代码而告终,这些代码通常无论如何都不会胜过库存编译器设置。
于 2010-06-05T06:11:31.333 回答
0

如果 methodsA-Z 是非静态的和虚拟的,你可以很容易地做到这一点,对吗?因此,如果静态方法 A-Z 要调用非静态的虚拟方法 A-Z,您将能够覆盖行为。了解这一点后,您需要一种方法来更改包含用于测试的非静态版本的实例。

或者,您可以采用要重构的两个类,并让它们使用这个仅静态类的包装器。然后两者可以根据需要分歧。

这就是我所有的想法,而没有考虑真正的问题。

于 2010-06-03T15:56:20.383 回答
0

感谢您的回答和评论。

在阅读《有效地使用遗留代码》一书的“依赖打破技术”一章之后,以下两种技术的组合实际上似乎是我们问题的解决方案:

  • Instance Delegator - 用新的虚拟方法包装/替换实用程序类的静态方法。
  • 静态 Setter - 启用不同实用程序类(生产代码或存根代码)的实例化。

将这两者结合起来将使我们能够将被测组件与产品代码的其余部分分离。

我们现在唯一关心的是性能损失(由于使用了虚函数)。

于 2010-06-04T06:34:48.120 回答