【问题标题】:How to validate that a Refactoring is equal to original code如何验证重构是否等于原始代码
【发布时间】:2010-01-26 08:13:44
【问题描述】:

我正在使用遗留的 java 代码,没有任何单元测试。许多类需要重构才能与项目一起使用。

很多重构都可以用 Eclipse 完成,而我是手动做的。经过一些重构后,我查看了与 cvs-HEAD 的差异,但我不能确定一切都是 100% 正确的。

问题:如何验证重构,即数学与以前的版本相同?我希望有一个工具,但我也接受“基本人类算法”作为解决方案。

我知道,“运行你的 JUnit-Tests”是最好的答案,但遗憾的是,我的项目中没有。

谢谢!

【问题讨论】:

  • 先写 JUnit 测试?通过推迟编写它们(或根本不编写它们),您可能会花费更多时间来修复错误而不是编写测试。
  • 我发现使用 Eclipse 或 intellij 之类的 IDE 进行自动重构几乎总是会为您提供与原始语义完全相同的代码,极少数情况下会出错(主要是在 Class. forname() 调用,有时 Intellij 会感到困惑并生成无法编译的代码)。

标签: java validation refactoring


【解决方案1】:

在“TDD By Example”中有一个特定的部分讨论它。问题是你需要单元测试来重构,但复杂的代码通常是不可测试的。因此,您想要重构以使其可测试。循环。

因此最佳策略如下:

做微小的重构步骤。当步骤较小时,人类更容易确保整体行为完好无损。只选择提高可测试性的重构。这是你的直接目标。不要考虑支持未来的功能(或任何类似的功能)。想想“我怎样才能让单元测试来测试这个方法/类”。

一旦方法/类变得可测试,就为其编写单元测试。

重复此过程将逐渐使您达到可以进行测试的位置,因此您可以更积极地进行重构。通常,此过程比预期的要短。

【讨论】:

【解决方案2】:

如果您认为可以通过目测程序来检测到这一点,那么您将走下坡路。正如其他响应者之一已经说过的那样,两个程序是否相等的问题是无法确定的(通过图灵机)。

如果您没有单元测试,我建议您至少设置一个回归测试工具。拍摄第 1 版程序的一些输入和一些输出的快照,并在第 2 版中运行它并确保结果相同。

如果是GUI,希望有MVC分离,可以单独测试模型,否则可能会卡住。

【讨论】:

    【解决方案3】:

    问题:如何验证 重构,即数学 和以前的版本一样吗?一世 希望有一个工具,但我也 接受“基本人类算法”为 解决方案。

    严格来说,重构是一组众所周知的转换,已被证明可以保留代码的语义。见Refactoring Object-Oriented Frameworks。 其他一切都应该称为re-engineering,但我同意两者可以互换使用。

    关于语义保存的推理很难,并且是一个开放的研究课题。例如见Formalising Behaviour Preserving Program Transformations

    在我们有有效的工具来检查语义保存之前,最好还是依靠测试。另一种让您对更改更有信心的方法是添加assertionscontracts。它将迫使您进行审查并考虑可能发生的变化,不变量是什么,可以更深入地打破什么。

    【讨论】:

      【解决方案4】:

      恐怕没有一种算法可以验证一个程序在语义上是否与另一个程序相同 - 这有点像是一个停止问题,并且被证明是无法解决的。

      另一种稍微自动化的方法是比较两个程序的输出。然而,这对于任何大型程序来说都很难,因为可能没有明确定义的输入范围......

      也许是你编写了你觉得如此缺乏的单元测试的时候了?

      编辑:假设你会接受人类算法——这是我通常做的。我会研究要重构的代码,并理解它的语义。至少为这部分代码库编写一个单元测试或某种自动化测试。执行重构,然后查看测试是否仍然通过。如果是这样,你很有可能重构(*)没有破坏任何东西。

      (*) 在这里,我的意思是重构以更改实现/算法等,而不仅仅是简单的重命名和改组代码以及将通用代码行制作成方法/基类等。只要你有那些你几乎可以眼球的东西对代码库有很好的理解。

      【讨论】:

        【解决方案5】:

        问题:我如何验证重构,即在数学上与以前的版本相同?我希望有一个工具,但我也接受“基本人类算法”作为解决方案。

        我同意 Chii 的观点,这本质上是不可能的。如果你真的需要重构它(而不是为遗留代码编写适配器,让你的新东西松散耦合到旧代码),你必须特别注意子类、重写方法等。编写单元测试可能会有所帮助,但是如果您实际上不知道代码应该做什么,您如何为它编写单元测试?您只能编写单元测试来确保新代码执行您认为旧代码执行的操作。

        【讨论】:

          【解决方案6】:

          我仍然会说“运行你的单元测试”;)。您必须真正确定结果相同的唯一选择是单元测试。您可以在重构特定代码之前自己添加它们。一开始需要更长的时间,但从长远来看会为您节省大量时间。

          【讨论】:

          • 如果你的单元测试不好怎么办?例如,它们不涵盖某些边缘情况?或者其他一些程序员同时出现并添加了一个没有单元测试的功能?你如何确保不会破坏这种功能?
          【解决方案7】:

          我会选择 Itay 的答案,但只是提供另一种选择。
          如果您愿意为此付费,Agitar 的产品可以自动为现有代码生成 JUnit 测试。这些测试是“表征”测试,旨在测试代码是否符合当前的情况。然后,一旦您进行更改,您就会看到中断的只是您想要更改的内容。

          更多信息请见here

          【讨论】:

            【解决方案8】:

            理论上,您可以定义一组安全转换(重构),并编写一个程序来检查程序 B 是否是将这些重构的有限子集应用于程序 A 的结果。(通过设置上限)。但是恐怕做这样的程序比为程序A编写单元测试要困难得多,这才是你真正应该做的。

            【讨论】:

              【解决方案9】:

              我对一个需要重构的大神类项目做了以下工作:

              使用 AOP 在方法开始和结束时“转储”对象状态和参数。也转储返回值。

              然后用你需要更改的代码记录很多场景,并使用你重构的代码重放它们。

              这不是数学,设置起来有点繁重,但是在您获得一组良好的非回归测试之后。

              使用 XStream 之类的工具可以轻松设置转储程序。

              【讨论】:

                【解决方案10】:

                我有点惊讶,到目前为止还没有人提到 Michael Feathers 的书Working Effectively with Legacy Code。它以各种语言处理这种确切的情况,并提供许多实用的建议。我向任何处理遗留项目的人推荐它。

                【讨论】:

                  猜你喜欢
                  • 1970-01-01
                  • 2018-01-27
                  • 1970-01-01
                  • 2010-09-27
                  • 2013-02-08
                  • 1970-01-01
                  • 2010-12-24
                  • 2018-08-31
                  • 1970-01-01
                  相关资源
                  最近更新 更多