Refactoring techniqueSimplifying Conditional Expressions
Consolidate Duplicate Conditional Fragments
Move code that runs identically in every branch of a conditional to before or after the conditional so each branch holds only what differs.
Problem
Every branch of an if/else or switch ends (or starts) with the same statements. The duplication hides what actually varies between branches, and a fix applied to one copy is easily forgotten in the others.
Solution
Hoist the shared statements out of the conditional: leading duplicates go before it, trailing duplicates go after it. Each branch keeps only its unique work.
Before
async function reserveStock(sku: string, qty: number, warehouse: Warehouse): Promise<void> {
const item = await warehouse.find(sku);
if (item.onHand >= qty) {
item.onHand -= qty;
item.reserved += qty;
await warehouse.save(item);
audit.log("reserve", sku, qty);
} else if (item.backorderable) {
item.backordered += qty - item.onHand;
item.reserved += item.onHand;
item.onHand = 0;
await warehouse.save(item);
audit.log("reserve", sku, qty);
} else {
throw new OutOfStockError(sku);
}
}After
async function reserveStock(sku: string, qty: number, warehouse: Warehouse): Promise<void> {
const item = await warehouse.find(sku);
if (item.onHand >= qty) {
item.onHand -= qty;
item.reserved += qty;
} else if (item.backorderable) {
item.backordered += qty - item.onHand;
item.reserved += item.onHand;
item.onHand = 0;
} else {
throw new OutOfStockError(sku);
}
await warehouse.save(item);
audit.log("reserve", sku, qty);
}Why
- The save and the audit line exist once, so they cannot drift apart.
- Each branch now shows only the stock arithmetic that differs.
- The function's shape (decide, then persist) is visible.
Steps
- Find statements that are textually and semantically identical at the start or end of every branch, including any
elseanddefault. - Check that a branch which exits early (by
throw,return,continue) does not receive code it previously skipped. Here thethrowbranch never saved, and after the move it still never reaches the save. - Move trailing duplicates below the conditional, or leading duplicates above it. Run the tests.
- If the duplicate sits in the middle of each branch, check whether the statements around it can be reordered safely. If not, extract it into a function instead.
- For duplicates inside
tryandcatch, move them tofinallyonly if they must also run on failure.
Watch out
- Do not hoist code past a branch that exits early unless that branch also ran it before.
- Leading duplicates moved above the conditional run before the condition is evaluated. If the condition reads state those statements change, the result changes.
- Statements that look identical may reference variables assigned differently per branch. Compare the values, not only the text.
- If only some branches share the fragment, extract a function rather than restructuring the conditional.
Related
- Fixes Duplicate Code inside conditionals.
- Combine with Consolidate Conditional Expression when the branches also share a condition.
- Use Extract Method for a shared fragment that cannot move.
Agent checklist
- The moved code appeared in every branch, including the
else/default. - No branch that previously exited early now executes the moved code.
- Moved leading code does not affect the condition it now precedes.
- Tests cover each branch, and they pass after the move.
Treats these smells
See also
- Consolidate Conditional ExpressionMerge a run of separate checks that all lead to the same result into one condition with a name that states the shared rule.
- Extract MethodMove a coherent fragment of a long function into its own well-named function and call it from the original place.
- Decompose ConditionalMove a complex condition and each of its branches into well-named functions so the decision reads as intent instead of arithmetic.
Original writing and examples. Topic coverage follows Refactoring.Guru, which explains this topic with its own illustrations.