Refactoring
The process of improving a program’s internal structure in small steps without modifying its external behaviour.
Some benefits of refactoring are:
- hidden bugs become easier to spot
- improve performance
Types of Refactoring
Consolidating conditional expressions
If conditional expressions return the same value, they can be condensed.
Example
if (isNotOldEnough()) return 1; if (isNotTallEnough()) return 1; if (isPregnant()) return 1;
can be consolidated into
bool isAllowedToRideRollercoaster() { return isNotOldEnough() || isNotTallEnough() || isPregnant(); } return isAllowedToRideRollercoaster ? 1 : 0;
Decomposing conditionals
Conditionals can be decomposed into their own specific functions for easier reading.
Example
if (!date.isBefore(month.January) && !date.isAfter(month.February) ) { fee = quantity * item.offPeakPrice; } else { fee = quantity * item.peakPrice; }
can be decomposed into
isDuringOffPeak(date) { return !date.isBefore(month.January) && !date.isAfter(month.February); } calculateOffPeakPrice(quantity, item) { return quantity * item.offPeakPrice; } calculatePeakPrice(quantity, item) { return quantity * item.peakPrice; } if (isDuringOffPeak(date)) { return calculateOffPeakPrice(quantity, item) } else { return calculatePeakPrice(quantity, item); }
Inline method
Instead of using a separate method, use the implementation inside the helper method directly.
Example
getRating(driver) { return moreThanFiveLateDeliveries(driver) ? 2 : 1; } moreThanFiveLateDeliveries(driver) { return driver.numberOfLateDeliveries > 5; }
can be changed to
getRating(driver) { return driver.numberOfLateDeliveries > 5 ? 2 : 1; }
Remove double negative
Change double negatives to a single positive conditional.
!(item.isNotFound)
can be refactored toitem.isFound
Replace magic literal
Magic literals can be replaced with named constants.
3.14 * radius * radius
can be replaced withconst PI = 3.14; PI * radius * radius
Replace nested conditional with guard clauses
Some nested conditionals can be replaced with guard clauses, removing the nesting.
Example
getInsurancePayout() { if (isDead) { return deadAmount; } else { if (isMajorInjury) { return majorInjuryAmount; } else { if (isMinorInjury) { return minorInjuryAmount } else { return normalAmount } } } }
can be replaced with
getInsurancePayout() { if (isDead) return deadAmount; if (isMajorInjury) return majorInjuryAmount; if (isMinorInjury) return minorInjuryAmountl return normalAmount; }
Replace parameter with explicit methods
Replace the use of a parameter and instead use specific functions for each conditional case.
Example
setDimension(name, value) { if (name === "height") { this.height = value; } if (name === "width") { this.width = value; } }
can be refactored to:
setHeight(value) this.height = value; setWidth(value) this.width = value;
Reverse conditional
Reorder a conditional, to remove negatives from the conditional
Example
if (!order.exists) { order.create(items); } else { order.update(items); }
can be refactored into
if (order.exists) { order.update(items); } else { order.create(items); }
Split loops
Split loops to increase readability
Example
totalSalary = 0; averageAge = 0; for (p in people) { averageAge += p.age; totalSalary += p.salary; } averageAge = averageAge / people.length;
can be refactored to
totalSalary = 0; for (p in People) { totalSalary += p.salary; } averageAge = 0; for (p in People) { averageAge += p.age; } averageAge = averageAge / people.length;
Split temp
Split temporary variables, if they are used to mean different things;
Example
temp = 2 * height * width; setPerimeter(temp); temp = height * width; setArea(temp);
can be refactored to
perimeter = 2 * height * width; setPerimeter(perimeter); area = height * width; setArea(area);
When to refactor
Code smell
A surface indication that usually corresponds to a deeper problem in the system
A non exhaustive list of code smells:
- Data class
- A class with all data, and no behaviour
- Long method
- Method contains too many LoC (>30)
- Large class
- Class is too large
- Primitive obssession
- Overuse of primitives instead of objects
- Temporary field
- Some variables are mostly empty
- Shotgun surgery
- Making modifications require big changes to many classes
Periodic refactoring can be used to pay off technical debt accumulated in a codebase.
Too much refactoring is when benefits no longer justify the cost.