【问题标题】:simplifying code via refactoring通过重构简化代码
【发布时间】:2011-08-26 20:42:08
【问题描述】:

是否有用于 C 或 Java 的重构工具可以简化此类冗余代码。我相信这就是所谓的数据传播。

这基本上是优化编译器会做的事情。

public int foo() {
    int a = 3;
    int b = 4;
    int c = a + b;
    int d = c;
    System.out.println(c);
    return c;
}

进入

public int foo() {
    int c = 7;
    System.out.println(c);
    return c;
}

【问题讨论】:

  • 如果编译器仍然这样做,你为什么要这样做?
  • 这里进行了两种转换:“常量传播”和“常量折叠”。但这不是您要摆脱的“冗余”代码。这是不必要的代码。有人争辩说,放置“不必要的”代码是为了区分问题。 (其中一些可能只是愚蠢的,当然)。
  • 如果a和b的值不再是3和4呢?
  • @undur_gongor:原因是出错的空间更小,一般来说,代码越少,错误就越少。
  • @Jack:是的,但是对于重构,代码必须已经正确。而且我猜在大多数情况下,精心设计的代码在可维护性/可读性方面会更好(参见 Alfredo O 的回答)。

标签: java c refactoring


【解决方案1】:

我认为这不是一个好主意。

例如下面的代码:

long hours = 5;
long timeInMillis = hours * 60 * 1000;

这比仅仅更简洁和易于理解:

long timeInMillis = 300000;

【讨论】:

  • 与 OP 的要求并不真正相关,下面的机器代码与正在显示的高级语言有关。
  • @Woot4Moo -- 机器码?没有人提到他写的关于代码清晰度和人类理解的机器代码。他正确地指出,任何阅读第一个版本的人都可以清楚地理解我们所说的间隔是五小时,并且可以验证该值是否正确。在第二种情况下,即使变量被称为“五小时”,代码审查员也可能会错过一个错误的常量值,例如 3000000。
  • @James 他在哪里问过关于可读性的任何问题。我想他可能在谈论代码优化。
  • @Woot4Moo——任何现代编译器都会自动优化它。因此,除了清理源代码之外,没有其他动机可以这样做。然而,降低代码的可读性并不是一个好主意。
【解决方案2】:

我可以为 C 提供一个解决方案。我的解决方案使用我在另一个答案 here 中描述的两个工具(以相反的顺序)。

这是你的程序,翻译成 C:

int foo() {
    int a = 3;
    int b = 4;
    int c = a + b;
    int d = c;
    printf("%d", c);
    return c;
}

第 1 步:不断传播

$ frama-c -semantic-const-folding t.c -lib-entry -main foo
...
/* Generated by Frama-C */
/*@ behavior generated:
      assigns \at(\result,Post) \from \nothing;  */
extern int ( /* missing proto */  printf)() ;
int foo(void) 
{
  int a ;
  int b ;
  int c ;
  int d ;
  a = 3;
  b = 4;
  c = 7;
  d = 7;
  printf("%d",7);
  return (c);
}

第 2 步:切片

$ frama-c -slice-calls printf -slice-return foo -slice-print tt.c -lib-entry -main foo
...
/* Generated by Frama-C */
extern int printf() ;
int foo(void) 
{
  int c ;
  c = 7;
  printf("%d",7);
  return (c);
}

【讨论】:

    【解决方案3】:

    是的,我见过人们使用的最好的重构工具就是他们的大脑。

    大脑似乎是一个非常好的工具,可以在逻辑上组织代码以供其他大脑使用。它还可用于在适当的情况下使用 cmets 增强代码,并赋予布局和命名附加意义。

    编译器有利于优化代码以供更接近构成处理器的晶体管的底层使用。上一代编程语言的好处之一是它不像机器制造的那样读起来。

    抱歉,如果这看起来有点油嘴滑舌且没有帮助。我当然使用过各种工具,但我不记得有任何处理“数据传播”的工具。

    【讨论】:

      【解决方案4】:

      Eclipse(我确信 NetBeans 和 IntelliJ)几乎提供了所有这些重构功能。我将给出 Eclipse 的细节。开始:

      public int foo() {
          int a = 3;
          int b = 4;
          int c = a + b;
          int d = c;
          System.out.println(c);
          return c;
      }
      

      首先,d 将显示为警告您有一个未读的局部变量。 <CTRL>+1 在该行上并选择“删除 d 和所有分配”。那么你有:

      public int foo() {
          int a = 3;
          int b = 4;
          int c = a + b;
          System.out.println(c);
          return c;
      }
      

      接下来,突出显示int c = a + b; 中的a,然后键入<CTRL>+<ALT>+I 以内联a。重复b,您将拥有:

      public int foo() {
          int c = 3 + 4;
          System.out.println(c);
          return c;
      }
      

      现在你快到了。我不知道将 3+4 转换为 7 的重构。似乎有人很容易实现,但可能不是常见的用例,因为其他人指出,根据域,3 +4 可以比 7 更具表现力。您可以更进一步并内联 c,给您:

      public int foo() {
          System.out.println(3 + 4);
          return 3 + 4;
      }
      

      但如果不知道原始代码的“真正”问题,就不可能知道这是改进还是倒退。

      【讨论】:

        【解决方案5】:

        代码的语义信息可能会丢失。可能的依赖关系可能会中断。简而言之:只有程序员知道哪些变量是重要的或可能变得重要,因为只有程序员知道代码的上下文。恐怕您必须自己进行重构

        【讨论】:

        • 如果我手动执行此操作而不是使用不会累的计算机,事情更有可能发生故障
        • 哦,这只是实践问题,真的。另外,请记住,更少的代码行数并不一定意味着更好的代码。
        【解决方案6】:

        是的,IntelliJ 在其社区版中提供了此功能。现在要解决一个更严重的问题,我很确定您将编译与重构混为一谈。当您编译某些东西时,您会使用一种高于机器代码的语言并将其转换为机器代码(本质上)。您想要的是删除高级语言(即您的程序文件 .c、.java 等)中多余的声明。编译器很可能已经将不太好的代码优化为您建议的代码,有可用的工具可以查看它在做什么。就重构而言,通常越少越好,但不要为了更少的代码行而牺牲可维护性。

        【讨论】:

        • 在 INtelliJ 中这个功能叫什么?我浏览了他们的整个 Refactor 菜单,没有发现任何合适的东西。
        • 它使用开箱即用的配置自动完成(当然我是付费版本)
        【解决方案7】:

        一种可能的方法是将其放入符号数学程序(如 Mathematica 或 Maple)中,并让它为您进行简化。不管它们是否是常量,它都会这样做。

        缺点是您需要将代码转换为不同的语言。 (虽然如果语法相似,它可能主要是复制和粘贴。)此外,如果您期望某些整数类型以特定大小溢出,这可能是危险的。符号数学程序不在乎,将根据“数学”对其进行优化。浮点舍入误差也是如此。

        在您的示例中,如果您将其输入 Mathematica:

        a = 3;
        b = 4;
        c = a + b;
        d = c;
        c
        

        将在 Mathematica 中输出:

        7
        

        当然,您不能只是复制和粘贴,因为它是一种不同的语言和不同的语法,但对于您的问题,这是我想到的最好的东西。我自己使用 Mathematica 来简化表达式和其他数学,然后再将其放入 C/C++ 中。

        对于一个涉及未知数的更复杂的例子:

        原始C代码:

        int a = 3 + x*x;
        int b = 4 + y*y;
        int c = a + b - 7 + 2*x*y;
        int d = c;
        

        在 Mathematica 中输入(仍然主要是复制+粘贴):

        a = 3 + x*x;
        b = 4 + y*y;
        c = a + b - 7 + 2*x*y;
        d = c;
        FullSimplify[c]
        

        输出:

        (x + y)^2
        

        转换回以下 C 代码:

        d = (x + y)
        d = d * d;
        

        这显然比原始代码简单得多。一般来说,符号程序甚至可以处理非平凡的表达式,并且会比任何内部编译器做得一样好(甚至更好)。

        最后一个缺点是像 Mathematica 或 Maple 这样的符号数学程序不是免费的,而且相当昂贵。 SAGE 是一个开源程序,但我听说它不如 Mathematica 或 Maple。

        【讨论】:

          【解决方案8】:

          如果您在谈论 C,您可以查看已编译、优化的汇编代码。然后,您可以将 C 代码重构为与优化程序集相同的结构。不过,正如 Alfredo 所说,这可能会导致代码更加模棱两可。

          【讨论】:

          • 没有什么比使用寄存器名称而不是变量名称来使代码更具可读性。
          【解决方案9】:

          为什么不使用优化编译器来编译代码。然后反编译代码。这只是我的想法,我没有尝试过。

          【讨论】:

          • 也许你应该先看看优化编译器的输出。
          • 首先编写更好的代码可能更容易
          • @Saideira,我不需要查看优化编译器的输出。我认为它非常模糊,但正确。如果它不正确,那么你应该得到一个新的编译器。
          • @Jodrell,我同意。我不明白为什么 Saideira 需要做这种“代码简化”。我倾向于将“代码简化”视为编写好代码的反效果。
          猜你喜欢
          • 1970-01-01
          • 2022-12-23
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2019-11-27
          • 1970-01-01
          相关资源
          最近更新 更多