Open In App

Refactoring in Agile

Last Updated : 01 Apr, 2022
Improve
Improve
Like Article
Like
Save
Share
Report

Refactoring is the practice of continuously improving the design of existing code, without changing the fundamental behavior. In Agile, teams maintain and enhance their code on an incremental basis from Sprint to Sprint. If code is not refactored in an Agile project, it will result in poor code quality, such as unhealthy dependencies between classes or packages, improper class responsibility allocation, too many responsibilities per method or class, duplicate code, and a variety of other types of confusion and clutter. Refactoring helps to remove this chaos and simplifies the unclear and complex code. Following are the definitions quoted by various experts in Agile on Refactoring concepts:  

A refactoring is a “behavior-preserving transformation” – Joshua Kerievsky  

Refactoring is “a change made to the internal structure of software to make it easier to understand and cheaper to modify without changing its observable behavior” – Martin Fowler  

It is best to refactor continuously, rather than in phases. Refactoring continuously prevents the code from getting complicated and helps to keep the code clean and easy to maintain. In Agile development, there can be short and separate sprints to accommodate refactoring. This module provides an overview of the technical/engineering practice of Refactoring which would be implemented in Agile projects.

Challenges:

Though refactoring brings a lot of benefits to the code quality of the software, there are multiple challenges that dissuade developers in Agile projects from continuously refactoring the code. Following are a few challenges that are mostly seen in Agile projects  

  • Time Constraint: Time is the biggest challenge for doing refactoring in Agile projects as the Sprints are time-boxed with a defined set of deliverables.
  • Reluctance: If the code is working fine without any refactoring done, there will be an inclination towards not revisiting the code. This is primarily because of the mindset that there is no error and hence no need to do additional activities i.e. refactoring.
  • Integration with branches: To integrate the code across different branches post refactoring is considered a challenge
  • Fear factor: Developer often fears that refactoring will introduce bugs and break the existing functionality which is working fine
  • Re-Testing: In case automated test suites are not available, the developer is discouraged to do refactoring with the additional effort of manual testing to check the functionality
  • Backward Compatibility: Backward compatibility often prevents developers from starting refactoring efforts.

Motivation For Refactoring:

The following points for motivation are more common among the developers while they do refactoring  

  • It becomes easier to add new code
  • The existing code’s design is improved.
  • Helps to gain a better understanding of code
  • Makes coding less annoying

Advantages of Refactoring:

Refactoring in small steps helps to prevent defects from being introduced. The following points can be further perceived from the benefits of implementing Refactoring.  

  • Improves software extendibility
  • Reduce the expense of code maintenance.
  • Provides standardized code
  • Architecture improvement without impacting software behavior
  • Provides more readable and modular code
  • Modular component refactored to maximize possible reusability

Design Guidelines for Agile Projects:  

In a traditional software development project, requirements and plans are set before development begins, which enables the team to know the road ahead with reasonable confidence that requirements or design will not change drastically in the middle whereas the Agile method is to enable and embrace change. On an Agile project, requirements can change at any point in the project cycle. Therefore, it is imperative for Agile teams to have a code in a position where they can conveniently accept a new requirement or change.  

Good design is an important principle of Agile manifesto projects: “Continuous attention to technical excellence and good design enhances agility”.

Precautions for Refactoring:

  • Too much pattern focus takes away the focus from writing small, simple, and understandable code.
  • Look at patterns from a refactoring perspective and not just as reusable elements.

How to Check Faculty Code?

‘Code Smell’ is the phrase coined by Kent Back and Martin Fowler. Some of the most important “smells in code” are:

S.No. Smell Description
1. Duplicated code Identical or very similar code exists in more than one location
2. Long Method A method/function/procedure that has grown too large
3. Long Class A class that has grown too large
4. Too Many Parameters A long list of parameters is hard to read and makes calling and testing the function complicated
5. Feature envy A class that uses methods of another class excessively
6. Lazy Class A class that does too little
7. Contrived complexity Forced usage of overly complicated design patterns where the simpler design would suffice.
8. Complex to debug Code has become complex enough to debug

Guidelines for Refactoring:

  • Make sure the code is working before you start.
  • Ensure that an automated test suite is available and provides good coverage.
  • Run the tests frequently before, during, and after each refactoring.
  • Before each refactoring, use a version control tool and save a checkpoint. Not only does this mean recovery can be done quickly from disasters, but it also means refactoring can be tried out and then back out if unsatisfied with the refactored code.
  • Break each refactoring down into smaller units.
  • Finally, if a refactoring tool is available in your environment, utilize it.

Implementation of Refactoring Techniques:  

There are multiple refactoring techniques available that will make the existing code better in terms of performance and maintainability. The basic purpose of refactoring in Agile or any other methodology is “Leave a module in a better state than you found it”  

The Refactoring techniques are categorized by Martin Fowler as follows:  

  • Composing methods
  • Moving features within Objects
  • Organizing data
  • Simplifying Conditional Expression
  • Making method calls simpler

Every category has certain refactoring techniques. Few techniques are explained below.  

1. Composing Methods: It deals with the proper packaging of method or function code. Large methods/functions having very long complex logic implementation can reduce the maintainability and readability of the code. Applying refactoring techniques like extract methods will help make the code more maintainable and readable. A few of the refactoring techniques in composing method/function are detailed below:  

  • Extract Method
  • Inline Method
  • Replace method with method object

Extract Method: This is one of the popularly used Refactoring techniques. The implementation behind this technique is very simple. It consists of breaking up long methods by shifting complex chunks of code into new methods which have very descriptive identifiers. This method is used when: 

  • The code consists of long methods covering multiple logical flows that can be broken up into smaller methods
  • If the same code is duplicated in more than one flow, move this to a single method and call from all other flows

Example:

Before Refactoring: 
void printStudentRecord() {

   printSchoolName();

   // print details  
   System.out.println("Id: " + _id);

   System.out.println("name " + _name);

}
After Refactoring:
void printStudentRecord() {
   printSchoolName();
   printStudentDetails();
}
void printStudentDetails() {
   // print details  
   System.out.println("Id: " + _id);
   System.out.println("name " + _name);
}

The advantages of this method are:  

  • Proper code reorganization by grouping a set of statements into the single smaller method
  • Reduces code duplication
  • Increases the readability of the code

Inline method: In contrast to the Extract Method technique of refactoring, Inline method techniques suggests replacing a method/function call with the body of the method/function. It is suggested when the source code of the method/function is very small. This method is used when:  

  • When a function call causes a performance bottleneck
  • When inline code increases the readability of the code since the body of the function is the same as the function name
  • When there are too many delegations in the code

Example:

Before Refactoring 
int getRating() {

   return (moreThanFiveLateDeliveries()) ? 2 : 1;

}

boolean moreThanFiveLateDeliveries() {

   return _numberOfLateDeliveries > 5;

}

After Refactoring 
int getRating() {

   return (_numberOfLateDeliveries > 5) ? 2 : 1;

}

The advantages of this method are:  

  • Unnecessary complexity reduced, reduction in method calling overhead
  • Increases the readability of the code

Replace method with method object: The Thumb rule of good code is that, it should be readable and easily manageable. At times we might come across a very long method having complicated logic implementation done inside it with many local variables. Such a method cannot be simplified by applying the extract method technique because of the usage of too many local variables, because passing around that many variables would be just as messy as the long method itself. In such cases, a class can be created for such a method. This class will have all local variables of that long method as its data members. One of the methods will be the long method. The complicated logic of this method can easily be simplified by applying the extract methods technique.  

Example:

Before Refactoring
class Employee {

   private:

       // some declarations  
       double CalSalary(float fVCPI, float fCPI, float fPPF) {

           double dIncomeTax;

           double dHRA;

           double dLTA;

           // complicated salary calculations  

       }

};
After Refactoring 
class SalaryCalculator {
   private: float m_fVCPI;

   float m_fCPI;

   float m_fPPF;

   double m_dIncomeTax;

   double m_dHRA;

   double m_dLTA;

   public:

       SalaryCalculator(float fVCPIpercent, float fCPIpercent, float 
       fPF, double dIncomeTaxPercent, double dHRA, double dLTA) {

           // initialize private data members  
       }

   double CalculateSal() {

       // complicated salary calculations  
   } };

class Employee {

   private:

       // some declarations  
       public:

       double CalSalary() {

           SalaryCalculator obj(14, 13, 200.43, dITPercent, dHRA, dLTA);

           return obj.CalculateSal();

       }};

2. Moving Features Within Objects:

One of the most important and critical parts of object design is deciding on where to assign the responsibilities. There are many options. Refactoring techniques can help us decide the same. Following are a few of the refactoring techniques which can be used to decide the proper responsibility allocation:  

  • Move Method: Consider a scenario for performing a functionality. A method is using members (data or functions) of another class i.e. Class-2 multiple times, as compared to the one in which it is written i.e. Class-1. Then such a code can be refactored using the Move Method technique. According to this technique in such cases, that method can be moved to Class-2. Class-1 can call this method in order to complete the needful functionality and the method can be removed from Class-1 altogether.  Move method technique can also be applied when a method is used a maximum number of times by one of the classes in which it is declared and defined.
Move Method Refactoring Technique

 

  • Move field: If a member of one class is used a maximum number of times by access methods from some other class, then such field can be moved to another class itself. This needs necessary modifications in the earlier class which was having that data member in it. Move method techniques will also be applied when new classes are identified during the development phase.
Move Field Refactoring Technique

 

  • Hide delegate: One of the important features of object-oriented design is if changes are made in one of the classes, then no other classes or very few classes should have an impact on its code because of this change. If one class is sending its requests to another class then such a class can be referred to as a client class. The class which serves the sent requests can be referred to as the server class. Consider a function that has to get an object of class B by calling one of the methods of class A. This function has to also call a method of class B to get the required data. This means that function has to interact with class A first and then with class B. In this case, the internal structure of class B is partially revealed to that function. The same can be avoided if class A has a method that itself will get the necessary things from class B and send the same to the client function. This delegation part can be made hidden from the client function. The below diagram shows a pictorial presentation where the ‘Department’ Class is hidden from ‘Client’, as the call to ‘Department’ Class is moved to the ‘Person’ class method.
Hide Delegate Refactoring Technique

 

3. Organizing Data: 

The following section illustrates a few techniques that would make data organization better. This also makes working with data easier. Replace array with Object: One of the most common data structures used for data organization is an array. The array is a collection of homogeneous data in contiguous locations of memory having the same name. Although homogeneous, the same array can contain data with a different meaning causing confusion while accessing this data structure. In such cases, it is good to replace the array with an object. 

4. Simplifying Conditional Expressions: 

Understanding conditional logic implemented in a program can be one of the challenging tasks. Using refactoring techniques, the conditional logic can be simplified so that the code can be understood easily. This section has a few refactoring techniques using which conditional logic can be simplified easily.  

5. Making method calls simpler: 

Objects can interact with each other and the outside world through public methods. These methods should be easy to understand then the entire object-oriented design will become easy to understand. In this section, we will discuss a few refactoring techniques which will make the methods calls easier.  

Refactoring in Test Driven Development:

Test-Driven Development (TDD) is a software engineering approach that consists of writing failing test case(s) first covering functionality. And then implementing the necessary code to pass the tests and finally refactor the code without changing external behavior.  

TDD deals with 2 types of tests i.e.  

  • Unit tests: used to verify the functionality of a single class that is separated from its environment.
  • Acceptance tests: used to check a single functionality

On similar lines, Refactoring can also be categorized into different types.  

  • Refactoring at local class level:  The unit tests for this class are unchanged and the tests are still green. If the Unit tests are designed to check complete scenarios instead of single-member calls, the refactoring falls into this category.  
  • Refactoring impacting multiple classes:  In this case, Acceptance tests are the only way to check complete functionality after refactoring. This is useful if a new way of functionality is provided, and implemented by changing class after class and associated unit tests. An important aspect to remember for refactoring in TDD is when code is refactored impacting interface/API, all the callers of this interface/API along with Tests written for the interface/API need to be changed as part of refactoring. In congruence with the definition, refactoring is a “behavior-preserving” change.


Like Article
Suggest improvement
Previous
Next
Share your thoughts in the comments

Similar Reads