简化代码:通过重构实现

6

有没有一种重构工具,可以简化这种冗余代码,无论是用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;
}

14
如果编译器已经处理了这个,你为什么还想要这样做呢? - undur_gongor
2
这里进行了两个转换:“常量传播”和“常量折叠”。但你要消除的不是“冗余”的代码,而是不必要的代码。有人认为“不必要”的代码是为了分离问题而放置的。(其中一些可能只是愚蠢的,可以承认)。 - Ira Baxter
2
@undur_gongor:这样做的原因是为了减少错误的可能性,通常来说,代码越少,出错的机会就越少。 - Jack
1
@Jack:是的,但是重构代码之前必须先确保代码正确。我想在大多数情况下,精心编写的代码在可维护性/可读性方面会更好(请参见Alfredo O的答案)。 - undur_gongor
我真的很担心那位编写你如此渴望重构的代码片段的开发人员的技能。我认为在这种情况下,适当的工具应该是广泛的洗脑。;-) - Wivani
显示剩余3条评论
9个回答

20

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

例如下面这段代码:

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

这比仅仅使用下面的代码更为简洁易懂:

long timeInMillis = 300000;

这并不是与 OP 所询问的高级语言相关的机器代码。 - Woot4Moo
1
@Woot4Moo -- 机器码?没有人提到机器码,他在谈论代码清晰度和人类理解。他正确地指出,任何阅读第一个版本的人都可以清楚地理解我们正在讨论五小时的时间间隔,并且可以验证该值是否正确。在第二种情况下,即使变量被称为“fivehours”,代码审查员也可能会忽略错误的常量值,例如3000000。 - James Anderson
@James 他在哪里提到了可读性?我认为他可能在谈论代码优化。 - Manish Singh
2
@Woot4Moo -- 任何现代编译器都会自动优化这个过程。因此,做这件事情的唯一动机就是为了清理源代码。然而,使代码难以阅读并不是一个好主意。 - James Anderson

6
我可以提供一个关于C语言的解决方案。我的方案使用了我在另一个答案中描述的两个工具,链接在这里(倒序排列)。
以下是您的程序的C语言翻译:
int foo() {
    int a = 3;
    int b = 4;
    int c = a + b;
    int d = c;
    printf("%d", c);
    return c;
}

Step 1: Constant propagation

$ 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);
}

Step 2: Slicing

$ 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

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

大脑似乎是一个非常好的工具,可以逻辑地组织代码,以便其他人更好地理解。它还可以用于在适当的位置为代码添加注释,并通过布局和命名赋予附加含义。

编译器可用于优化代码,以便更接近构成处理器的晶体管的基础层消耗。更高一代的编程语言的好处之一是它不像由机器生成的东西那样难懂。

如果这听起来有些浅显和没用,我表示歉意。我肯定使用过各种工具,但我不记得有任何工具可以处理“数据传播”。


2

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;
}

但是,如果不知道原始代码的“真正”问题,就无法确定这是否是一种改进还是一种倒退。


1
一种可能的方法是将其放入符号数学程序(如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好。


1

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


如果我手动操作而不使用不会疲劳的计算机,那么事情更容易出错。 - Saideira
哦,这只是一个实践问题。同时要记住,代码行数越少并不一定意味着代码质量更好。 - Andreas Grapentin

1

是的,IntelliJ 在他们的社区版中提供了这个功能。现在来解决一个更严重的问题,我很确定你把编译和重构混淆了。当你编译某些东西时,你会将一种高于机器码的语言转换成机器码(基本上是这样)。你想要的是删除在程序文件(.c、.java 等)中高级语言中多余的声明。编译器很可能已经将不太好的代码优化成了你所提出的内容,有工具可以查看它正在做什么。在重构方面,通常越少越好,但不要为了减少代码行数而牺牲可维护性。


在IntelliJ中,这个功能叫什么?我浏览了他们整个的重构菜单,但没有看到合适的东西。 - Saideira
它会自动执行此操作,使用开箱即用的配置(前提是我使用付费版本)。 - Woot4Moo

0

如果你在谈论C语言,你可以查看编译后的优化汇编代码。然后,你可以将你的C代码重构为与优化汇编相同的结构。不过,正如Alfredo所说,这可能会导致更加模糊的代码。


3
使用寄存器名称而非变量名称有助于使代码更加易读。 - Ira Baxter

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

也许你应该先查看优化编译器输出。 - Saideira
最好一开始就写出更好的代码,这样可能会更容易。 - Jodrell
@Saideira,我不需要查看优化编译器的输出。我认为它可能会非常混乱,但是正确的。如果不正确,那么你应该获取一个新的编译器。 - emory
@Jodrell,我同意。我不明白为什么Saideira需要进行“代码简化”。我倾向于认为“代码简化”对编写好的代码是有害的。 - emory

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接