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!!

Posted in Better Programming

Essential Git Commands To Improve Project Workflow

1. Convert a repository into a Git repository

This will create a .git sub folder in your project directory.

$ cd <PROJECT_DIRECTORY>
$ git init

2. Clone a repo and pull the latest changes

$ git clone <GIT URL>
$ git pull

3. Create a new branch

$ git checkout -b <DESCRIPTIVE BRANCH NAME>

# View all branches
$ git branch

# Checkout an existing branch
$ git checkout <BRANCH_NAME>

4. Delete a branch

# Delete a local branch
$ git branch -d <BRANCH NAME>

# Delete a remote branch
$ git push origin --delete <BRANCH NAME TO DELETE>

5. Rename a branch

# From the branch to be renamed
$ git branch -m <NEW BRANCH NAME>

# From another branch
$ git branch -m <OLD BRANCH NAME> <NEW BRANCH NAME>

# Steps to rename a remote branch after following the above steps
$ git push origin -u <NEW BRANCH NAME>

# Delete old remote branch
$ git push origin --delete <OLD BRANCH NAME>

6. Pull the latest changes from the master branch

There are two ways to pull the latest changes from master into your branch. Tip: If you are working on a complex change, pull the changes from master often (once or twice everyday depending on how frequently new changes are shipped to master). This will save a lot of time not having to deal with merge conflicts.

Do a merge

Note: In this way, you can checkout master and pull all the latest changes into your branch but will have to solve multiple conflicts that arise from this scenario. Solve any conflicts that come up after executing the following commands.

$ git checkout <YOUR BRANCH>
$ git merge master

Do a rebase

$ git checkout <YOUR BRANCH>
$ git rebase master

Not sure whether to do a merge or rebase? Checkout this tutorial on merging vs rebasing.

7. Commit and push changes

# Check the changes you have made
$ git diff

# Stage all the changes
$ git add .

# Stage only a single file
$ git add <FILE NAME>

# Commit the changes
$ git commit -m "Commit message"

# Stage changes and commit one-liner
$ git commit -am "Commit message"

# Check if everything looks okay
$ git status 

# Push changes to remote
$ git push origin master

8. Save changes locally without committing

# Save uncommitted changes in a stack where you can get it back later
$ git stash 

# To retrieve the stash and apply it on top of your branch
$ git stash apply 

# Save stash with a name
$ git stash push -m "Name of the stash"

# To view list of all stashes
$ git stash list 

# Apply a stash with index n
$ git stash apply stash@{n}

# Apply a stash and pop it from stack
$ git stash pop stash@{n}

9. Pull the latest changes into a branch

This can result in change conflicts which have to be resolved.

$ git pull

10. Check commit history

This will display an entire scrollable commit history of your repository.

$ git log

11. Merge branch with master

# Squash and merge if there are too many noisy commits
$ git checkout master
$ git merge --squash <BRANCH TO MERGE>
$ git commit

# Regular merge
$ git checkout master
$ git merge <BRANCH TO MERGE>
$ git push origin master

12. Something’s on fire! Revert!

# Revert a single commit
$ git revert <COMMIT SHA>

# Revert Multiple commits
# Note: Works well only if there are no merge commits done
$ git revert <OLDEST COMMIT SHA>..<LATEST COMMIT SHA>

# Revert multiple commits and retain commit history
# Note: Works with merge commits
$ git checkout -f <TARGET COMMIT SHA> -- .
$ git commit -m 'revert to <TARGET COMMIT SHA>'
$ git diff HEAD # To check

These commands have helped me get 90% of my work done during all these years. Special scenarios have been pretty rare and they definitely warrant a deeper understanding before diving into them. Good luck!