Refactoring Recording/Playback - Is it possible?

已回答

I would like to record refactoring operations in one Java project, a library in this case. Then be able to play back applicable refactoring changes in dependent project(s), with user assistance where necessary.

I see the following steps:

  1. Turn on refactoring recording in a project.
  2. All refactoring actions on global objects that affect dependencies (classes, methods, fields, files, packages, etc) are recorded in encoded form.
  3. Once refactoring is complete optimize recorded refactoring log:
  4. Resolve final actions (when multiple intermediate actions, for example rename A to B to C to D, only need rename A to D)
  5. Generate a refactoring file which can be run against another project in the IDE.
  6. In the new project generate the set of all references to refactored entities in the log file.
  7. Apply recorded refactoring to the referencing entities via the IDE refactoring API.
  8. Any failed refactoring is undone so it does not mess up later refactoring actions.
  9. Show a list of all refactoring operation results so the user can eyeball the failed refactoring actions and fix them manually.

The use case for this is an OSS Java library which is in dire need of attention. I have avoided doing cleanup refactoring to prevent users' code from breaking.

If I was able to do the cleanup and refactoring while having it recorded then the play back could be done by users on their projects. It would probably address 90% to 95% of the changes instead of upgrading the library and seeing a lot of red while scratching your head and having to search the code.

On my end, I do not have to meticulously document the changes because they will be recorded. Documentation can be generated from the log too.

I can see where this would be a huge benefit on interdependent projects which cannot be refactored as a single project. 

I was hoping someone has already implemented a plugin for this purpose but found none, so I am considering implementing it myself.

JetBrains Tools are already the best for refactoring. Extending this functionality to multi-project and time separated seems like the next logical small step. Small, because all the hard work is already done by the IDE. I would guess 99% is already there. 

The remaining work is in recording actions in a format which can identify the referenced element and the refactoring action applied. On playback search for the referenced element and apply the action. .... lots of unknowns, testing, debugging.... Done!

Any suggestions and ideas will be greatly appreciated. 

BTW, I am comfortable with the JetBrains Open API--meaning I use existing intellij-community source to find what I need and hack the rest. That is to say if you do not discourage me now, you will not regret it later because of a lot of stupid questions. ;)

 

 

0

The problem is similar we have with our API, did you check google Refaster (http://errorprone.info/docs/refaster)? In most simple cases, it does the job and more complicated cases would really need understanding from users, I am afraid.

Anna

0

Thank you for the Refaster link. It was exactly the type of information I needed. 

0

I thought about it a lot more, especially during my effort to factor out a common plugin utility library from 4 plugin projects with tweaked copies these utility classes. I could not create a single project but did include the evolving common library as a module in each of the projects so I could adjust the library or project as needed using IDE refactoring.

It was an iterative process and an exercise in frustration because there is no way to tell the IDE that all references to a class or static member should be changed to another one, without actually applying the changes to the target of the reference. What I did is described at the bottom to avoid clutter. See:Extracting a library and refactoring multiple projects to use it

What I realized from the experience:

  1. There is a need for a refactoring action requiring a simple importchange from old FQN to new FQN which could be text based making this “refactoring” independent of whether the old reference class still exists in the project or not.

    The same for static imports changing the static members’ movement to another class movement andL possible renames.

    After the imports and statics are fixed up, any unresolved references to moved static members can be easily addressed because the containing class is now resolved. Only necessary to change the static member reference to a new name.

    This would eliminate over 90% of the effort of replacing a project’s implementation with a library.

  2. Downstream migrations caused by API refactoring is a PITA when old references become unresolved due to FQN changes. At this point the IDE can only show red unresolved reference with no useful information.

  3. Migration assistance does not need to address the hard general case of API refactoring. The IDE already handles this beautifully, except when the changes cause unresolved class or static member references.

    These API changes happen to be the easiest ones to track in the source project and apply in the downstream project. Once all class references and static references are resolved, the IDE can give great assistance in refactoring for other API changes.

    There is less need for the hard general solution to the problem. The easy solution will give 90% of the bang for 1% of the effort.

My original gut instinct that this functionality is “Simple” compared to the 99% already done by IntelliJ seems to be correct.

I am definitely eager to implement a simple version of this plugin as a proof of concept and for my use before inflicting it on the world.


Extracting a library and refactoring multiple projects to use it

So if I wanted to have MyClassin the project to use MyClassin the library instead, withMyClassalready in the library. I had to:

  1. rename the project’s MyClassto MyClass2
  2. move MyClass2to the library, in the same package as MyClass
  3. rename file MyClass2to MyClass3
  4. rename class MyClass2to MyClassand ignore the warning that the class already exists
  5. delete file MyClass3

This has the effect of changing references to MyClassproject file, to MyClasslibrary file.

Static member function reference movement from project’s implementation class to another library’s implementation class would require inserting the following between steps 2. and 3. above:

  1. rename static member myMemberin MyClass2to myMember2so as not to conflict with existing implementation.
  2. move static member myMember2to destination library class MyOtherClass, not MyClassotherwise this step would not be needed.
  3. rename static member myMember2to myMemberand ignore duplication warnings
  4. delete the newly renamed static member myMember
0

Sorry, I am not sure that I followed your thought correctly. My another suggestion is to try Refactor | Migrate..., you would need both vesrions of your library attached to the project but then you may remove the old one so I don't see much problem here. It must resolve the first problem:

>1. There is a need for a refactoring action requiring a simple importchange from old FQN to new FQN which could be text based making this “refactoring” independent of whether the old reference class still exists in the project or not.

and the 2 & 3 points I just don't understand, could you please elaborate?

Thanks

0

Anna,

Refactor Migrate is exactly what I needed. Thank you. It would make the class migrations a snap. I was looking for equivalent but could not find it. I was sure it had to exist but missed it in the menu. There is a ton of functionality that I have not discovered in the IDE.

This migration is exactly what addresses point 1 for classes, but not for static members which are moved to another class and/or renamed. Reorganizing utility classes will invalidate references to static members which are moved to another class for downstream projects.

The solution would be a refactoring similar to Migrate Class, except it specifies the old static member(s) within the class and their corresponding new class static member references. It is would be exactly like Refactoring Move to another class but without the actual moving of old static member(s). It would only change all references to point to the new member(s).

Point 2 highlights that when you upgrade to a new version of a library which has moved classes to new packages leaving old references unresolved, the real issue for downstream projects is lack of useful information from the IDE on what to do. Having a "Refactoring Migration" template (old library to new library migration) as part of the upgraded library would handle most of the head scratching library upgrade issues.

On the developer side of the API, having a plugin which would automate gathering of this information as the library is refactored and use it to generate the migration template would not only make the job easier but also more reliable. In the spur of the moment it is easy to make a small code change and forget to address it in the upgrade documentation. The automatic gathering of this information would make forgetting impossible.

Point 3 is drawing attention to the fact that most API migration issues for downstream projects are caused by dangling references to classes/statics which is a soft problem to solve automatically. The hard general automated migration problem is not very critical because once the soft migration issues are automatically resolved, the rest are so much easier to address by the developer. Once the references are resolved to new classes/members, the IDE can provide tons of useful information and it limits the field of options to a class or a group of methods in the class.

For example, if a function signature is changed, then the IDE will highlight the problem and as a developer I can open the class to see what else is available or how I can adapt my code, When a class is renamed in a library, after the upgrade the IDE shows unresolved reference. There is no quick way to figure out whether it was moved (package change), renamed or deleted. I am stuck and need to manually search for the solution.

The same for static members which are renamed or moved to another class as part of re-organizing library's utility classes. There is no place to start looking on what to do. Having a move/rename migration template for the upgraded library would solve over 90% of API migration issues.

Thank you for your help, Anna. I now have a much clearer picture on how to approach this and I can use existing Refactor Migrate functionality to provide a template for downstream projects.

0

请先登录再写评论。