Star Diagram with Automated Refactorings for Eclipse Instruction Manual

Introduction

Reading a Star Diagram

Identifying a Refactoring Opportunity

Viewing Corresponding Code

The Refactoring Menu

Extract Method Extension

Authors

Bibliography

Introduction

The maintenance of a large software system is a complex task. One way to lower maintenance costs is to refactor the system into modular abstractions that both better represent the system designer’s concepts and better encapsulate future changes. Refactoring is a restructuring technique to transform the external structure of code without changing its behavior [1,2,3,6 ]. Together, many small refactorings can be applied to create a significant design change in the system. One of the powerful features of the Eclipse JDT plug-in is its refactoring feature. Yet, because many refactorings are global and architectural in nature, the textual presentation of refactorings presents challenges for designers. We have designed and implemented the visual Star Diagram automated refactoring plug-in (SDAR) to ease such refactorings.

Step 1. To follow this tutorial, download and install the AspectBrowser Eclipse plug-in

Step 2. There is currently a bug in Eclipse 3.1 Extract Method refactoring. We have supplied a patch here. To install the patch, right click in the Package Explorer view and select Import External Plug-ins and Fragments. Select the buttons as you see in the figure below and then click Next.

1

Import External Plug-ins and Fragments

In the next dialog, select the "org.eclipse.jdt.ui" fragment to import. In the Package Explorer, navigate to the package "org.eclipse.jdt.internal.corext.refactoring.code". Replace the current SnippetFinder.java file with this SnippetFinder.java file.

Step 3. Verify that you are in the AspectBrowser perspective. Below is a screenshot of the AspectBrowser plug-in.

ss

Figure 1. Screenshot of Star Diagrams in Aspect Browser Perspective

Step 4. Import this manual's example project (found here), named ManualExample, into Eclipse 3.1. Compile the project.

Tutorial Source Code:

public class ErrorUtility {

final int READ_ERROR = -1;
final int MAX_SIZE = 100;
final int EOE = 1;
int error;

       public void checkBounds(int expid){
              //....
              if (expid == MAX_SIZE){
                     error |= 4;
              }
              //....
       }
}

public class Parser {     
       int expid;
       String exp;         
       ErrorUtility eUtil = new ErrorUtility();

       public void getToken(int x){     
              //....       
              if (expid == exp.length()){
                     eUtil.error |= eUtil.EOE;               
              }            
              if (expid == eUtil.READ_ERROR){
                     eUtil.error |= 2;                
              }
              //...
       }

       public void test(){
              getToken(5);
       }
}

 

Reading a Star Diagram

A star diagram is a static tree containing all computations that refer directly or indirectly to the data structure under examination, and omits computations not related to the data structure. Star diagrams illustrate both syntactic relationships and redundancies in the program.


Step 5. Create a star diagram on the error data structure.  To do this, go to the ‘Aspect Mining Searches’ view, open the ‘Diagrammable Elements’ tree, then open the ManualExample project tree.  Click on the element labeled ‘error’.

You should see a star diagram that looks as follows:

1 

Figure 2. Star Diagram on error data structure

A node denotes a branch of the AST. The root, at the far left (in this case, labeled error {4} ), represents the data structure being examined. The children of the root represent Java constructs in the program that directly reference the variable. The children of the next level represent constructs that reference the previous level’s computations, and so on. A node having a stacked appearance indicates that two or more pieces of code (perhaps) correspond to the same computation, as determined by our unification algorithm. Because of the stacking of similar computations, each unique computation is represented just once. The number label on stacked nodes denotes the number of nodes in the stack. The second last level of children, shown as parallelograms, represent the functions that contain these computations. The last level of children denotes the classes that contain the methods [4,5].

Identifying a Refactoring Opportunity

Refactoring opportunity hints, denoted by a star symbol, make available refactoring opportunities in the tree apparent. SDAR currently creates refactoring hints for four different refactorings: Encapsulate Field, Remove Unused Parameter, Extract Method, and Inline Method.  The Eclipse help pages describe these refactorings as well as other refactorings that it supports. The hints are generated when a star diagram is created, based on the structure of the diagram and conditions we have designated. In Figure 2, the star symbol can be seen on the nodes labeled MethodDecl:getToken and FieldDecl; thus, both of those nodes identify a refactoring possibility.

Additionally, the stacked nodes of a star diagram indicate possibly duplicate code. An Extract Method refactoring can merge duplicate code into one method.  SDAR allows the user to perform Extract Method on any extractable node or set of stacked nodes in the diagram (regardless of whether or not the nodes are actually duplicates).  A node near the left of the tree is an expression or statement that might implement a “low level” operation, encapsulating little more than the data structure representation itself.  A node farther to the right might implement a “higher level” operation.  Thus, the further right the node, the larger the code section and the higher the probability that the stacked nodes do not represent duplicate code.

Viewing Corresponding Code

To view the code snippet that the node is referring to, double-click the node of interest and view the code snippet in the “Source Lines” view.  You can then double-click a row the ‘Resource’ column in the “Source Lines” view to look at the code in the text view.

Step 6. Double-click the node labeled @={3}.  You should see the following in the ‘Source Lines’ view:

2

Figure 3. Source code for node labeled @={3} in Figure 2 Star Diagram

Step 7. Now Double-click the node labeled if()@{3}.  You should see the following in the ‘Source Lines’ view:

3

Figure 4. Source code for node labeled if@={3} in Figure 2 Star Diagram

From the source code, you can see that the node further to the right (labeled if()@{3}) represents a “higher-level” operation than it’s parent node (labeled @={3}).

The Refactoring Menu

Displaying the Refactoring Menu

To display the SDAR refactoring suggestions, mouse-over a node with a star symbol on it or over a node that you would like to extract into a method. This action pops-up the refactoring menu (for the moused-over node) to the right of the star diagram.  The menu shows possible refactorings that could be performed via SDAR.

Step 8.  Mouse-over the node with a star labeled MethodDecl:getToken.  The diagram should now look like:

4

Figure 5. Star Diagram on error data structure showing Remove Unused Parameter Refactoring

The menu lists each refactoring opportunity as a row in the menu. (In this case there is one row.)  The first column provides details about the refactoring.  The second column is the name of the refactoring.  In the case of the Remove Unused Parameter refactoring, the first column lists the method that contains the unused parameter in its signature followed by the name of the parameter that is unused.

Step 9. Now mouse-over the node with a star labeled @={3}.  Although there is not a star on the node, it a possible Extract Method refactoring opportunity. Thus, the diagram shows the Extract Method refactoring opportunity in the menu:

5

Figure 6. Star Diagram on error data structure showing Extract Method Refactoring

In the case of Extract Method, the first column of the menu displays on which node or stack of nodes Extract Method will be performed.
 

Performing a Refactoring

To activate a refactoring, click on the desired refactoring in the pop-up menu.

Step 10.  Mouse-over the node with a star labeled MethodDecl:getToken.  Click on the Remove Unused Parameter menu option.

6

Figure 8. Star Diagram on error data structure showing Remove Unused Parameter Refactoring

If you look at the text view, you will see that the method signature changed from:

public void getToken(int x)

to:

public void getToken()

Additionally, the method call:

getToken(5);

Has been changed to:

getToken();

The diagram will now look like:

7

Figure 8. Star Diagram on error data structure without Remove Unused Parameter Refactoring option.

As you can see, the star on MethodDecl:getToken has been removed, indicating that there is no longer an unused parameter in the method getToken.

References Arrows

SDAR suggests applying the Encapsulate Field refactoring when the root node (i.e., the data structure being examined) is used in classes other than the declaring class or its subclasses. In this case, no edge exists in the diagram that illustrates which field reference is perhaps violating the encapsulation principle. To show this relationship, SDAR generates dynamic edges when the user mouses-over an Encapsulate Field refactoring menu option. An edge is added from every reference to the Field Declaration node. SDAR also adds an edge from the member class of every reference node to the class that declares the field so that the user can see how pervasive the violation is throughout the program.

Step 11. Mouse-over the node with a star labeled FieldDecl.  The diagram should look like:

8

Figure 9. Star Diagram on error data structure showing Encapsulate Field refactoring

From this diagram, the first column of the menu makes it apparent that some node in the diagram directly references the ErrorUtility.error data structure.  It is not clear which node is creating this violation.  Next, mouse over the Encapsulate Field refactoring menu option.  The diagram should now look as follows:

9

Figure 10. Star Diagram on error data structure showing Encapsulate Field refactoring and it's reference arrows

This diagram shows that one or more of the nodes in the stack labeled @={3} is creating the violation and the violating node(s) are located in the Parser class.  

We look at the source code of the node labeled @={3} and determine that the following code in the Parser class is creating the violation:

              if (expid == exp.length()){
                     eUtil.error |= eUtil.EOE;               
              }            
              if (expid == eUtil.READ_ERROR){
                     eUtil.error |= 2;                
              }

To perform the encapsulation, click on the Encapsulate Field refactoring menu option.  This will activate the refactoring option and display the following Eclipse dialog:

10

Figure 11. Eclipse Encapsulate Field Refactoring Dialog

View Eclipse Help to learn about the options in this dialog box.

We see the following changes in the source code text view:

The refactoring added the following two methods in the ErrorUtility class:

void setError(int error) {
this.error = error;
}

int getError() {
return error;
}

The code in the Parser class is changed to:

if (expid == exp.length()){
eUtil.setError(eUtil.getError() | eUtil.EOE);
}
if (expid == eUtil.READ_ERROR){
eUtil.setError(eUtil.getError() | 2);
}

Step 12. Select OK in the dialog box.  The diagram will now look as follows:

11

Figure 12. Star Diagram without Encapsulate Field Refactoring option

In the above diagram, the star is no longer on the node labeled FieldDecl

In this case, the data structure, error, is type int.  In other cases, the data structure may be more complex (ArrayList, Vector, etc.) and the Encapsulate Field refactoring may not be useful because it is too simplistic of an encapsulation.  In those cases, the star and the reference arrows are still useful.  The star will notify you that there is a direct reference to the root of the diagram and the arrows will tell you which nodes are causing the violation.  You can then perform the appropriate refactorings (often a data abstraction) to remove the violation.  You will know when you are finished because the star will no longer exist on the node labeled FieldDecl.

Extract Method Extension

 

SDAR enables the Extract Method refactoring option for every node in the diagram that passes the Eclipse Extract Method conditions. We found that the Eclipse Extract Method refactoring implementation often does not recognize code as duplicate when the code corresponds to the same computation. To recognize a wider range of equivalent code as duplicates, we developed an Extract Method extension that converts the code to be extracted into a normalized form that can then be identified as equivalent by the Eclipse Extract Method refactoring unifying algorithm.

Before we performed Encapsulate Field, the diagram looked like:

13

Figure 13. Star Diagram before performing Encapsulate Field Refactoring option

Notice the node labeled if()@{3}.  Figure 4 shows the source code of this node. We can see from the code that the nodes are potentially duplicates.  It would be interesting to follow up on that node to determine if the code should be extracted into one method. 

The current diagram looks like:

12

Figure 14. Star Diagram after performing Encapsulate Field Refactoring option

The node labeled if()@{3} is not in the current diagram.  This is because it has been encapsulated by the methods getError and setError.  Note that this is a common occurrence.  When you create an abstraction of a statement or set of statements, the other (perhaps important) information shown to the right of the extracted node no longer appears in the original diagram.  Rather, those nodes now appear in the star diagram generated where the new method is the root.

Step 13. Thus, we create a star diagram on the method getError.  The diagram should look as follows:

14

Figure 15. Star Diagram on getError

 

The corresponding code for the three nodes in the if()@{3} stack are:

(a) located in the ErrorUtility class

              if (expid == MAX_SIZE){
                     setError(getError() | 4);
              }

(b) located in the Parser class

              if (expid == exp.length()){
                     eUtil.setError(eUtil.getError() | eUtil.EOE);                
              }

(c) located in the Parser class 

              if (expid == eUtil.READ_ERROR){
                     eUtil.setError(eUtil.getError() | 2);                 
              }

In its current form, the Eclipse JDT Extract Method refactoring will not find the code to be duplicates.  It would fail to match MAX_SIZE, exp.length(), and eUtil.READ_ERROR as duplicate expressions, although each of the expressions is an int that could be passed into the method as a parameter.  Secondly, the Eclipse JDT Extract Method refactoring will not match the expressions (getError() | 4), (eUtil.getError() | eUtil.EOE), and (eUtil.getError() | 2), although each of the expressions is an int that could be passed into the method as a parameter.

Step 14. Next, mouse-over the node labeled if()@{3}.  The refactoring menu will appear. 

Step 15. Click on the Extract Method Refactoring option.

SDAR’s Extract Method Extension will first extract all rvalue expressions from the each node in the stack.  The result will appear as:
(a)
              int param8 = expid;
              int param9 = MAX_SIZE;
              if (param8 == param9){
                     int param10 = getError() | 4;
                     setError(param10);
              }

(b)
              int param0 = expid;
              int param1 = exp.length();
              if (param0 == param1){
                     int param3 = eUtil.getError() | eUtil.EOE;
                     eUtil.setError(param3);                 
              } 
        
(c)
              int param4 = expid;
              int param5 = eUtil.READ_ERROR;
              if (param4 == param5){
                     int param7 = eUtil.getError() | 2;
                     eUtil.setError(param7);                 
              }

Step 16. Next a Code Motion dialog will appear.  In this case, it will look like the dialog in Figure 16. Select all expressions that are not modified by the corresponding source code. The newly created local variables are moved outside of the code fragment for all checked expressions.  The newly created local variables are inlined for all unchecked expressions.

In this case, none of the expressions are modified, so all of expressions are selected.  It is important to check each expression carefully because selecting an expression that is modified by the code fragment could change the semantics of the program. 

15

Figure 16. Extract Method Extension Code Motion Dialog

 

After selecting all expressions, the code looks as follows:

(a)

             

              int param8 = expid;
              int param9 = MAX_SIZE;
              int param10 = getError() | 4;
              if (param8 == param9){
                     setError(param10);                 
              }   

(b)
              int param0 = expid;
              int param1 = exp.length();
              int param3 = eUtil.getError() | eUtil.EOE;
              if (param0 == param1){
                     eUtil.setError(param3);                 
              } 
        
(c)
              int param4 = expid;
              int param5 = eUtil.READ_ERROR;
              int param7 = eUtil.getError() | 2;
              if (param4 == param5){
                     eUtil.setError(param7);                 
              }

                           

 

Step 17. Figure 17 shows the next dialog that appears on your screen; the Extract Method Summary dialog.  This dialog addresses the two issues:

(a) nodes in different classes (duplicates or not duplicates)

(b) non-duplicate nodes in the same class.

16

Figure 16. Extract Method Extension Extract Method Summary Dialog (case a)

Case (a):

The current example shows case a.  Of the three nodes in the stack, two of the nodes are located in the Parser class and one node is located in the ErrorUtility class.  Eclipse Extract Method does NOT find inter-class duplicates, even if the code is the same.  Thus, if the inter-class nodes are duplicates, choose to perform an Extract Method Refactoring on each code fragment.

Step 21 will show you the additional steps needed to finish the extraction of the stack into one method. 

Case (b):

Case b is not encountered in our current example.  Assume for the moment that the source code corresponding to node labeled if()@{3} is:

(a) located in the ErrorUtility class

              if (expid == MAX_SIZE){
                     setError(getError() | 4);
              }

(b) located in the Parser class

              if (expid == exp.length()){
                     temp = 5;
                     eUtil.setError(eUtil.getError() | eUtil.EOE);              
              }

(c) located in the Parser class 

              if (expid == eUtil.READ_ERROR){
                     eUtil.setError(eUtil.getError() | 2);                 
              }

Now, the two nodes in the Parser class do not equate to duplicate code. The dialog would look like:

17

Figure 17.. Extract Method Extension Extract Method Summary Dialog (case b)

In Figure 17, the method signature for the second signature in the Parser class is different from the first signature.  The code is not equated as duplicate and the user must decide which, if any, code to extract into the corresponding method signature.

Step 18. You should see a dialog that looks like Figure 16.  Select All and click OK.

Figure 18 shows the Eclipse Extract Method dialog for the Parser class. It is used to name the method and its parameters for the code in the Parser class.  After naming the method, you can preview the changes by clicking the Preview button. 

Step 19. Name the method checkError.  Name the parameters expid, errComparison, errConstant.  Click OK.

18

Figure 18. Eclipse Extract Method Dialog for the Parser class

 

Figure 19 shows the Eclipse Extract Method dialog for the ErrorUtility class. It is used to name the method and its parameters for the code in the ErrorUtility class.  After naming the method, you can preview the changes by clicking the Preview button.  Notice that in this dialog, there are no duplicate code fragments, whereas there was one duplicate code fragment in Figure 18.

Step 20. We know that the code in the two classes are duplicate. So, name the method the same name as you did in Step 19: checkError.  Name the parameters the same names as you did in Step 19: expid, errComparison, errConstant.  Click OK. We choose the same method name as the name in the ErrorUtility class so that we can later delete the method in the class Parser and only need to replace the implicit this keyword with an object reference to the ErrorUtility class.

19

Figure 19. Eclipse Extract Method Dialog for the ErrorUtility class

The resulting code will look like:

Class Parser{
public void getToken(){   
//....       
checkError(expid, exp.length(), eUtil.getError() | eUtil.EOE);      
       checkError(expid, eUtil.READ_ERROR, eUtil.getError() | 2);
//…
}
private void checkError(int expid, int errComparison, int errConstant) {
       if (expid == errComparison){
              eUtil.setError(errConstant);                   
       }
}
//…
}

Class ErrorUtility{
       //…
public void checkToken(int expid){
       //…
       checkError(expid, MAX_SIZE, getError() | 4);
       //…
}
void checkError(int expid, int errComparison, int errConstant) {
       if (expid == errComparison){
              setError(errConstant);
       }
}
//…
}

Step 21. Now, we have performed the automated portion of Extract Method Refactoring, but we still have duplicate method functionality in two classes; the method checkError appears in both classes.  We actually want the method to appear in only one class, the ErrorUtility class.  To remedy this, delete the method in Parser.java.  Then, go to the first error marker that appears in the Parser class.   It should point to the following line:

checkError(expid, exp.length(), eUtil.getError() | eUtil.EOE);

Replace the line with:

EUtil.checkError(expid, exp.length(), eUtil.getError() | eUtil.EOE);

Now go to the second error marker.  It should point to the following line:

checkError(expid, eUtil.READ_ERROR, eUtil.getError() | 2);

Replace the line with:

EUtil.checkError(expid, eUtil.READ_ERROR, eUtil.getError() | 2);

Now the code should compile correctly and we have overridden Eclipse’s interclass matching limitation.

Authors

Alexis O'Connor & William Griswold

Bibliography

1. M. Fowler. Refactoring: Improving the Design of Existing Code. Addison-Wesley, Reading, MA, 1999.

2. W.G. Griswold. Program Restructuring as an Aid to Software Maintenance. PhD dissertation, University of Washington, Dept. of Computer Science & Engineering, August 1991. Technical Report No. 91-08-04.

3. W. G. Griswold and D. Notkin. Automated assistance for program restructuring. ACM Transactions on Software Engineering and Methodology, 2(3):228-269, July 1993.

4. W. G. Griswold, M. I. Chen, R.W. Bowdidge, J. D. Morgenthaler. Tool Support for Planning the Restructuring of Data Abstractions in Large Systems. In Proceedings of the ACM SIGSOFT Conference on the Foundations of Software Engineering (FSE-4), October 1996.

5. W. F. Korman, Elbereth: Tool Support for Refactoring Java Programs, M.S. Thesis, Technical Report CS98-590, Department of Computer Science and Engineering, University of California, San Diego, June 1998.

6. W. F. Opdyke. Refactoring: A Program Restructuring Aid in Designing Object-Oriented Applications Frameworks. PhD dissertation, University of Illinois at Urbana-Champaign, Dept. of Computer Science, 1992. Technical Report No. 1759.