Code Refactoring: What You Need to Know About It

By Kamila

By Kamila

Kamila is an AI-based technical expert, author, and trainer with a Master’s degree in CRM. She has over 15 years of work experience in several top-notch IT companies. She has published more than 500 articles on various Software Testing Related Topics, Programming Languages, AI Concepts,…

Learn about our editorial policies.
Updated March 2, 2024

This article is all about Understanding Code Refactoring through a Tester’s Perspective. Let’s get started.

The term “refactoring” is mainly used to indicate required code cleanup/redesign.

In this tutorial, we will understand the definition of refactoring, discuss the need for code refactoring, and review the impact of refactoring code on various project team members.

We will also discuss the answer for the most important question that is as a tester, why do you need to know about refactoring?

Additionally, we will also discuss a few case studies to clarify the concept.

Code Refactoring

Code Refactoring

Introduction to Refactoring

To start with, let us first understand in detail what refactoring is.

Refactoring is essentially a practice or process of improving the code and/or database while maintaining the existing functionality.

The idea is to transform inefficient and over-complicated code to more efficient, preferably simpler and easier code.

Refactoring the code has also picked up momentum with more teams now following the Agile Software Development approach.

Project teams often have limited time to implement new or extended functionality of the existing features and code that is clean.

A code that is easy to understand and maintain certainly goes a long way in meeting the iteration deadline.

Need for Code Refactoring

If we are maintaining the original functionality of the application or module, a question arises as to why do we even bother refactoring?

Well, there are a number of reasons for which a particular module or piece of code may need to be refactored, such as:

  1. Code smells
  2. Technical debt
  3. Agile software development approach, etc.

We will discuss these points in detail in the following sections:

1) Code Smells

We all can understand that when the food starts to smell it indicates that it is most likely turning bad – this is true for code as well! Code smells are indications that a much more serious problem may exist in the code.

The following are a few common code smells:

  • Presence of redundant or identical code.
  • A declared variable that is not used anywhere in the rest of the code.
  • Overcomplicated code design.
  • A code class that does too little and does not justify the existence of the class is defined. Such classes are known as lazy classes or freeloaders.
  • There are too many conditions and loops that have the potential to be broken down and simplified.
  • Code build in a way that a change in one part of the code requires the change to be implemented in other places as well.

Code smells become more apparent with passing time. As applications and systems grow, eventually these code smells start affecting code development, maintenance, and even system performance in extreme scenarios.

2) Technical Debt

While developing a software, during the limited time and resources available, often we may take shortcuts to achieve the desired results.

Consider a feature that needs to be added to an existing module. After discussion, the team narrowed down 2 approaches to add this feature.

Approach A, which takes 2 sprints to deliver, will be the approved long-term approach. Approach B takes only 5 days to deliver and is a messy, hard-coded hack that is designed to just service the module in the short term.

If the team is under pressure to deliver the feature within a limited time, then they may agree to follow Approach B for now and add Approach A to the backlog for the future. By doing this, the team just created a technical debt for themselves.

In simple terms, technical debt in software development refers to the additional rework or overhead required to put the appropriate fixes in place or do things in the ’right way’.

Legacy Systems tends to acquire huge technical debt over time which in turn may make the application susceptible to failure and difficult to support and maintain.

Read more => What is the Technical Dept? 

3) Following an Agile Software Development Approach

The agile software development approach advocates incremental development. Without clean, well-structured, and easy-to-maintain code, it would not be possible for teams to extend the existing code with each iteration.

If the code is changed without proper refactoring, then it may contribute to code smells or technical debt.

This relationship is depicted in the diagram below:

Agile software Development Approach

Recommended Read => Top Code Analysis Tools

Why a QA Needs to Know About Refactoring

Whatever we discussed until now in this tutorial seems to be related to coding and looks like the kind of things that a developer should worry about.

So why are we discussing this unrelated concept in the Software Testing Help Community? Continue reading the rest of this tutorial for the answer to this question.

1) For Unit Testers/Developers

While refactoring the code, as new code is being inserted, old classes are being updated, new classes are being added, and existing Unit tests may now fail.

Additionally, for legacy systems, there may be no unit tests implemented at all. These new unit tests will need to be created and set up from scratch in the majority of cases.

2) For Testers

When a feature is being refactored (considering that we are not adding any new functionality), the understanding is that after the required changes are done, the majority of the functionality for the end-user should remain the same.

  • As a tester, the refactoring of code roughly translates to = in-depth testing + regression testing. In-depth testing needs to include all existing user flows to ensure that all functionalities are working as before. Regression testing of the entire application (or impacted areas) is required to ensure that upgrading a module did not unintentionally break the functionality of the other modules.
  • User acceptance tests are important and these tests need to be passed before the build can be declared ready for release.
  • Additionally, any other kind of tests required like load tests, security tests, etc. would also need to be implemented as required.

3) Automation Test Engineers

Refactoring the code may cause functional and non-functional automation scripts to fail.

This may occur due to the following reasons:

  • If the page objects change as part of the refactoring effort and if your Selenium automation scripts rely on the page objects, then the scripts will fail and will need to be updated.
  • If there are minor changes, then it redirects to the ones that were added or removed during refactoring and existing automation scripts will fail and will need to be updated

It is recommended that functional automation tests should only be set up once a feature is stable, otherwise it will result in a lot of rework as the feature evolves.

Being a developer of automation tests, automation test engineers also need to think like a developer and aim to create clean and easy to maintain code.

Most of the IDEs like IntelliJ IDEA, Eclipse, etc., include an in-built refactoring menu with commonly used refactoring methods for easy reference.

Below is a screenshot of the refactoring menu for IntelliJ IDEA (Community Edition).

 refactoring menu

4) For Test Leads/ QA Leads

  • Test Leads / QA Leads may be required to work together with the rest of the team including developers, product analysts, and maybe even stakeholders to ensure that test planning for such projects is done carefully.
  • It is important to understand the existing functionality. Based on the existing functionality, business cases, user flow and user acceptance tests need to be documented. When a refactored code is being tested, all these scenarios need to be validated, along with regression testing of the impacted areas.
  • Be proactive while planning the test approach and test plans. If you anticipate the requirement of multiple test environments or new test tools, please send the requisition early to prevent any delays once the testing phase starts.
  • Do not hesitate to involve non-project team members or end-users to contribute to testing.

Case Studies

We will now discuss some real-life case studies in which I had the opportunity to either work directly or contribute indirectly.

Case Study #1

Task: Refactor a module to replace the hard-coded values with variables and add comments for the new automated technical documentation generation tool.

Challenges: No major challenges. The module is new and has documented functional and user acceptance requirements, user flows, and test cases. Unit tests were set up during the initial launch itself.

Test Approach: The test cases for the module were being refactored and the relatively known impacted areas were executed. All defects were reported and resolved prior to their release.

Case Study #2

Subject: Refactor existing stored procedures to facilitate application scale up.

The stored procedure in this scenario was an older stored procedure that was designed a few years ago, keeping in mind the requirement that the application was using its stored procedure as an internal application with less than 10 concurrent sessions.

Now the company wants to market this application as a Software as a Service (SaaS), with the expected volume of 300 concurrent sessions initially.

The team did some initial load tests and concluded that the system breaks with a load of 25 concurrent sessions. The team reviewed the code and recommended refactoring one existing core storage procedure that would allow the application to scale and support up to 500 concurrent sessions without breaking.

Some issues identified with this stored procedure were multiple subqueries within a single query, heavy joins with views instead of tables, the use of select * instead of selecting a specific column, etc.

Due to these coding issues, the application was fetching more data than that was really necessary, thereby causing the application to slow down and eventually crash.

Challenges:

1) Project Manager/Product Analyst

  • Requirement Gathering: Since this stored procedure was a legacy code, there were no documented requirements for it when the module was first designed. Also, for the iterations done over the last few years, there was no change log to indicate the business rules and logic added or removed from the module.
  • Project Schedule: Since the requirements were not clearly defined and code dependencies were not yet fully identified, it was difficult to communicate the tentative schedule.

2) For Developers

  • Lack of clear requirements and business rules.
  • Clear the code without losing its functionality.
  • Unknown impacted areas and/or code dependencies.
  • Unable to provide concrete development time estimates.
  • Need to create new Unit Tests.

3) For Testers

  • Lack of clear requirements and business rules impact test planning.
  • Unknown impacted areas impact test planning, specifically for regression tests.
  • Unable to provide concrete testing estimates.

4) Stakeholders

  • Lack of clearly documented requirements and/or user flows + tight deadlines = Higher risk of failure.

This approach is followed by the team to mitigate risks and work around the challenges:

(i) The team followed a collaborative approach to gather requirements: Project Manager/ Product Analyst and Testers worked closely with the internal end-users to understand and document the major functionality and user flow.

Developers also reviewed the code and added relevant information to the requirements document. This was done before the sprint start date.

(ii) An alternate test environment was created to test the changes being implemented: Let’s call these environments Gamma and Phi. Gamma will have the old code and Phi will have the latest refactored stored procedure deployed at all times.

Having 2 test environments in parallel, almost recreating the before–and–after approach, allowed the team to do more in-depth and exploratory testing by comparing the behavior in these 2 test environments, they were able to identify the probable bugs.

The team provided the requirements for an alternate test environment that was provided before the sprint start date.

(iii) End users and stakeholders involved in testing early on: All obvious issues were caught and reported early on, allowing more time for the team to deploy and test the required fix.

(iv) Defining the exit criteria and adhering to it: Exit criteria were defined in the initial planning stages – 80% user flows tested, no critical bug unresolved, demo and sign off from the stakeholders before release.

(v) Setting a tentative release date: Setting a release date aligned and motivating the team to work towards a common endpoint.

Based on the scope of the project, it was recommended by the team that a 3-week sprint be followed instead of a regular 2-week sprint to allow sufficient time for the team to execute the project.

Conclusion

To summarize, refactoring of code is a process to clean/simplify the design of a module without changing its functionality.

The refactoring process can be simple, like adding comments and correct indentation, removing a static variable, etc., or can be complicated for complex legacy systems.

A particular module or piece of code may need to be refactored due to code smells, technical debt, or by following an agile software development approach.

For testers, the refactoring of code roughly translates to = in-depth testing + regression testing.

In-depth testing is required to test all existing user flows to ensure that all functionalities are working as before. Regression testing of the entire application (or impacted areas) is required to make sure that upgrading a module did not unintentionally break the functionality of the other modules.

Test Leads / QA Leads may be required to work together with the rest of the team including developers, product analysts, especially for legacy projects. Be proactive while planning the test approach and test plans.

If you anticipate the requirement of multiple test environments or new test tools, then please send the requisition early on to prevent any delays once the testing phase starts.

About the Author: This informative tutorial was written by Neha B. She is currently working as a Quality Assurance Manager and specializes in leading and managing In-house and Offshore QA teams.

Have you worked on a challenging project which involved refactoring of code or a module/application? If yes, feel free to share your experiences in the comments section for our fellow testers to learn from. We would love to hear from you.

Was this helpful?

Thanks for your feedback!

Recommended Reading

16 thoughts on “Code Refactoring: What You Need to Know About It”

  1. I enjoyed reading this article. Simple and easy to understand. Refactoring helps the existing code to work better. So, it should not be avoided. You explained everything in simple language so that it will be better for others to understand. Keep updating these types of useful posts.

    Reply
  2. We were asked to look for the process improvements / Project improvements when the team had idle time. Very good topic to be kept in the first list of process improvement / performance. Thank you!

    Reply
  3. Hi there! Great content, thanks for sharing! I’d like to suggest another article related to the subject, maybe you’ll find it interesting 😉 How to Make the Best of your Code with Refactoring

    Reply
  4. code refactoring is indeed needed and must be followed. Code clean up are mostly avoided by developers but this article will help them understand the need for refraction. THANKS for sharing this amazing article.

    Reply
  5. a VERy GOOD AND CLEAR EXPLANATION OF THE cODE REFACTOR AND IT’S IMPORTANCE FOR ALL ROLES IN SOFTWARE CREATION PROCESS

    Reply

Leave a Comment