23

我需要更改整个代码库中使用的方法的签名。

具体来说,该方法void log(String)将采用两个额外的参数 ( Class c, String methodName),这两个参数需要由调用者提供,具体取决于调用它的方法。我不能简单地通过null或类似。

为了给出范围的概念,Eclipse 找到了 7000 个对该方法的引用,所以如果我更改它,整个项目将会失败。我需要数周时间才能手动修复它。

据我所知,Eclipse 的 Eclipse 重构插件不能胜任这项任务,但我真的很想自动化它。
那么,我怎样才能完成工作呢?

4

12 回答 12

22

太好了,我可以复制我以前的答案,我只需要编辑一点点:


我认为您需要做的是使用像javaparser这样的源代码解析器来执行此操作。

对于每个 java 源文件,将其解析为 CompilationUnit,创建一个 Visitor,可能使用ModifierVisitor作为基类,并覆盖(至少)visit(MethodCallExpr, arg)。然后将更改后的 CompilationUnit 写入一个新文件,然后进行比较。

我建议不要更改原始源文件,但创建影子文件树可能是个好主意(例如 old file: src/main/java/com/mycompany/MyClass.java, new file src/main/refactored/com/mycompany/MyClass.java,这样您就可以区分整个目录)。

于 2010-10-11T13:26:06.767 回答
6

Eclipse 能够使用Refactor -> Change Method 签名来做到这一点,并为新参数提供默认值。

对于类参数,defaultValue 应该是 this.getClass() 但您的评论是对的,我不知道如何处理方法名称参数。

于 2010-10-11T12:57:41.297 回答
3

IntelliJ IDEA对此应该没有任何问题。

我不是 Java 专家,但这样的事情可能会奏效。这不是一个完美的解决方案(它甚至可能是一个非常糟糕的解决方案),但它可以让你开始:

使用 IntelliJ 的重构工具更改方法签名,并为 2 个新参数指定默认值:

c: self.getClass()
methodName: Thread.currentThread().getStackTrace()[1].getMethodName()

或者更好的是,只需将null指定为默认值。

于 2010-10-11T12:35:54.140 回答
2

我认为有几个步骤可以解决这个问题,因为这不仅仅是一个技术问题,而是一个“情况”:

  1. 由于风险,拒绝在短期内这样做。
  2. 指出不使用标准框架而是重新发明轮子(正如保罗所说)所导致的问题。
  3. 如果进行更改,请坚持使用 Log4j 或等效的。
  4. 在合理的块中使用 Eclipse 重构来进行更改并处理不同的默认值。

我已经在相当大的更改上使用 Eclipse 重构来修复旧的臭代码 - 现在它相当健壮。

于 2010-10-11T13:26:39.107 回答
2

也许我太天真了,但你为什么不能重载方法名呢?

void thing(paramA) {
    thing(paramA, THE_DEFAULT_B, THE_DEFAULT_C)
}

void thing(paramA, paramB, paramC) {
    // new method
}
于 2010-10-11T13:31:28.990 回答
2

您真的需要更改调用代码和方法签名吗?我要说的是,添加的参数似乎旨在为您提供调用类和方法以添加到您的日志数据中。如果唯一的要求只是将调用类/方法添加到日志数据中,那么 Thread.currentThread().getStackTrace() 应该可以工作。获得 StackTraceElement[] 后,您可以获得调用者的类名和方法名。

于 2010-10-11T15:03:47.913 回答
1

如果您需要替换的行属于少数类别,那么您需要的是 Perl:

find -name '*.java' | xargs perl -pi -e 's/log\(([^,)]*?)\)/log(\1, "foo", "bar")/g'

我猜想将一个将类名(从文件名派生)作为第二个参数放入的脚本不会太难。将方法名称作为第三个参数作为练习留给读者。

于 2010-10-11T13:10:07.700 回答
1

尝试使用 intellij 重构。它有一个称为 SSR(结构搜索和替换)的功能。您可以为上下文引用类、方法名称等。(seanizer 的回答更有希望,我赞成)

于 2010-10-11T14:50:56.147 回答
1

我同意 Seanizer 的回答,即您需要一个可以解析 Java 的工具。这是必要的,但还不够;你真正想要的是一个可以进行可靠的大规模更改的工具。

为此,您需要一个可以解析 Java、可以对已解析代码进行模式匹配、安装替换调用并在不破坏源代码的其余部分的情况下吐出答案的工具。

我们的DMS Software Reengineering Toolkit可以为各种语言(包括 Java)完成所有这些工作。它解析完整的 Java 源代码系统,构建抽象语法树(用于整个代码集)。

DMS 可以应用模式导向的源到源转换来实现所需的更改。

为了达到OP的效果,他将应用以下程序转换

 rule replace_legacy_log(s:STRING): expression -> expression
    " log(\s) " -> " log( \s, \class\(\), \method\(\) ) "

这条规则的意思是,找到一个对 log 的调用,它有一个字符串参数,并将其替换为对 log 的调用,其中两个参数由辅助函数 classmethod确定。

这些函数确定规则找到匹配项的 AST 节点根的包含方法名称和包含类名称。

该规则以“源格式”编写,但实际上与 AST 匹配,并用修改后的 AST 替换找到的 AST。

要取回修改后的源代码,您可以要求 DMS 简单地进行漂亮打印(以制作漂亮的布局)或保真打印(如果您希望保留旧代码的布局)。DMS 保留注释、数字基数等。\

如果现有应用程序对“日志”函数有多个定义,则需要添加一个限定符:

... if IsDesiredLog().

其中IsDesiredLog使用 DMS 的符号表和继承信息来确定特定日志是否引用了感兴趣的定义。

于 2010-10-12T07:43:11.560 回答
0

事实上,您的问题不是使用允许您替换所有出现的点击播放引擎

log("some weird message");

经过

log(this.getClass(), new Exception().getStackTrace()[1].getMethodName());

因为它很少有机会处理各种情况(例如静态方法)。

我倾向于建议你看看勺子。该工具允许对源代码进行解析和转换,使您能够以一种明显基于代码的缓慢但受控的操作来实现您的操作。

但是,您也可以考虑使用一个探索堆栈跟踪来转换您的实际方法以获取信息,或者更好的是,在内部使用 log4j 和显示正确信息的日志格式化程序。

于 2010-10-11T12:41:55.040 回答
0

我会搜索并替换log(log(@class, @methodname,

然后用任何语言(甚至是java)编写一个小脚本来查找类名和方法名并替换@class 和@method 标记......

祝你好运

于 2010-10-11T13:29:52.630 回答
0

如果“此日志来自哪里?”需要类和方法名称?输入数据,然后另一个选项是在您的日志方法中打印出堆栈跟踪。例如

public void log(String text)
{
   StringWriter sw = new StringWriter();
   PrintWriter pw = new PrintWriter(sw, true);
   new Throwable.printStackTrace(pw);
   pw.flush();
   sw.flush();
   String stackTraceAsLog = sw.toString();
   //do something with text and stackTraceAsLog
}
于 2010-10-11T14:31:44.340 回答