Code is written once but read multiple times. Hence, writing readable, understandable, and maintainable code is essential to create a robust software application. In this article, we will discuss the basics of code refactoring. We will also discuss some code refactoring techniques that you can use to improve your code.
What is Code Refactoring?
Code refactoring is the process of modifying an existing code. In code refactoring, we do not introduce any new functionality or alter the working of any software module. Instead, we optimize the structure and implementation of the code to increase readability and understandability. The goal of code refactoring is to write clean code having the following features.
- Refactored code shouldn’t contain random variable names, poorly structured classes, and functions, magic numbers, etc. This helps improve readability and maintainability.
- A refactored code shouldn’t have duplicate code snippets. A duplicate code makes it hard to introduce changes to the code in the future as a developer needs to update the changes at multiple places. These changes may be hard to track and can produce errors.
- A clean and refactored code must avoid bugs and it should pass most of the test cases.
When Should We Refactor a Source Code?
Code refactoring is good. But how do we know that we need to refactor the code? You can use the following criteria to decide on code refactoring.
The Rule of Three
The Rule of three says that when you are writing a code snippet for the third time in the source code, you should refactor your code.
- When you are writing code for the first time, you need to write it for sure.
- For the second time, it might seem repetitive but you should write the code.
- When you need to write the same code snippet for the third time, refactor and create a function from the code.
Refactor Code While Fixing Bugs
Refactoring is one of the easiest ways to fix bugs in a code. If the code is messy and unstructured, bugs will be there for sure. Hence, you should always refactor your code to make it clean and readable to reduce bugs.
Refactor While Adding a Feature
When you are adding a new feature to a source code, it is the right time to refactor it. Whether or not the existing code is written by you, you should first refactor it to understand the existing functionalities and make the code clean and readable. This will help you add new features to the code easily as you will have an understanding of the code structure, naming conventions, etc.
Refactor After a Code Review
When your team reviews your code, they can point out the mistakes and anomalies in the code. Hence, you should do a code review, gather the inputs and then refactor the code before freezing the code.
How to Perform Code Refactoring?
We should refactor the code in a series of smaller changes. Each change should make the code slightly better without disturbing the functionality of the code.
- While refactoring the code, you should focus on making the code cleaner. If the code remains messy even after refactoring, there is no point in investing your time to refactor the code.
- Never mix code refactoring and the addition of new features in the code. If you want to add a new feature to the code, you should refactor it first. Then, you can add new features to the code.
- After refactoring, the code must pass all the existing test cases. This will help you ensure that the code hasn’t been changed functionally and unwanted errors haven’t been introduced into the code while refactoring.
Now, let us understand some code refactoring techniques with examples.
Suggested reading: The Zen of Python
Code Refactoring Techniques
There are different code refactoring techniques that you can use to improve your code. Let us discuss all these techniques one by one.
Extract Methods and Functions
If there is a code fragment that can be grouped together, you should consider moving the code fragment into a new method or function. When there are more lines in a code snippet, it is harder to understand. Hence, we can create functions using the code that performs a particular task.
To understand this, consider the following code.
weight=int(input("Enter Weight in Kgs:"))
height=int(input("Enter height in cms:"))
heightInM=height/100
BMI=weight/heightInM**2
print("BMI is:",BMI)
The above code takes height and weight input from the user and prints BMI. Now, BMI can also have other applications in the program. So, it’s better to create a new function for calculating BMI. Also, having a separate function for each atomic task helps us reduce code duplication and isolates independent parts of code. To refactor the above code, we will use the following steps.
- First, we will create new methods and name them in a way that makes their purpose clear.
- Next, we will copy the existing codes to the relevant methods and delete the code snippets from their original place. After deletion, we will put a function call for each new method that we create at appropriate places.
- If the variables inside a code fragment that we move to the new methods are declared inside the fragment and aren’t used anywhere else, we will leave them unchanged. These variables will become local variables for new methods.
- If the variables are declared before the code in the new method, we will pass those variables as input to the new method.
- If the variables are used after the code that we moved to the new method, we will return the variable value from the function.
Using the above steps, we can refactor the code given in the previous snippet as follows.
def calculateBMI(weight,height):
heightInM=height/100
BMI=weight/heightInM**2
return BMI
def printBMI(BMI):
print("BMI is:",BMI)
weight=int(input("Enter Weight in Kgs:"))
height=int(input("Enter height in cms:"))
BMI=calculateBMI(weight,height)
printBMI(BMI)
In the above code, we first split the code into three main components. The first code component takes inputs from the user. The second code component calculates BMI and the third code component prints the value.
- We take the user inputs in the
main()
method as usual. - Next, we moved the code to calculate BMI into another function named
calculateBMI()
. As we need the height and weight values to calculate BMI, we will pass these values as input arguments to the function. As the BMI value is later used in the program, we return the value using a return statement. - We also define a printBMI() function that takes the BMI value as its input and prints it.
You can also put the code to take input values into a function and return the height and weight values as shown below.
def takeInput():
weight=int(input("Enter Weight in Kgs:"))
height=int(input("Enter height in cms:"))
return height, weight
def calculateBMI(weight,height):
heightInM=height/100
BMI=weight/heightInM**2
return BMI
def printBMI(BMI):
print("BMI is:",BMI)
height,weight=takeInput()
BMI=calculateBMI(weight,height)
printBMI(BMI)
This approach will also help you take height and weight inputs multiple times in the program without duplicating code.
Extract Variables From Expressions
If you have an expression that is hard to understand, you can replace the parts of the expression with variables that are self-explanatory. For example, consider the following code.
def determineHealth(heightinM,weight):
if weight/heightInM**2 <18.5:
print("Underweight")
elif 18.5<=weight/heightInM**2 <=24.9:
print("Healthy")
elif 25<=weight/heightInM**2 <=29.9:
print("Overweight")
else:
print("Obese")
In the above code, you can observe that the expression weight/heightInM**2 has been used multiple times in conditional expressions. It increases confusion and reduces readability.
To refactor this code, you can insert a new variable and assign the expression to the variable. After this, you can replace all the instances of the expression with the newly created variable as shown below.
def determineHealth(heightinM,weight):
BMI=weight/heightInM**2
if BMI<18.5:
print("Underweight")
elif 18.5<=BMI <=24.9:
print("Healthy")
elif 25<=BMI<=29.9:
print("Overweight")
else:
print("Obese")
In the above function, we have replaced the expression “weight/heightInM**2” with the variable BMI to make the code more readable.
Although this approach increases the number of variables in the code, it increases the readability. If there are multiple arithmetic expressions in the same if statement, the code will improve a lot after refactoring each expression into a variable.
Split Temporary Variable
In a function, there might be a local variable that is used to store different intermediate values. In such cases, we can use different variables for different values where each variable is responsible for only one value. To understand this, consider the following code.
def calculateBMI(weight,height):
temp=height/100
temp=weight/temp**2
return temp
In the above code, the first temp variable is used to store the height value in meters after dividing the height in cm by 100. Within the next line, the temp value on the right-hand side of the expression represents the height value in meters whereas the temp value on the left-hand side of the expression represents the BMI value. In the return statement, the temp variable represents the BMI value. Hence, a single variable has different meanings at different places.
We can refactor this code by renaming the temp variable according to their usage as shown below.
def calculateBMI(weight,height):
heightInM=height/100
BMI=weight/heightInM**2
return BMI
In the above code, we have renamed the temp variable to heightInM and BMI as per their values. This helps us make sure that each component of the code is responsible for only one thing.
Replace Inline Temporary Variables
If you have methods where you execute a single statement and return the value, you can avoid using temporary variables. For example, consider the following code.
def calculateBMI(weight,heightInM):
BMI=weight/heightInM**2
return BMI
In the above code, you can remove the variable BMI by putting the entire expression to calculate the BMI value as shown below.
def calculateBMI(weight,heightInM):
return weight/heightInM**2
In the approach for code refactoring, if a variable is used multiple times, you cannot replace it with the expression. This will make the code less readable and can also decrease the speed of code execution.
Remove Assignments to Parameters
If a code assigns a value to an input parameter, you should refactor it to create a local variable instead of modifying the value in the parameter. For example, consider the following code.
def calculateBMI(weight,height):
height=height/100
BMI=weight/height**2
return BMI
In the above code, we have reassigned the value from the height parameter. This is a bad practice. We should always create a local variable to store temporary values in a function.
In programming languages like C, this practice may lead to errors. In C, we can pass values by reference or by copying to a function. When the value is passed by reference and we reassign values to the parameter, it may change the values outside the scope of the function.
Hence, we can refactor the above code as shown below.
def calculateBMI(weight,height):
heightInM=height/100
BMI=weight/heightInM**2
return BMI
Here, you can observe that we have created the heightInM variable to handle local variables instead of modifying the parameter. This helps us avoid the side effects of the function.
Consolidate Conditional Expressions
If there are multiple conditional expressions that lead to the same result, you can consolidate all the conditions into a single expression. This will help eliminate duplicate code.
To consolidate multiple expressions, you can create a new method to check for the conditions and use it in the code. To understand this, consider the following code.
def determineHealth(heightinM,weight):
BMI=weight/heightInM**2
if BMI<18.5:
print("Not Healthy")
elif 18.5<=BMI <=24.9:
print("Healthy")
elif 25<=BMI<=29.9:
print("Not Healthy")
else:
print("Not Healthy")
In the above code, there are three conditional statements that lead to the same result. Hence, we can club all these conditions into a single expression to consolidate them as shown below.
def determineHealth(heightinM,weight):
BMI=weight/heightInM**2
if BMI<18.5 or BMI>=25:
print("Not Healthy")
else :
print("Healthy")
Here, we have consolidated three conditional statements into a single statement. This can confuse the reader.
Hence, we can put this statement into a function named isNotHealthy() to specify the meaning of the expression. Then, we can use the function instead of the complex expression as shown below.
def isNotHealthy(BMI):
if BMI<18.5 or BMI>=25:
return True
def determineHealth(heightinM,weight):
BMI=weight/heightInM**2
if isNotHealthy(BMI):
print("Not Healthy")
else :
print("Healthy")
Consolidate Duplicate Conditional Fragments
If you are using multiple conditional expressions in your code, it is possible that the conditions can have duplicate statements. In such a case, we can move the duplicate code out of the conditional expression. For example, consider the following code.
def isNotHealthy(BMI):
if BMI<18.5 or BMI>=25:
return True
def determineHealth(heightinM,weight):
BMI=weight/heightInM**2
if isNotHealthy(BMI):
print("Not Healthy")
print("Your BMI is:",BMI)
else :
print("Healthy")
print("Your BMI is:",BMI)
In the above code, the statement to print the BMI is repeated multiple times. Hence, we can move this statement outside the conditional statements without changing the output as shown below.
def isNotHealthy(BMI):
if BMI<18.5 or BMI>=25:
return True
def determineHealth(heightinM,weight):
BMI=weight/heightInM**2
if isNotHealthy(BMI):
print("Not Healthy")
else :
print("Healthy")
print("Your BMI is:",BMI)
Benefits of Code Refactoring
Code refactoring enhances the quality, maintainability, and efficiency of our program. Following are some of the key advantages of code refactoring.
- Improved readability: Code refactoring helps simplify complex code. It makes it easier for the developers to understand and maintain the code. Clear and concise code also promotes collaboration among developers and reduces the chances of introducing bugs.
- Enhanced maintainability: Refactored code is easier to maintain as it is well-structured and modular. It becomes simpler to add new features, fix bugs, and make changes without unintended consequences.
- Increased extensibility: Refactoring enables the codebase to be more extensible. By breaking down large and monolithic components into smaller, reusable modules, it becomes easier to add new functionality and adapt to evolving requirements.
- Reduced code duplication: Refactoring helps us eliminate code duplication by extracting reusable components and promoting the DRY (Don’t Repeat Yourself) software engineering principle. This reduces maintenance efforts and ensures consistent behavior throughout the codebase.
- Improved performance: Code Refactoring can optimize performance bottlenecks in the code. We can make the code more efficient and streamlined by identifying and eliminating inefficient algorithms or data structures.
- Increased agility: Refactored code is more flexible and adaptable to change. It allows developers to respond quickly to new requirements, market demands, or technological advancements, promoting overall project agility.
- Easier collaboration: Well-refactored code improves collaboration among team members. It fosters easier code reviews, pair programming, and knowledge sharing, leading to higher productivity and better overall code quality.
- Enhanced code documentation: Code refactoring often involves adding or improving code comments, annotations, and documentation. This makes it easier for developers to understand the purpose and behavior of different code segments, reducing knowledge gaps and facilitating the onboarding of new team members.
- Increased codebase longevity: Regular refactoring reduces technical debt and helps maintain a healthy codebase over time. It prevents the accumulation of obsolete or redundant code, making it easier to adapt the software to changing business needs and ensuring its long-term viability.
Conclusion
In this article, we discussed the basics of code refactoring. We also discussed different code refactoring techniques with examples to understand how to refactor code in a better manner. Finally, we discussed the benefits of code refactoring. To learn more about coding, you can read this article on best coding practices. You might also like this article on bad data visualization examples.
I hope you enjoyed reading this article. Stay tuned for more informative articles.
Happy Learning!
Disclosure of Material Connection: Some of the links in the post above are “affiliate links.” This means if you click on the link and purchase the item, I will receive an affiliate commission. Regardless, I only recommend products or services I use personally and believe will add value to my readers.