2.4 Refactoring

Refactoring[7] is the practice of improving the design of code without breaking its functionality. In order to keep code simple and prevent the cost of making changes from skyrocketing, you must constantly refactor. This keeps your code as simple as possible.

[7] See Refactoring: Improving the Design of Existing Code by Martin Fowler, et al. (Addison-Wesley).

Here is a simple refactoring. Suppose you have this code:

public class Person {
    private String firstName;
    public void setFirst(String n) {
       this.firstName = n;
    }
}

This code can be improved by picking a better argument name, changing n to firstName:

public class Person {
    private String firstName;
    public void setFirst(String firstName) {
       this.firstName = firstName;
    }
}

The code can be improved even further by renaming the method to setFirstName( ):

public class Person {
    private String firstName;
    public void setFirstName(String firstName) {
       this.firstName = firstName;
    }
}

The method has been refactored and is now more easily understandable. Of course, changing the method name requires you to change all references to the method throughout your application. This is where a good IDE can help out, because it can identify all usages and update the calls automatically.

2.4.1 Goals

You refactor code to make it simpler. Each individual refactoring introduces a small improvement; however, these small improvements add up over time. By constantly striving to keep code as concise and as simple as possible the cost of making changes to an application does not rise so dramatically over time.

Removing duplication is another goal of refactoring that deserves mention. Duplicated logic is almost always harder to maintain because changes must be made to more than one part of the system as it evolves. We have found that duplicated logic is often a signal that code should be refactored and simplified.

Without refactoring, complexity inevitably increases as more and more features are tacked onto a system. Increasing complexity is known as entropy, and is a fundamental reason why the cost of change increases over time. Our goal is to stave off entropy as long as possible through constant refactoring.

2.4.2 When to Refactor

You should refactor constantly, throughout the lifetime of a project. Your customer and manager will not ask you to refactor your code, just like they probably won't ask you to write unit tests. Instead, this is a practice that must be engrained into your daily routine. Each time you fix a bug or add a new feature, look for overly complex code. Look for chunks of logic that are duplicated and refactor them into a shared method. Try to rename methods and arguments so they make sense, and try to migrate poorly designed code towards better usage of design patterns.

Writing unit tests is a great way to identify portions of code that need refactoring. When you write tests for a class, your test is a client of that class. You will have first-hand experience using the class, and will be the first to realize when the API is overly complicated. Use this opportunity to refactor the API and make it simple to use.

2.4.3 How to Refactor

Refactoring works hand-in-hand with automated unit testing. Before you refactor code, make sure you have a working unit test on hand. Assuming that the test works before the refactoring effort, it should also work after you are done refactoring. This process is similar to any new feature or bug fix that you put into the system:

  1. Make sure you have a working unit test for the feature you are about to refactor.

  2. Do the refactoring, or a portion of the refactoring.

  3. Run the test again to ensure you did not break anything.

  4. Repeat steps 2-4 until you are finished with the refactoring.

2.4.4 Refactoring Tools

Most new IDEs on the market offer rudimentary support for refactoring, and this is constantly improving. Some key features to look for include:

  • The ability to rapidly find usages for fields, methods, and classes. This ability makes it easier for you to determine what will break if you start changing things.

  • The ability to automatically rename fields, methods, classes, and other entities. All references to those items should be updated automatically.

  • The ability to automatically convert a selected block of code into a method call. This is called Extract Method in most refactoring tools.

These are just the tip of the iceberg. Many tools already do much more than this.