Composite Refactorings

Mohsen Vakilian edited this page Jul 14, 2014 · 11 revisions

The following is a list of refactorings that we need to better support refactoring in a composition-based style. Some refactorings can be implemented as a single Quick Assist action. Other composite refactorings can be carried out by a composition of some of the basic refactorings. The following defines each composite refactoring as a composition of basic refactorings using a notation like that of regular expressions.

Supported Refactorings

This section lists the refactorings that we already support in a composition-based style.

Convert Anonymous Class to Nested Class

A screenshot of the Convert Anonymous Class to Nested Class refactoring wizard of Eclipse

Eclipse already provides the Quick Assist action "Convert anonymous to nested class" for this refactoring.

Extract Superclass

A screenshot of the Extract Superclass refactoring wizard of Eclipse

Extract Superclass on type 'C' in compilation unit 'D.java' = (Create a new supertype for type 'C' in compilation unit 'D.java' | Create a new supertype for type 'C' in a new compilation unit) . (Move member 'm' to supertype 'SuperC')* . (Move type 'SuperC' to a new file)?

If the programmer opens the Quick Assist menu while type 'C' in compilation unit 'D.java' is selected, the Quick Assist menu will include the following two actions:

  • Create a new supertype for type 'C' in compilation unit 'D.java'
  • Create a new supertype for type 'C' in a new compilation unit

The programmer can choose to create the new supertype in the same compilation unit or in a new one. Either of these actions will create a new supertype of 'C', say 'SuperC'. Then, the programmer can optionally perform the action "Move member 'm' to supertype 'SuperC'" on a member of 'C', say 'm' to move it to 'SuperC', where 'SuperC' is the immediate supertype of 'C'. Finally, the action "Move type 'SuperC' to a new file" allows the programmer to move the selected type to a new file. This action is useful if the new supertype was created in the same compilation unit.

Introduce Parameter

A screenshot of the Introduce Parameter refactoring wizard of Eclipse

Introduce Parameter for expression 'e' in method 'm' = Add a parameter for 'e' to method 'm'

Move Type to New File

We have added a Quick Assist action called "Move type 'C' to a new file" for this refactoring. This action moves the selected type, say 'C', to a new compilation unit.

Use Supertype Where Possible

A screenshot of the Use Supertype Where Possible refactoring wizard of Eclipse

Use Supertype Where Possible on type 'S' = Replace type 'S' by super type 'T' in variable declarations | Replace type 'S' by super type 'T' in instanceof expressions

The refactoring tool will propose the above actions for the few closest super types of 'S' like 'T'. The closest super type of 'S' is its immediate super type.

To Be Supported Refactorings

This section lists the refactorings that we plan to support in a composition-based style.

Extract Interface

A screenshot of the Extract Interface refactoring wizard of Eclipse

Extract Interface from type 'C' = Create new interface 'NewI' . Make 'C' implement 'NewI' . (Move constant 'c' to interface 'NewI' | Declare method 'm()' in interface 'NewI')* . Use Supertype Where Possible

Currently, Eclipse can infer the first two actions "Create new interface 'NewI'" and "Make 'C' implement 'NewI'" from manual edits. If a programmer types "... implements NewI", Quick Fix will propose that the programmer creates a new interface. Unfortunately, the action opens a dialog to configure the action. Our tool provides a new Quick Assist action for creating and implementing a new interface when the programmer selects a class. Also, if the programmer selects a method 'm' in type 'C', Quick Assist will propose the action "Create 'm()' in supertype 'NewI'", which will declare the method in the interface. Finally, the Move refactoring allows the programmer to move static field to the interface, but, it doesn't provide a Quick Assist action.

Pull Up

A screenshot of the Pull Up refactoring wizard of Eclipse

Pull Up = (Move 'x' to supertype 'T')+ . (Declare 'm' in supertype 'T' as abstract)? . Use Supertype Where Possible

Push Down

A screenshot of the Push Down refactoring wizard of Eclipse

Push Down = (Move 'x' to all immediate subtypes | Copy 'x' to subtype 'T' )+ . (Declare 'm' in supertype 'S' as abstract)? . (Delete 'x' from supertype 'S')?

Change Method Signature

A screenshot of the Change Method Signature refactoring wizard of Eclipse

Change Method Signature on method 'm(T1 p1, ..., Tn pn)' = (Remove method parameter 'pi' | Move method parameter 'pi' to right | Move method parameter 'pi' to left | Add new method parameter)+

Eclipse suggests the "Add/Remove new method parameter" Quick Fix actions when the programmers adds/removes a new argument in a method invocation. These two actions change the signature of the method based on the change made to the selected method invocation. However, these actions won't update the other call sites.

The "Add/Remove new method parameter" actions use the types of method arguments to infer the changes to the method parameters. Thus, these two actions may not work accurately when several method parameters of the same type are besides each other. Because these actions just analyze the types of method arguments, the "Add new method parameter" action may not infer the right place to insert the new method parameter, and the "Remove new method parameter" action may not infer the right method parameter to remove.

What I like about the existing Eclipse actions "Add/Remove new method parameter" is that they are based on examples. That is, the programmer doesn't have to make a selection to trigger these actions. Rather, the programmer changes a call site manually. Then, Quick Fix proposes several ways to make the method invocation and method signature consistent. Kent Beck refers to this technique as "refactoring by example". We could use the refactoring-by-example technique to suggest a single Change Method signature action based on the changes that the programmer has made to a method signature. For instance, if the programmer manually changes the signature of a method from void m(int a, int b, double c) to void m(int a, double c), Quick Fix could propose the action "Update call sites to conform to the new method signature". Such an action will complete the Change Method Signature refactoring behind the scene.

Alternatively, we could provide the following individual Quick Assist actions when the programmer selects a method parameter or argument:

  • Remove method parameter 'pi'
  • Move method parameter 'pi' to right
  • Move method parameter 'pi' to left
  • Add new method parameter

If the cursor is inside the pair of parentheses of the method, the "Add new method parameter" will infer the place to insert the new method parameter. Otherwise, this action will add the new parameter as the last parameter.

Encapsulate Field

A screenshot of the Encapsulate Field refactoring wizard of Eclipse

Encapsulate Field 'f' of class 'C' = (Generate getter for 'f' | Generate setter for 'f') . (Replace read accesses to 'f' inside the 'C' by getter | Replace read accesses to 'f' outside 'C' by getter | Replace write accesses to 'f' inside 'C' by setter | Replace write accesses to 'f' outside 'C' by setter ) . (Make 'f' private)?

Extract Class

A screenshot of the Extract Class refactoring wizard of Eclipse

Extract Class from class 'C' in 'D.java'

= Declare field 'newF' of type 'NewC' . (Create new nested class 'NewC' in 'C' | Create new class 'NewC' in 'D.java' | Create new class 'NewC' in a new compilation unit) . (Move field 'f' to 'newF' | Move method 'm' to 'newF') . (Move type 'NewC' to a new file)?

= (Create new nested class in 'C' | Create new class in 'D.java' | Create new class in a new compilation unit) . (Declare field 'newF' of type 'NewC') . (Move field 'f' to 'newF' | Move method 'm' to 'newF') . (Move type 'NewC' to a new file)?

If a programmer declares a variable of a new type, Quick Fix will propose to create a new class. This is another transformation-by-example. Unfortunately, the action opens the big "New Class" dialog rather than just creating a new class with some default configurations. I propose the following three actions that can get triggered either by an example or selection:

  • Create new nested class
  • Create new class in 'D.java'
  • Create new class in a new compilation unit

Any of the above actions will create a new class, say 'NewC'. The programmer has to define a new field, say 'newF', of type 'NewC' in class 'C'. Then, the programmer can move members of 'C' to the new field 'newF' using the two move actions. Finally, the programmer may move the new type 'NewC' to a new file.

If the refactoring tool tracks the recent actions of the programmer, it can pre-configure the actions. For instance, the tool could pre-configure the move actions by the recently added field.

Note that the existing move field refactorings of Eclipse are textual. We need to develop new move actions that update the accesses to the field as the field moves to a new class.

Generalize Declared Type

A screenshot of the Generalize Declared Type refactoring wizard of Eclipse

Generalize Declared Type = Change the type of 'v' to the supertype 't'

If a programmer selects a variable, say 'v' of type 't', our tool will compute the list of supertypes of 't' that 'v' can generalize to. Then, the Quick Assist menu will include an action to generalize the declared type of 'v' to each of these pre-computed supertypes. If there are too many potential supertypes, the tool can open a hierarchy menu for the programmer to choose the target supertype from.

Inline Constant

A screenshot of the Inline Constant refactoring wizard of Eclipse

Inline Constant on a declaration of a constant 'c' = Inline constant 'c'

Inline Constant on a use of a constant 'c' = Inline constant 'c' | Inline only the selected reference to constant 'c'

The Inline Constant refactoring can be implemented as a single Quick Assist action. Currently, Eclipse doesn't provide any Quick Assist actions for inlining constants. If a programmer selects the declaration of a constant, our tool will add a new Quick Assist action to inline the selected constant. And, if the programmer selects a use of a constant, it will add two Quick Assist actions, one to inline the constant and another to inline just the selected reference to the constant.

Inline Method

A screenshot of the Inline Method refactoring wizard of Eclipse

The Inline Method refactoring can be implemented as a single Quick Assist action.

Inline Method on a declaration of a method 'm' = Inline method 'm'

Inline Method on a call site of a method 'm' = Inline method 'm' | Inline only the selected invocation of method 'm'

Similar to how our tool will support Inline Constant, our tool proposes different actions for inlining all invocations of the selected method or inlining just the selected method invocation.

Introduce Parameter Object

A screenshot of the Introduce Parameter Object refactoring wizard of Eclipse

Introduce Parameter Object from method 'm(T1 p1, ..., Tn pn)' of class 'C' in 'D.java'

= Add new method parameter 'newP' . (Create new nested class 'NewC' in 'C' | Create new class 'NewC' in 'D.java' | Create new class 'NewC' in a new compilation unit) . (Move method parameter 'pi' to 'newP')+

= (Create new nested class 'NewC' in 'C' | Create new class 'NewC' in 'D.java' | Create new class 'NewC' in a new compilation unit) . Add new method parameter 'newP' . (Move method parameter 'pi' to 'newP')+

The first variant fits the transformation-by-example model better. That is, the programmer first declares a new method parameter and the tool proposes an action to create a class based on the name the programmer has already entered.

In the above definition, the programmer will use the "Add new method parameter" action to create a new method parameter 'newP' of type 'NewC'. The tool could pre-configure this action for 'NewC' based on the list of recently added classes. Alternatively, the tool could choose default values for the name and type of the new type and let the programmer modify them in the linked editing mode.

Note that each of the following actions will update all the call sites of the method:

  • Add new method parameter
  • Move method parameter 'pi' to 'newP'

Move Accumulation to Visitor

Move Accumulation to Visitor is defined in the Refactoring To Patterns book.

Move Accumulation to Visitor = (Covert local variable to field | Extract method)* . Add an accept method for 'visit(C)' to type 'C' . Extract visitor interface . Replace type 'D' by supertype 'NewI' in variable declarations where possible

The "Add an accept method for 'visit(C)' to type 'C'" operates on methods whose names match "visit*". This action will create an accept method in type 'C' that delegates to the selected visit method.

The "Extract visitor interface" creates a new interface and moves the methods whose names match "visit*" to the new interface. We need to investigate if it's useful to perform the creation of such an interface in one step.

You can’t perform that action at this time.
You signed in with another tab or window. Reload to refresh your session. You signed out in another tab or window. Reload to refresh your session.
Press h to open a hovercard with more details.