There are dozens of well-defined refactoring recipes for a huge range of small, specific tasks. Several of the books in the Further Reading at the end of this document contain catalogs of these refactorings, and in most cases they give step-by-step mechanics for carrying out the refactoring. Here we provide a taster by looking in detail at four specific and quite varied refactorings.
Inline Temporary Variable
Replace all uses of a local variable by inserting copies of its assigned value.
We begin with a simple method:
public double applyDiscount() {
double basePrice = _cart.totalPrice();
return basePrice * discount();
}
Step 1: Safety Check.
First, we check that the refactoring's pre-conditions are met. We can do that in this case by making the temporary variable final:
public double applyDiscount() {
final double basePrice = _cart.totalPrice();
return basePrice * discount();
}
Compiling now will tell us whether the refactoring is safe to perform. Leaning on the compiler like this is a trick that is useful in many refactorings (although of course it only works in compiled languages).
We can also test at this point, and even commit if we think we may be distracted away from the task in the next few seconds. We are at a safe "base" (and arguably we have already improved the code).
Step 2: Migrate each client.
Next, we identify all places in the code that need to change. In this case there's only one, in the return statement. We change it to use the value that's currently assigned to the temp:
public double applyDiscount() {
final double basePrice = _cart.totalPrice();
return _cart.totalPrice() * discount();
}
Compile and test (and commit if you're nervous). It's important to get into the habit of running the tests this frequently, because it significantly enhances one's feeling of safety. Most programming errors are created under conditions of stress, and simple practices such as this can save hours of debugging later.
It is thus also important to have tests that run quickly. In cases such as this we would probably only run the tests for the current class, so at least these must be fast.
(Note that the tests could fail here if _cart.totalPrice() has side effects, because we are now calling it twice. If that happens, they have saved us from making an unsafe refactoring, so we back out the change and walk away.)
Step 3: Remove the old code.
Finally, we can remove the now-obsolete declaration:
public double applyDiscount() {
return _cart.totalPrice() * discount();
}
Compile and test, and definitely commit this version to source control.
Most of the available refactoring tools provide an automated implementation of this refactoring.
Extract Method
Create a new method by extracting a code fragment from an existing method.
As an example, consider this code:
public void report(Writer out, List<Machine> machines) throws IOException {
for (Machine machine : machines) {
out.write("Machine " + machine.name());
if (machine.bin() != null)
out.write(" bin=" + machine.bin());
out.write("\n");
}
}
We want to extract the code that reports on each Machine.
Step 1: Create a new, empty method.
private void reportMachine() {
}
At this stage we are designing a mini-API. It is important to choose a name that reflects the new method's purpose and not its implementation.
In case of any doubt, we can check that the new method signature is valid by compiling and testing.
Step 2: Copy the code into the new method
This is a simple copy-paste:
private void reportMachine() {
out.write("Machine " + machine.name());
if (machine.bin() != null)
out.write(" bin=" + machine.bin());
out.write("\n");
}
In our example, this creates a reportMachine method that doesn't compile, due to the temporary variable machine and the out parameter.
Note that the original method remains unchanged at this point.
Step 3: Add parameters for temporary variables.
For each of the temporary variables used in the copied code we add a parameter to the new method:
private void reportMachine(Writer out, Machine machine) {
out.write("Machine " + machine.name());
if (machine.bin() != null)
out.write(" bin=" + machine.bin());
out.write("\n");
}
We also need to declare the checked exception thrown by the write methods:
private void reportMachine(Writer out, Machine machine) throws IOException {
out.write("Machine " + machine.name());
if (machine.bin() != null)
out.write(" bin=" + machine.bin());
out.write("\n");
}
At each stage we can check our progress by compiling. We know we're done when the new method compiles cleanly. And since it still hasn't called, the entire application should pass its tests at this point.
Step 4: Call the new method.
Finally we can replace the copied code in the original method by a call to the new method:
public void report(Writer out, List<Machine> machines) throws IOException {
for (Machine machine : machines) {
reportMachine(out, machine);
}
}
Compile, test, and we're done.
The Extract Method refactoring can be a little more complex if the code we want to extract modifies a temporary variable. For example, consider the following modified version of the previous code:
public String report(List<Machine> machines) {
String result = "";
for (Machine machine : machines) {
result += "Machine " + machine.name();
if (machine.bin() != null)
result += " bin=" + machine.bin();
result += "\n";
}
return result;
}
Here, the code we want to extract modifies the result temporary variable. In this case, during Step 3 we need to declare a new result in the new method and return its value at the end of the computation:
private String reportMachine(Machine machine) {
String result = "Machine " + machine.name();
if (machine.bin() != null)
result += " bin=" + machine.bin();
result += "\n";
return result;
}
Now in Step 4 we need to use the returned machine report:
public String report(List<Machine> machines) {
String result = "";
for (Machine machine : machines) {
result += reportMachine(machine);
}
return result;
}
Most of the available refactoring tools provide an automated implementation of this refactoring.
Introduce Parameter Object
A group of parameters is often seen together in method signatures. We can remove some duplication and convert them into a single new domain abstraction.
For this example, consider an application in which a robot moves along a row of machines in a production plant. We may have object such as this:
public class Report {
public String report(List<Machine> machines, Robot robot) {
String result = "FACTORY REPORT\n";
for (Machine machine : machines) {
result += reportMachine(machine);
}
return result + "\n" + reportRobot(robot) + "========\n";
}
}
Together with client code such as this:
String report = Report.report(machines, robot);
If we notice that the list of Machines is often passed around with the Robot we may decide to parcel them up together as a Plant object.
Step 1: Create a new class for the clump of values.
First, we create a new Plant class:
public class Plant {
public final List<Machine> machines;
public final Robot robot;
public Plant(List<Machine> machines, Robot robot) {
this.machines = machines;
this.robot = robot;
}
}
This is a simple container for the two values, and we have made it immutable to keep things clean.
Step 2: Add Plant as an additional method parameter.
We pick any one method that takes machines and robot as parameters, and add an additional parameter for the plant:
public class Report {
public String report(List<Machine> machines, Robot robot, Plant plant) {
//...
}
}
And change the caller to match:
String report = Report.report(machines, robot, new Plant(machines, robot));
Compile and test to verify that we have changed no behavior.
Step 3: Switch the method to use the new parameter.
Now we make the original parameters redundant, one at a time. First, we alter the method to get the machines from the plant:
public class Report {
public String report(List<Machine> machines, Robot robot, Plant plant) {
String result = "FACTORY REPORT\n";
for (Machine machine : plant.machines) {
result += reportMachine(machine);
}
return result + "\n" + reportRobot(robot) + "========\n";
}
}
This is another safe base; we have the option here to compile, test and commit if we wish.
Now the machines parameter is unused within the method we can remove it from the signature:
public class Report {
public String report(Robot robot, Plant plant) {
//...
}
}
And from every call site:
String report = Report.report(robot, new Plant(machines, robot));
Another safe base, if we wish to take advantage of it.
Then we do the same with the robot parameter, and we're done:
public class Report {
public String report(Plant plant) {
String result = "FACTORY REPORT\n";
for (Machine machine : plant.machines) {
result += reportMachine(machine);
}
return result + "\n" + reportRobot(plant.robot) + "========\n";
}
}
and:
String report = Report.report(new Plant(machines, robot));
We can follow these same steps for every method that has the same two parameters.
Separate Query from Modifier
Methods that have side effects are harder to test and less likely to be safely reusable. Methods that have side effects and return a value also have multiple responsibilities. So oftentimes it can benefit the code to split such a method into separate query and command methods.
Imagine we have a Meeting class with a method that looks for a manager in its configuration file and sends them an email:
class Meeting {
public StaffMember inviteManager(String fileName) throws IOException {
BufferedReader in = new BufferedReader(new FileReader(fileName));
String line = "";
while ((line = in.readLine()) != null) {
StaffMember person = new StaffMember(line);
if (person.isManager()) {
sendInvitation(this, person);
return person;
}
}
return null;
}
}
This method performs as a query – looking up the manager in the file – and as a command. The code will be somewhat more testable if we can separate those two responsibilities.
Step 1: Create a copy with no side effects.
We create a new method by copying the original and deleting the side effects:
class Meeting {
public StaffMember findManager(String fileName) throws IOException {
BufferedReader in = new BufferedReader(new FileReader(fileName));
String line = "";
while ((line = in.readLine()) != null) {
StaffMember person = new StaffMember(line);
if (person.isManager()) {
return person;
}
}
return null;
}
}
This is a pure query, and as it is never called, we can compile, test and commit if we wish.
Step 2: Call the new query.
Now we have the new query method, we can use it in the original method:
class Meeting {
public StaffMember inviteManager(String fileName) throws IOException {
BufferedReader in = new BufferedReader(new FileReader(fileName));
String line = "";
while ((line = in.readLine()) != null) {
StaffMember person = new StaffMember(line);
if (person.isManager()) {
sendInvitation(this, person);
return findManager(fileName);
}
}
return null;
}
}
Compile and test.
Step 3: Alter the callers.
Imagine the original method is called here:
public void organiseMeeting() throws IOException {
StaffMember manager = meeting.inviteManager(employeeData);
notifyOtherAttendees(manager);
}
We alter this method to make separate explicit calls to the command and the query:
public void organiseMeeting() throws IOException {
meeting.inviteManager(employeeData);
StaffMember manager = meeting.findManager(employeeData);
notifyOtherAttendees(manager);
}
We do this for all callers of the original method, and as usual we can compile, test and commit at any stage because we are not altering the application's overall behavior.
Step 4: Void the command method.
When all the callers have been converted to use the command-query separated methods, we can remove the return value from the original method:
class Meeting {
public void inviteManager(String fileName) throws IOException {
BufferedReader in = new BufferedReader(new FileReader(fileName));
String line = "";
while ((line = in.readLine()) != null) {
StaffMember person = new StaffMember(line);
if (person.isManager()) {
sendInvitation(this, person);
}
}
}
}
This method is now a pure command. As ever, the callers should still pass their tests.
Step 5: Remove duplication.
Finally, in our example we can use the new query within the command method to remove some duplication:
class Meeting {
public void inviteManager(String fileName) throws IOException {
StaffMember manager = findManager(fileName);
if (manager != null)
sendInvitation(this, manager);
}
}
Replace Inheritance with Delegation
Sometimes you need to extract a class from an inheritance hierarchy.
Step 1: Create a new field in the subclass to hold an instance of the superclass.
Initialise the field to this.
Step 2: Change all calls to superclass methods so that they refer to the new field.
Instead of directly calling superclass methods from the subclass, call them via the object referred to in your new field. Compile and test.
Step 3: Remove the inheritance and initialise the field with a new instance of the superclass.
Compile and test. At this point we may need to add new methods to the subclass if its clients use methods it previously inherited. Add these missing methods, compile and test.
Remove Control Couple
Sometimes a method parameter is used inside the method solely to determine which of two or more code paths should be followed. Thus the method has at least two responsibilities, and the caller "knows" which one to invoke by setting the parameter to an appropriate value. Boolean parameters are often used in this way.
Step 1: Isolate the conditional
If necessary, use Extract Method to ensure that the conditional check and its branches form the entirety of a method.
Step 2: Extract the branches
Use Extract Method on each branch of the conditional, so that each consists solely of a single call to a new method.
Step 3: Remove the coupled method
Use Inline Method to replace all calls to the conditional method, and then remove the method itself.
Replace Error Code with Exception
Sometimes the special values returned by methods to indicate an error can be too cryptic, and it may be preferable to throw an exception.
Step 1: Decide whether the exception should be checked or unchecked.
Make it unchecked if the caller(s) should already have prevented this condition occurring.
Step 2: Copy the original method, and change the new copy to throw the exception instead of returning the special code.
This new method is not called yet. Compile and test.
Step 3: Change the original method so that it calls the new one.
The original method will now catch the exception and return the error code from its catch block. Compile and test.
Step 4: Use Inline Method to replace all calls to the original method by calls to the new method.
Compile and test.
Hide Delegate
When one object reaches through another to get at a third, it may be time to reduce coupling and improve encapsulation.
Step 1: For each method you reach through to call, create a delegating method on the middle-man object.
Compile and test after creating each method.
Step 2: Adjust the client(s) to call the new delegating methods.
Compie and test at each change.
Step 3: If possible. remove the accessor from the middle-man.
If no-one now accesses the delegated object via the middle-man, remove the accessor method so that no-one can in future.
Preserve Whole Object
When several of a method's arguments could be obtained from a single object, it may be beneficial to pass that object instead of the separate values.
Step 1: Add a new parameter for the whole object
Pass in the object that contains the values you wish to replace. At this stage this extra parameter is not used, so compile and test.
Step 2: Pick one parameter and replace references to it within the method.
Replace uses of the parameter by references to the value obtained from the whole object. Compile and test at each change.
Step 3: Remove the parameter that is now unused.
Don't forget to also delete any code in the callers that obtains the value for that parameter. Compile and test.
Step 4: Repeat for every other value that can be obtained from the new parameter.
Compile and test.
{{ parent.title || parent.header.title}}
{{ parent.tldr }}
{{ parent.linkDescription }}
{{ parent.urlSource.name }}