Featureous 3.0: Automated Restructuring of Source Code
As some of you may already know from my earlier article, Featureous used to be a NetBeans IDE-based tool for feature-centric analysis of Java programs. The sole aim of the tool used to be to make it easier for programmers to deal with features in legacy code. But this not the complete picture anymore.
As of today, the new incarnation of the tool, Featureous 3.0, introduces automated restructuring of source code. Embodied as the Remodularization Workbench plugin, the new restructuring support makes it possible to use feature-code traceability links to automatically adjust the package structure of a program to localize and separate feature implementations. Moreover, we made it possible to automatically wrap the created packages into modules of the NetBeans Module System. While on the road towards a completely automatic “one-click modularization" there are still some challenges to be tackled, Featureous already made a number of important steps towards fulfilling this vision.
Below you can find a short tutorial on how to trace features of your legacy application and how to use the remodularization workbench. In this tutorial we use the Anagram game example application from the NetBeans IDE. An application consisting of 4 classes is not exactly what one could consider a real-world application scenario, but it will be just fine for presenting the workflow of using the tool and it will make it possible to easily replicate
the procedures being discussed.
As usual, more information, tutorials and the tool itself can be found at project's website http://featureous.org. If you feel like delving into the sources or hacking your own plugin, then you are very welcome to take a look at:
1. Let's begin by creating a new Anagram Game project and setting it as the main project.
2. Select the project in projects explorer window. You should see that two buttons provided by Featureous in the “Run toolbar” of NetBeans should become enabled.
Click the “Trace project” button (green triangle with a ruler).
3. You will get notified that an aop.xml file got created in src/META-INF directory. You need to edit the aop.xml to configure the tracer before you can trace features of your project.
4. Open the aop.xml and replace all “<YOUR_ROOT_PACKAGE>” with the root package of Anagram game, i.e. “com.toy.anagrams”.
Save the file.
5. Clean & Build the project.
6. Click “Trace project” again and observe that the program executes and prints AspectJ logs into the output window. However, no traces are being created, since we have not annotated any features yet.
7. In order to start annotating features you have to configure the sources of Anagram Game project to be of format “JDK 5” (do this in project's properties). Anagram game example gets created with JDK 1.4 by default, which does not allow annotations.
8. For the sake of this example annotate the program as follows. In the Anagrams.java class, annotate the initComponents() method with @FeatureEntryPoint("gui"), and the nextTrialActionPerformed(java.awt.event.ActionEvent evt) method with @FeatureEntryPoint("generate_word"). The intent here is to in the end modularize the “generate_word” feature from the rest of the program, but in order to do this we needed an additional feature “gui” for the infrastructural classes and classes unrelated to “generate_word” to stick to – more about this later.
9. Save the file, clean & build. “Trace project” again.
10. At runtime, use each feature of anagram game as a normal user would do (i.e. press all the buttons, try to guess words).
11. When done interacting, close the program. You should see that two traces got written to the disk.
12. Open the “Feature explorer” window of Featureous by executing [Window->Other->Show Featureous] from the main menu of NetBeans. Automatically open the latest traces by using the “Update traces” button (the one with two circular arrows).
Now, you can see the two traces you have collected. These traces contain a mapping between the features you have annotated and the packages, classes and methods that get executed as a result of entering their corresponding feature-entry-point methods.
13. From now on you can use all the views Featureous has to offer, to analyze and measure feature implementations captured in the traces. For Anagram game example this is not particularly exciting though. What we are really interested in are the new restructuring capabilities. Bring up the Remodularization workbench by clicking its icon (depicting yellow gears) in the main toolbar of Featureous.
14. You will be presented with a structural view of the source code of your project.
This view shows packages of your program, the classes they contain, and the runtime call relations between the classes. Each package, class and method is colored using the coloring scheme of Featureous that tells about the number of features it is used by. Although the actual algorithm for assigning colors is more complicated, the scheme can be intuitively understood as assigning green to units used by a single feature, whereas dark blue means “used by many features”. As for using this information to create a new program structure, single-feature classes are good candidates for moving to a dedicated feature-modules, while classes used by many features are good candidates for including in reusable core/infrastructural modules.
Furthermore, the diagram allows for (un)collapsing packages to hide or show contained classes and (un)collapsing classes to show the methods that were used by features at runtime.
The table at the bottom allows you to choose a set of objectives that you want to use for optimizing the package structure of your program. However, in this example we don’t need any of that, as we want to preserve the package structure of the program as much as possible, and only extract the feature-specific parts of the "generate_word" features into a separate module.
In order to do this, press “Compute new modularization…”.
15. After a while, the tool will propose a new package structure of the program:
As can be seen, the tool proposed to move the StaticWordLibrary single-feature class into its own feature package. If need, it is possible to rename packages and adjust the structure by manually moving classes between packages (dragging while holding Shift). This is what I did in the example by moving the About class from ui to lib package. After each such adjustment the values of four metrics displayed underneath updates accordingly and reflects the influence of you manual adjustments on the quality of proposed package structure.
16. Now, ensure that “Encapsulate as NetBeans modules” is selected and apply the current structure by pressing “Apply current structure”. The tool will first modify the existing package structure, and then will wrap the created packages into NetBeans modules as follows. All the feature-specific packages (green) will be placed into their dedicated feature-modules. All the remaining packages (non-green) will be placed in a single “Core” module. One additional module for wrapping jar libraries will be created. Each module will be made dependent on the libraries-module and the core module. All this will be wrapped in a suite.
The exact chain of events happening behind the scenes is as follows:
a) Backup the original sources into anagram/src_backup directory
b) Restructure the sources (using the NetBeans’ moveClass refactoring)
c) Ensure new imports are correct for every class (this is done using the NetBeans’ fixAllImports action – here, you may want to turn down the volume on your speakers as this code makes a beep sound whenever there is nothing to be fixed)
d) Create a suite and modules and copy classes to their destination places
You should arrive at the following state.
17. Now, you can open the created suite and modules found in anagram/modules directory. As you build the modules, you will notice that the Core module does not compile due to errors in the WordLibrary abstract class.
You will see that this is due to the dependency on its subclass - StaticWordLibrary, which now resides in its own feature-module. While one might be tempted to solve this by making a dependency from Core to “generate_word” feature-module, the module system will fortunately not let this happen, as it does not tolerate circular dependencies. Ruthlessly enforcing the "core modules should not depend on feature-modules" principle improves reusability of the core modules, reduces the potential for change propagation from core to feature-modules, and enables customizability of a resulting application, by making it possible to include/exclude feature-modules without changing a single line of code in the core.
As were are not yet at a point, where we can automatically resolve circular dependencies or migrate Swing APIs to NetBeans RCP APIs, this calls for some manual refactoring. And this is exactly the time when the story about interfaces, service providers, lookups and TopComponents begins…