7000 条引用的 Java 源码重构

发布于 2024-09-26 14:48:51 字数 329 浏览 2 评论 0 原文

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

具体来说,方法 void log(String) 将采用两个附加参数(Class c, String methodName),这两个参数需要由调用者提供,具体取决于所在的方法它被称为。我不能简单地传递 null 或类似的内容。

为了了解范围,Eclipse 发现了 7000 个对该方法的引用,因此如果我更改它,整个项目就会崩溃。我需要几周时间才能手动修复它。

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

I need to change the signature of a method used all over the codebase.

Specifically, the method void log(String) will take two additional arguments (Class c, String methodName), which need to be provided by the caller, depending on the method where it is called. I can't simply pass null or similar.

To give an idea of the scope, Eclipse found 7000 references to that method, so if I change it the whole project will go down. It will take weeks for me to fix it manually.

As far as I can tell Eclipse's refactoring plugin of Eclipse is not up to the task, but I really want to automate it.
So, how can I get the job done?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(12

时光暖心i 2024-10-03 14:48:51

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


我认为你需要做的是使用像 javaparser 来执行此操作。

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

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

Great, I can copy a previous answer of mine and I just need to edit a tiny little bit:


I think what you need to do is use a source code parser like javaparser to do this.

For every java source file, parse it to a CompilationUnit, create a Visitor, probably using ModifierVisitor as base class, and override (at least) visit(MethodCallExpr, arg). Then write the changed CompilationUnit to a new File and do a diff afterwards.

I would advise against changing the original source file, but creating a shadow file tree may me a good idea (e.g. old file: src/main/java/com/mycompany/MyClass.java, new file src/main/refactored/com/mycompany/MyClass.java, that way you can diff the entire directories).

╰◇生如夏花灿烂 2024-10-03 14:48:51

Eclipse 能够使用 Refactor -> 来做到这一点。更改方法签名并为新参数提供默认值。

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

Eclipse is able to do that using Refactor -> Change Method signature and provide default values for the new parameters.

For the class parameter the defaultValue should be this.getClass() but you are right in your comment I don't know how to do for the method name parameter.

各自安好 2024-10-03 14:48:51

IntelliJ IDEA 应该不会有任何问题。

我不是 Java 专家,但类似的东西可以工作。这不是一个完美的解决方案(甚至可能是一个非常糟糕的解决方案),但它可以帮助您入门:

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

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

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

IntelliJ IDEA shouldn't have any trouble with this.

I'm not a Java expert, but something like this could work. It's not a perfect solution (it may even be a very bad solution), but it could get you started:

Change the method signature with IntelliJ's refactoring tools, and specify default values for the 2 new parameters:

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

or better yet, simply specify null as the default values.

归途 2024-10-03 14:48:51

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

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

我已经使用 Eclipse 重构进行了相当大的更改来修复旧的臭代码 - 如今它相当健壮。

I think that there are several steps to dealing with this, as it is not just a technical issue but a 'situation':

  1. Decline to do it in short order due to the risk.
  2. Point out the issues caused by not using standard frameworks but reinventing the wheel (as Paul says).
  3. Insist on using Log4j or equivalent if making the change.
  4. Use Eclipse refactoring in sensible chunks to make the changes and deal with the varying defaults.

I have used Eclipse refactoring on quite large changes for fixing old smelly code - nowadays it is fairly robust.

塔塔猫 2024-10-03 14:48:51

也许我很天真,但为什么不能重载方法名称呢?

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

void thing(paramA, paramB, paramC) {
    // new method
}

Maybe I'm being naive, but why can't you just overload the method name?

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

void thing(paramA, paramB, paramC) {
    // new method
}
很快妥协 2024-10-03 14:48:51

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

Do you really need to change the calling code and the method signature? What I'm getting at is it looks like the added parameters are meant to give you the calling class and method to add to your log data. If the only requirement is just adding the calling class/method to the log data then Thread.currentThread().getStackTrace() should work. Once you have the StackTraceElement[] you can get the class name and method name for the caller.

一笔一画续写前缘 2024-10-03 14:48:51

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

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

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

If the lines you need replaced fall into a small number of categories, then what you need is Perl:

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

I'm guessing that it wouldn't be too hard to hack together a script which would put the classname (derived from the filename) in as the second argument. Getting the method name in as the third argument is left as an exercise to the reader.

掌心的温暖 2024-10-03 14:48:51

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

Try refactor using intellij. It has a feature called SSR (Structural Search and Replace). You can refer classes, method names, etc for a context. (seanizer's answer is more promising, I upvoted it)

埋情葬爱 2024-10-03 14:48:51

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

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

我们的 DMS 软件重新工程工具包 可以为多种语言完成所有这些工作,包括爪哇。它解析完整 java 系统源代码,构建抽象语法树(针对整套代码)。

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

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

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

这条规则是什么说的是,找到对具有单个字符串参数的 log 的调用,并将其替换为对 log 的调用,该调用具有由辅助函数确定的另外两个参数 class方法。

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

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

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

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

... if IsDesiredLog().

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

I agree with Seanizer's answer that you want a tool that can parse Java. That's necessary but not sufficient; what you really want is a tool that can carry out a reliable mass-change.

To do this, you want a tool that can parse Java, can pattern match against the parsed code, install the replacement call, and spit out the answer without destroying the rest of the source code.

Our DMS Software Reengineering Toolkit can do all of this for a variety of languages, including Java. It parses complete java systems of source, builds abstract syntax trees (for the entire set of code).

DMS can apply pattern-directed, source-to-source transformations to achieve the desired change.

To achieve the OP's effect, he would apply the following program transformation:

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

What this rule says is, find a call to log which has a single string argument, and replace it with a call to log with two more arguments determined by auxiliary functions class and method.

These functions determine the containing method name and containing class name for the AST node root where the rule finds a match.

The rule is written in "source form", but actually matches against the AST and replaces found ASTs with the modified AST.

To get back the modified source, you ask DMS to simply prettyprint (to make a nice layout) or fidelity print (if you want the layout of the old code preserved). DMS preserves comments, number radixes, etc.\

If the exisitng application has more than one defintion of the "log" function, you'll need to add a qualifier:

... if IsDesiredLog().

where IsDesiredLog uses DMS's symbol table and inheritance information to determine if the specific log refers to the definition of interest.

浪漫人生路 2024-10-03 14:48:51

事实上,您的问题不在于使用允许您替换所有出现的

log("some weird message");

by

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

的点击播放引擎,因为它几乎没有机会处理各种情况(例如静态方法)。

我倾向于建议您看一下spoon。该工具允许源代码解析和转换,允许您以(显然是基于代码的)缓慢但受控的操作来实现您的操作。

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

Il fact your problem is not to use a click'n'play engine that will allow you to replace all occurences of

log("some weird message");

by

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

As it has few chances to work on various cases (like static methods, as an example).

I would tend to suggest you to take a look at spoon. This tool allows source code parsing and transformation, allowing you to achieve your operation in a -obviously code based- slow, but controlled operation.

However, you could alos consider transforming your actual method with one exploring stack trace to get information or, even better, internally use log4j and a log formatter that displays the correct information.

情魔剑神 2024-10-03 14:48:51

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

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

祝你好运

I would search and replace log( with log(@class, @methodname,

Then write a little script in any language (even java) to find the class name and the method names and to replace the @class and @method tokens...

Good luck

清眉祭 2024-10-03 14:48:51

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

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
}

If the class and method name are required for "where did this log come from?" type data, then another option is to print out a stack trace in your log method. E.g.

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
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文