Back

Refactoring: Rid Your Code of Smells

In our previous post on refactoring, we discussed what code smells are and some ways to detect them. In this post, we will cover some specific refactorings that will help us get rid of code smells.

 

Method Refactorings

Extract Method

As a method grows in size, code fragments begin to appear that can be clumped together in a more reusable and descriptive way.

The old:

void printCustomerData(Customer customer) {    System.out.println ("First Name: " + customer.firstName);    System.out.println ("Last Name: " + customer.lastName);    System.out.println ("DOB: " + customer.dateOfBirth);    for (pet in customer.pets) {        System.out.println ("Pet Name: " + pet.name);    }}

 

The new:

void printCustomerData(Customer customer) {    printPersonalInformationForCustomer(customer);    printCatInformationForCustomer(customer);}void printPersonalInformationForCustomer(Customer customer) {    System.out.println ("First Name: " + customer.firstName);    System.out.println ("Last Name: " + customer.lastName);    System.out.println ("DOB: " + customer.dateOfBirth);}void printCatInformationForCustomer(Customer customer) {    for (cat in customer.cats) {        System.out.println ("Cat Name: " + cat.name);    }}

With the new code, the printCustomerData function is much more readable, since the individual code fragments are now named with exactly what they do. We can also easily reprint the personal information for a customer elsewhere in the app if we need to.
Extract Method can help resolve the following smells: Duplicate Code, Long Method, Feature Envy, Switch Statements, Message Chains, Comments, Data Class.

 

Inline Methods

On the flipside of Extract Method, the code might be easier to understand if we remove a call to a method and instead just replace a method call with the method’s contents. If there are many small functions masking the real content of the method, then the code can be cumbersome to read through.

The old:

boolean catIsTrending() {    return catHasTenUpvotes();}boolean catHasTenUpvotes() {    return cat.upvotes > 10;}

 

The new:

 

boolean catIsTrending() {    return cat.upvotes > 10;}

 

Inline Method can help resolve Speculative Generality.

 

Extract Variable

Sometimes as new features are added and new checks are required, we can end up with very long expressions that are very hard to understand or read. If we extract pieces of these expressions out into functions, then we can write an expression that is succinct and easy to read.

The old:

void addNewCatToCustomer() {    if (cat.name.length > 0 && cat.age > 3 && catBreed.indexOf(cat.breed) >= 0) {        // add cat to customer    }}

 

The new:

void addNewCatToCustomer() {    boolean hasName = cat.name.length > 0;    boolean isOfAge = cat.age > 3;    boolean isValidBreed = catBreed.indexOf(cat.breed) >= 0;    if (hasName && isOfAge && isValidbreed) {        // add cat to customer    }}

In the new code, although we have added more variables and made the code longer, the conditional is much easier to understand.
Extract variable can help resolve the Comments code smell.

 

Object Refactoring

Extract Class

Classes should start out clear and easy to understand if you have a design to follow. However, classes tend to grow as new features are added, and might start taking on more than they were initially intended for. If a class seems to be keeping track of two distinct data sets, then we can split it into two classes.

The old:

class Cat {    string name;    int age;    Color color;    boolean sheds;    boolean playful;    //..}

 

The new:

class Cat {    string name;    int age;    CatBreed breed;    //..}class CatBreed {    Color color;    boolean sheds;    boolean playful;    //..}

By breaking our main Cat class into both the Cat and CatBreed class, we adhere more to the Single Responsibility principle, meaning our classes each just handle one thing. This also makes it easier to update the CatBreed class without potentially breaking our Cat class.

Extract Class can help resolve the following smells: Duplicate Code, Large Class, Divergent Change, Data Clumps, Primitive Obsession, Temporary Field, and Inappropriate Intimacy.

 

Move Method

If we have a method that was created in one class, but ends up being used more by a different class, it might make sense to move that method. This will help reduce dependency between classes. In addition to this, we want to keep the method as close as possible to the data it will be using.

The old:

class Cat {    //..    CatBreed breed;    boolean isCatHappy() {        if (breed == "Siamese") {          // determine cat happiness based on breed factors        }        else if (breed == "Persian") {          // determine cat happiness based on breed factors        }    }    boolean isOwnerHappy() {        return isCatHappy();    }}class CatBreed {    //..}

 

The new:

class Cat {    //..    CatBreed breed;    boolean isOwnerHappy() {        return breed.isCatHappy();    }}class CatBreed {    //..    boolean isCatHappy() {        if (this == "Siamese") {          // determine cat happiness based on this breed’s fickle nature        }        else if (this == "Persian") {          // determine cat happiness based on this breed’s fickle nature        }    }}

In this new code, our methods are closer to the data that is relevant to them. Instead of determining cat happiness based on breed in the Cat class, we can determine this within the CatBreed class, where all of that data is located.
Move Method can help resolve the following smells: Shotgun Surgery, Feature Envy, Switch Statements, Parallel Inheritance Hierarchies, Message Chains, Inappropriate Intimacy.

 

Method Call Refactorings

Rename Method

Although this refactoring is simple, it can be extremely effective in increasing code readability. Just as the name states, renaming a method can add to understanding of what the method actually does. One notable example is the use of abbreviations. Often, developers will write method names with abbreviations that they are familiar with. However, after years have gone by and someone else is maintaining their legacy code, the abbreviation might not make much sense to them. By having descriptive method names, we avoid that issue.

The old:

void printCatABL();

 

The new:

void printCatAgeBreedLocation();

 

Rename Method can help resolve the following code smells: Alternative Classes with Different Interfaces, Comments.

 

Replace Parameter with Method Call

If a method call has too many parameters it can be difficult to read and/or understand. Sometimes these parameters are calculated by other methods. If this is the case, we might be able to calculate those parameters within the method, avoiding a long parameter list.

The old:

int age = cat.getAge();int livesRemaining = cat.getLivesRemaining();double lifeExpectancy = calculateLifeExpectancy(age, livesRemaining);

 

The new:

double lifeExpectancy = calculateLifeExpectancy(cat);

In the new code, if we pass the Cat object then we will have access to its methods within the calculateLifeExpectency method; then we won’t need to access those methods ahead of time.

 

What Now?

The above refactorings are just a very small subset of a much larger catalog that can be found on Refactoring.Guru, https://refactoring.guru/refactorings/refactorings. This site has a comprehensive catalog including interactive code samples and code samples for a variety of languages. The information on this site also mirrors the data found in the catalog on Martin Fowler’s site, http://www.refactoring.com/catalog/.

 

William Grand
William Grand