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.)
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.)
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
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
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
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
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