C ++重构:条件扩展和块消除

时间:2012-04-11 08:41:09

标签: c++ refactoring automated-refactoring

我正在重构大量代码(主要是C ++),以删除一些已经永久设置为给定值的临时配置检查。例如,我将拥有以下代码:

#include <value1.h>
#include <value2.h>
#include <value3.h>

...

if ( value1() )
{
    // do something
}

bool b = value2();

if ( b && anotherCondition )
{
    // do more stuff
}

if ( value3() < 10 )
{
    // more stuff again
}

其中对value的调用返回bool或int。因为我知道这些调用总是返回的值,所以我做了一些正则表达式替换以将调用扩展到它们的正常值:

// where:
//   value1() == true
//   value2() == false
//   value3() == 4

// TODO: Remove expanded config (value1)
if ( true )
{
    // do something
}

// TODO: Remove expanded config (value2)
bool b = false;

if ( b && anotherCondition )
{
    // do more stuff
}

// TODO: Remove expanded config (value3)
if ( 4 < 10 )
{
    // more stuff again
}

请注意,尽管这些值是固定的,但它们不是在编译时设置的,而是从共享内存中读取的,因此编译器当前没有在幕后优化任何内容。

虽然结果代码看起来有点傻,但这种正则表达式方法实现了我想要的很多东西,因为它很容易应用并消除了对调用的依赖,同时不改变代码的行为,编译器也可能知道块永远不会被调用或者检查总是返回true,因此优化了很多。它也使得它变得相当容易(特别是在对版本控制进行区分时)以查看已更改的内容并采取最后一步清理它,因此上面的代码最终看起来如下:

// do something

// DONT do more stuff (b being false always prevented this)

// more stuff again

麻烦的是,我有数百(可能是数千)的更改要从第二个,正确但愚蠢的阶段获得最终清理代码。

我想知道是否有人知道可以处理这个或我可以应用的任何技术的重构工具。主要问题是C ++语法使得完全扩展或消除非常难以实现,并且上面的代码有许多排列。我觉得我几乎需要一个编译器来处理我需要涵盖的语法变化。

我知道有类似的问题,但我找不到任何相似的要求,也想知道是否有任何工具或程序出现,因为他们被问到了?

2 个答案:

答案 0 :(得分:5)

听起来你有我称之为“僵尸代码”......在实践中已经死了,但就编译器而言仍然存在。对于大多数有组织的运行时配置变量系统来说,这是一个非常常见的问题:最终一些配置变量达到永久固定状态,但在运行时重复进行重新评估。

正如您所注意到的,固化不是正则表达式,因为正则表达式无法可靠地解析C ++代码。 你需要的是program transformation system。这是一个真正解析源代码的工具,可以将一组代码到代码的重写规则应用于解析树,并可以从更改的树中重新生成源文本。

我知道Clang在这里有一些能力;它可以解析C ++并构建一个树,但它没有源到源的转换功能。您可以通过编写AST-to-AST转换来模拟该功能,但这样会更加不方便恕我直言。我相信它可以重新生成C ++代码,但我不知道它是否会保留注释或预处理器指令。

我们的DMS Software Reengineering Toolkit及其C++(11) front end可以(并且已经习惯)对C ++源代码进行大规模转换,并具有源到源转换。 AFAIK,它是唯一可以做到这一点的生产工具。您需要的是一组转换,表示您对所关注的配置变量的最终状态的了解,以及一些简单的代码简化规则。以下DMS规则接近您可能需要的规则:

  rule fix_value1():expression->expression
    "value1()" -> "true";
  rule fix_value2():expression->expression
    "value2()" -> "false";
  rule fix_value3():expression->expression
    "value3()" -> "4";

  rule simplify_boolean_and_true(r:relation):condition->condition
     "r && true" -> "r".
  rule simplify_boolean_or_ture(r:relation):condition->condition
     "r || true" -> "true".
  rule simplify_boolean_and_false(r:relation):condition->condition
     "r && false" -> "false".
  ...
  rule simplify_boolean_not_true(r:relation):condition->condition
     "!true" -> "false".
  ...

  rule simplify_if_then_false(s:statement): statement->statement
      " if (false) \s" -> ";";
  rule simplify_if_then_true(s:statement): statement->statement
      " if (true) \s" -> "\s";
  rule simplify_if_then_else_false(s1:statement, s2:statement): statement->statement
      " if (false) \s1 else \s2" -> "\s2";
  rule simplify_if_then_else_true(s1:statement, s2: statement): statement->statement
      " if (true) \s1 else \s2" -> "\s2";

您还需要规则来简化(“折叠”)涉及算术的常量表达式,以及在现在常量的表达式上处理开关的规则。要查看整数常量折叠的DMS规则,请参阅Algebra as a DMS domain

与正则表达式不同,DMS重写规则不能“不匹配”代码;它们代表相应的AST,它是匹配的AST。因为它是AST匹配,所以它们没有空格,换行符或注释的问题。您可能认为他们可能遇到操作数顺序的问题(如果遇到“假”和“&amp; x”会怎么样?');它们没有,因为&amp;&amp;&amp; || 的语法规则在DMS C ++解析器中标记为关联和可交换,匹配过程自动将其考虑在内

这些规则本身不能做的就是跨任务的价值(在你的情况下,不变)传播。为此,您需要流量分析,以便您可以跟踪此类分配(“到达定义”)。显然,如果你没有这样的任务或很少,你可以手动修补那些。如果你这样做,你需要流量分析;唉,DMS的C ++前端并不完全存在,但我们正在研究它;我们有控制流程分析。 (DMS的C前端有全流量分析)。

(编辑2015年2月:现在完整的C ++ 14;功能/方法中的流程分析)。

我们实际上在十年前将这种技术应用于来自IBM Tivoli的混合C和C ++代码的1.5M SLOC应用程序并取得了极大的成功;我们不需要流量分析: - }

答案 1 :(得分:1)

你说:

  

请注意,尽管这些值是合理修复的,但它们不是在编译时设置的,而是从共享内存中读取的,因此编译器当前没有在幕后优化任何内容。

手动不断折叠值并没有多大意义,除非它们完全修复。如果您的编译器提供constexpr,您可以使用它,或者您可以在预处理器宏中替换如下:

#define value1() true
#define value2() false
#define value3() 4

优化器会从那里照顾你。如果没有看到<valueX.h>标题中的确切内容或知道从共享内存中获取这些值的过程是如何工作的示例,我只是抛弃重命名现有的valueX()函数并执行此操作运行时检查以防将来再次更改:

// call this at startup to make sure our agreed on values haven't changed
void check_values() {
    assert(value1() == get_value1_from_shared_memory());
    assert(value2() == get_value2_from_shared_memory());
    assert(value3() == get_value3_from_shared_memory());
}