paint-brush
The 6 Types of Code Refactoring That Every Programmer Should Knowby@hackercltvodz2u0000356n4zptwviq
280 reads

The 6 Types of Code Refactoring That Every Programmer Should Know

by Mehmet Ali EmektarApril 25th, 2024
Read on Terminal Reader
Read this story w/o Javascript
tldt arrow

Too Long; Didn't Read

Refactoring is the process of changing the current codes of software to make it easier to understand and maintain without modifying its internal functionality.
featured image - The 6 Types of Code Refactoring That Every Programmer Should Know
Mehmet Ali Emektar HackerNoon profile picture

Refactoring is the process of changing the current codes of software to make it easier to understand and maintain without modifying its internal functionality.


Repeating code that does the same thing leads to poor design. By reducing these repeating codes to one, you make certain that aimed work is done only in one method. In this way, the readability of the code also increases.


It increases the development pace because it prevents system design delays. As you get rid consequences of poor design with the help of refactoring, it makes the current design better.


Reading and understanding codes become easier by refactoring. This leads to recognizing the bugs easily.


Changing current code or adding new features takes a longer time while making an effort to figure out the logic and scan for bugs. Because delays in development is gotten rid of with refactoring, the development takes shorter time rather than it is in the previous situation.


Refactoring makes it easier to figure out logic of the code. Then, you can add a feature rapidly and with an ease.


If you are required to fix a bug and codes are clear enough to find the bug, it is an appropriate time to refactor.


Code review enables to pass knowledge through a team. In this way, less experienced ones can learn more about structure of the system. It also leads to clear coding. Reviewer and person whose codes are reviewed can make a refactoring decision at time of review and do so.


Refactoring brings along a comprehensive design, enabling development easier. Because It leads to a simpler structure, it becomes easier to figure out over-all logic and takes less time to make development. And, this provides you with time to take care performance.


Codes are separated to parts according to single responsibility. In this way, complexity is eliminated, leading to understand easily. And, it offers you additional time to adjust the codes for a better performance.


Here is some samples of Refactoring in different areas.


Extracting Method

If a method is long or its purpose is explained with comments, then that code is carried into a new method. Method extracting makes it possible to reuse method. If method name explains method body clearly, it doesn't matter whether it is long.


Pattern(Each step is told with a number on the example below.)

  1. Define a new method, and name it by what it does
  2. Move the extracted code from the source method into the new target method
  3. Analyze whether local-scope variables are affected by extraction. If a variable is affected, consider whether you can assign the result of the extracted code to the variable.
  4. Send local-scope variables from extracted code into the target method as parameters
  5. Change extracted code to call to the new method


bool ValidateTransactionBeforeSave(){
  if(IsNullOrEmpty(document.getElementById("CustomerNumber"))){ → (5)
	alert("CustomerNumberRequiredMessage"); → (3)
	return false;
  }
  .
  .
  .	→ Assume that if block above is repeated for a transactions' header fields. (2)

  if(IsNullOrEmpty(document.getElementById("PaymentMaturityStartDate"))){ → (5)
	alert("PaymentMaturityStartDateRequiredMessage"); → (3)
	return false;
  }
  .
  .
  .	→ Assume that if block for PaymentMaturityStartDate is repeated for the transactions' other payment details on defined dates. (2)

  return true;
}


After extracting the method above;

bool ValidateTransactionBeforeSave(){
  Result headerResult = ValidateTransactionHeader();	
  if(!headerResult.IsValid){
	alert(headerResult.Message); → (3)
	return false;
  }

  Result detailsResult = ValidateTransactionDetails();
  if(!detailsResult.IsValid){
	alert(detailsResult.Message); → (3)
	return false;
  }
  
  return true;
}

Result ValidateTransactionHeader(){	→ (1)
	Result headerValidationResult = new Result();

	if(IsNullOrEmpty(document.getElementById("CustomerNumber"))){
		headerValidationResult.Message = "CustomerNumberRequiredMessage";
		headerValidationResult.Result = false;
	}
    .
	.
	. → Here, validation is repeated for CustomerName, BranchCode, BranchName, DealerCode, DealerName, TranDate, MaturityDate, TransactionAmount, TransactionUnit.  

	return  headerValidationResult;
}

Result ValidateTransactionDetails(){	→ (1)
	Result detailValidationResult = new Result();

	if(IsNullOrEmpty(document.getElementById("PaymentMaturityStartDate"))){
		detailValidationResult.Message = "PaymentMaturityStartDateRequiredMessage";
		detailValidationResult.Result = false;
	}

	.
	.
	. → Here, validation is repeated for PaymentMaturityEndDate, PaymentSettlementDate, PaymentTransactionAmount, PaymentTransactionUnit.
	
	return detailValidationResult;
}


Moving Method

It is time to move methods when classes are highly coupled and have too much responsibilities. If a method interacts with another object than the object it lives on, it is a leading criteria to change.


Pattern(3. 4. and 7. steps are told with a number on the example below.)

  1. Scan other methods using method you consider to move
  2. Look through for methods performing the same work with the method in the sub and super classes
  3. Create method in target class
  4. Move the code to the target class
  5. Make it work in target class by providing with necessary pieces
  6. Replace the source code by delegating to the new method
  7. Replace references of old method with ones to the target method


class Transaction {
	private TransactionType transactionType;
	private double assetAmount;
	private double assetUnit;
	private double secondAssetAmount;
	private double secondAssetUnit;
	private double parity;

	double CalculateTransactionAmount(){ → (4)
		double transactionAmount = 0;

		if(transactionType.IsType1()){
			transactionAmount = (assetAmount * assetUnit * parity) + (secondAssetAmount * secondAssetUnit * parity); 
		}
		else {
			transactionAmount = assetAmount * assetUnit * parity;
		}

		return transactionAmount;
	}
}


After extraction;

class TransactionType {

	double CalculateTransactionAmount(Transaction transaction){ → (3)
		double transactionAmount = 0;

		if(IsType1()){
			transactionAmount = transaction.getAssetAmount() * 				
						 transaction.getAssetUnit() * 				
						 transaction.getParity() + 
						 transaction.getSecondAssetAmount() * 
						 transaction.getSecondAssetUnit() * 
                         transaction.getParity();
		}
		else {
			transactionAmount = transaction.getAssetAmount() * transaction.getAssetUnit() * transaction.getParity();
		}

		return  transactionAmount;
	}
}

class Transaction {
	private TransactionType transactionType;
	private double assetAmount;
	private double assetUnit;
	private double secondAssetAmount;
	private double secondAssetUnit;
	private double parity;

	public double getAssetAmount(){ return assetAmount; }
	public double getAssetUnit(){ return assetUnit; }
	public double getSecondAssetAmount(){ return secondAssetAmount; }
	public double getSecondAssetUnit(){ return secondAssetUnit; }
	public double getParity(){ return parity; }
	public double getTransactionAmount(){ 
		return transactionType.CalculateTransactionAmount(); → (6)
	}
}


Extracting Class

If a class has many methods and a lot of data, you are required to take into account how to shorten it. A good clue is that a set of data and methods work together and are depend on each other.


Pattern

  1. Separate the class by extracting variables and methods you thing they don't belong the class
  2. Define a new class
  3. Reference to the new class from the old one
  4. Move fields and methods you determined to the new class


class Transaction{
	private DateTime tranDate;
	private double transactionAmount;
	private string transactionUnit;
	private int customerNumber;
	private string customerName;
	private string customerType;
	private string customerSwiftCode;

	public DateTime getTranDate(){ return tranDate; }
	public double getTransactionAmount(){ return transactionAmount; }
	public string getTransactionUnit(){ return transactionUnit; }

	public int getCustomerNumber(){ return customerNumber; } → (4)
	public string getCustomerName(){ return customerName; } → (4)
	public string getCustomerType(){ return customerType; } → (4)	
	public string getCustomerSwiftCode(){ return customerSwiftCode; } → (4)
	
	.
	.
	.
}


After extraction;

class Customer{	→ (2)
	private int customerNumber;
	private string customerName;
	private string customerType;
	private string customerSwiftCode;

	public int getCustomerNumber(){ return customerNumber; }
	public string getCustomerName(){ return customerName; }
	public string getCustomerType(){ return customerType; }	
	public string getCustomerSwiftCode(){ return customerSwiftCode; }

	.
	.
	.
}

class Transaction{
	private DateTime tranDate;
	private double transactionAmount;
	private string transactionUnit;

	private Customer customer = new Customer();	→ (3)

	private int getCustomerNumber { return customer.getCustomerNumber(); }
	private string getCustomerName { return customer.getCustomerName(); }
	private string getCustomerType { return customer.getCustomerType(); }	
	private string getCustomerSwiftCode { return customer.getCustomerSwiftCode(); }
}


Using Array instead of Object

Despite that arrays should keep only objects of the same type, they are used to strore different objects. It may be sometimes difficult to find out which element on the array represent what meaning. If you use an object instead of array, fields and methods carry purpose of information.


Pattern

  1. Generate a class to keep information in the array
  2. Generate a public property in the class for each item of the array
  3. Assign each array element to related property on the class
  4. After successful build, delete the array


object[] emailInfos = new object[6];			→ (4)
emailInfos[0] = "[email protected]";
emailInfos[1] = "[email protected]";
emailInfos[2] = "[email protected]";
emailInfos[3] = "Mail Subject";
emailInfos[4] = "Mail Content";
emailInfos[5] = new byte[Int64.MaxValue];	//Mail attachment


After extraction;

public class EmailInformation {	→ (1)
	public string FromEmailAddress { get; set; }	→ (2)
	public string CcEmailAddress { get; set; }	    → (2)
	public string ToEmailAddress { get; set; }	    → (2)
	public string Subject { get; set; }		        → (2)
	public string Content { get; set; }		        → (2)
	public byte[] Attachment { get; set; }		    → (2)
}

EmailInformation emailInfos = new EmailInformation();		
emailInfos.FromEmailAddress = "[email protected]";	→ (3)
emailInfos.CcEmailAddress = "[email protected]";		→ (3)
emailInfos.ToEmailAddress = "[email protected]";		→ (3)
emailInfos.Subject = "Mail Subject";					        → (3)
emailInfos.Content = "Mail Content";					        → (3)
emailInfos.Attachment = new byte[Int64.MaxValue];			    → (3)


Generating Conditions

Complex condition is one of complexity concepts in a software. Blocks of codes become longer as count of conditions in those blocks increases. Therefore, reading these codes gets harder.

Any of those blocks can be shortened and made clearer by generating a method indicating the purpose of that block and then inserting that block's code into this new method. With the help of this method further codes in the method can be decomposed depending on what you are branching.


Pattern

  1. Create a new method and move condition into it
  2. Create a method for each branch in the method


if(loanType == LoanTypes.Mortgage){
	monthlyPayment = loanAmount * [(6.25 * (1 + 6.25) ^ 30 * 12) ]
		/ [(1 + 6.25) ^ 30 * 12 - 1]
}
else if(loanType == LoanTypes.Vehicle){
	monthlyPayment = loanAmount * [(5.64 * (1 + 5.64) ^ termsOfNMonths) ]
		/ [(1 + 5.64) ^ termsOfNMonths - 1]
}


After extracting;

class LoanParameters {
	public double LoanAmount {get; set;} 
	public double InterestRate {get; set;}
	public int TermsOfNMonths	{get; set;}
}

double CalculateLoanMonthlyPayment(LoanParameters parameters){	→ (1)
	double monthlyPayment = parameters.LoanAmount * [(parameters.InterestRate * (1 + parameters.InterestRate) ^ parameters.TermsOfNMonths)]
			/ [(1 + parameters.InterestRate) ^ parameters.TermsOfNMonths – 1]

	return monthlyPayment;
}

if(loanType == LoanTypes.Mortgage){
	monthlyPayment = CalculateLoanMonthlyPayment(new LoanParameters(){ LoanAmount = loanAmount, 
  									                                   InterestRate = 6.25, 
									                                   TermsOfNMonths = 30 * 12}); → (2)
}
else if(loanType == LoanTypes.Vehicle){
	monthlyPayment = CalculateLoanMonthlyPayment(new LoanParameters(){ LoanAmount = loanAmount, 
								                                       InterestRate = 5.64, 
								                                       TermsOfNMonths = termsOfNMonths}); → (2)
}


Replacing Condition Branches with Methods

With parameterized method you are required to write block of codes for a numerous parameters. Preparing blocks of codes for each parameter is get ridden of by replacing parameter with explicit methods.


It is easier to read than an explicit method than a parameterized method. In a parameterized method, any developer needs to look through conditions to understand over-all logic.

It is better not to replace parameter with explicit methods if the parameter has probability to have more values.


Pattern

  1. Generate a method for each parameter
  2. Alter each condition branch with calls to new methods


Transaction _transaction = null;
Document _transactionDocument = null;

void initialize (objects[] args) { 
	if(args != null && args.Length > 0){
		if(args[0] is Transaction){
			_transaction = args[0];
		}
		else if(args[1] is Document){
			_transactionDocument = args[1];
		}
	}
}


After extracting;

void setTransaction(object transaction){  → (1)
	_transaction = transaction;
}

void setTransactionDocument(object document){  → (1)
	_transactionDocument = document;
}

void initialize (objects[] args) { 
	if(args != null && args.Length > 0){
		if(args[0] is Transaction){
			setTransaction(args[0]);    → (2)
		}
		else if(args[1] is Document){
			setTransactionDocument(args[1]);    → (2)
		}
	}
}



References

  • Refactoring: Improving the Design of Existing Code by Martin Fowler, Kent Beck (Contributor), John Brant (Contributor), William Opdyke, don Roberts