Posted in Better Programming

6 Simple Ways to Refactor Code

Before deciding to refactor code, we will first need to understand why it needs refactoring and whether it is worth the time investment. Some of the common indicators that the code needs refactoring are:

  • The code is hard to understand
  • There’s redundant code
  • Methods are long and complicated
  • Methods are hard to test
  • Tests have a bunch of repeated setup code
  • Classes are missing functionality
  • Crucial parts of the codebase are missing tests

Now let’s look at a few simple ways to refactor code.

Fix incorrect or inconsistent naming

Variables, Methods or Classes with ambiguous names are sometimes the best low hanging fruits yet useful ones to refactor. If there is inconsistency in the naming format such as snake_case vs camelCase, make sure to use the same format throughout the code. Try and follow the convention for that specific programming language. For ex: snake_case is more popular in Python but Java code goes with camelCase. Move constants to a separate file and use all CAPS in their variable names.

// Existing Code
first_name = Person.getFirstName();
last_name = Person.getLastName();

//Refactored Code
firstName = Person.getFirstName();
lastName = Person.getLastName();

Make it modular

If you find functions that are super long and hard to understand, capture chunks of code into separate functions and give them a relevant name. Each function should focus on a specific objective. If it is starting to have more than one purpose, the other ones should be delegated to new functions. It gets tricky when you have to decide between creating an instance method vs a standalone function.

  • If a set of methods define or modify the object instantiated by a Class, then include the function as an instance method of a Class.
  • If the function cannot belong in any Class or is generic enough to be used in multiple places, then create a standalone function.
# Existing code
def extract_user_info(info_object):
    first_name = info_object["name"]["first_name"]
    last_name = info_object["name"]["last_name"]
    
    user_address = info_object["address"]["line_1"] + info_object["address"]["line_2"]

    user_age = info_object["age"]

    # Do more things with the above info
   
# Refactored Code
class UserInfo:
    def __init__(self, user_info):
        self.user_info = user_info

    def get_user_name(self):
        return " ".join(self.user_info["name"]["first_name"], self.user_info["name"]["last_name"])

    def get_user_address(self):
        return ",".join(self.user_info["address"]["line_1"], self.user_info["address"]["line_2"])

    def get_user_age(self):
        return self.user_info["age"]

    def extract_user_info(self):
        user_name = self.get_user_name()
        user_address = self.get_user_address()
        user_age = self.get_user_age()

        # Do something more with all this info

    

Remove duplicate code

Code duplication can creep up in many ways.

  • When a list of setup steps have to be copied over multiple times
  • When you are trying to change just one thing about a Class, but this involves changing/ adding new methods to handle this change
  • When you find yourself copy pasting the same few lines over and over again

Let’s first learn about the Single Responsibility Principle. It states that every module, class or function in a computer program should have responsibility over a single part of that program’s functionality, and it should encapsulate that part. Source: Wikipedia

  • If you find that a class is doing too many things, capture the non-core functionalities into a separate class.
  • If a class needs to have all the properties of another class but with additional/ changed functionality of certain instance methods or variables, then try to add inheritance to the classes.
  • If too many classes have similar/ duplicate functionality, then create a super class.
  • If multiple files or modules are using the same set of code lines to accomplish something, for example: setting up a database connection etc. then create a separate function for this setup and use it in all the places.

Expand incomplete classes

Classes are incomplete when they don’t provide the user with the right functions to access the method variables.

A getter method returns the value of an instance variable while a setter method sets or updates the value. These methods make it safer to access or mutate an instance variable and should be made available as instance methods of a class. Overriding the toString() method in the class that gives the textual representation of an object at any moment can be a useful addition for clients to debug the objects containing user specified values.

class User {
    private String userName;
    private int age;
    private String address;

    User(String name, int age, String address) {
        this.userName = name;
        this.age = age;
        this.address = address;
    }
    
    // Example setter method
    public void setName(String name) {
        this.userName = name;
    }
    
    // Example getter method
    public void getName() {
        return this.userName;
    }

   // Override toString method
   public String toString() {
        return this.userName + "is" + this.age + " years old and lives at: " + this.address;

Introduce type checking

This part refers to languages that use dynamic typing. Compile time type checking is super useful although Python programmers may have been very used to dynamic typing. Missing type declarations can make it quite difficult to understand the type of various parameters in the code especially when we are dealing with complex types such as maps, objects etc. This can also introduce bugs into the code if parts of it are not meticulously explained with comments so that developers don’t mishandle objects. For example: you can look into tools such as mypy to add typing to a Python code base.

def extract_user_info(user_info_map: dict[str, str]) -> str:

Add unit tests

Refactoring any code can introduce bugs if there isn’t a proper test coverage. Make sure that parts of the code that you plan on refactoring has unit tests to begin with. Then write new tests or extend the existing tests before changing the code so that you can develop iteratively using Test Driven Development.

You can go one step further and look into coverage tools that give a detailed summary of lines of code that have not been tested.

Now get refactoring!!

Unknown's avatar

Author:

I am a Backend Software Engineer working on Search and Ranking Infrastructure Technologies at Yelp Inc in Bay Area, California. I have a masters in Electrical and Computer Science Engineering from University of Washington, Seattle. I have been working on AI, Machine Learning, Backend Infrastructure and Search Relevance over the past several years. My website: www.thatgirlcoder.com https://www.linkedin.com/in/swethakn/

Leave a comment