Refactoring techniqueSimplifying Conditional Expressions
Consolidate Conditional Expression
Merge a run of separate checks that all lead to the same result into one condition with a name that states the shared rule.
Problem
A function has several if statements in a row, and every one of them returns the same value or performs the same action. The checks look independent, so a reader cannot tell they express one rule, and a new check gets added in the wrong place.
Solution
Combine the checks into a single boolean expression with || (or && for nested checks), then extract that expression into a function named for the rule.
Before
function canIssueRefund(order: Order, now: Date): boolean {
if (order.paymentMethod === "gift-card") return false;
if (order.refundedAt !== null) return false;
if (now.getTime() - order.deliveredAt.getTime() > 30 * DAY_MS) return false;
if (order.items.every((item) => item.finalSale)) return false;
return order.total > 0;
}After
function canIssueRefund(order: Order, now: Date): boolean {
if (isRefundIneligible(order, now)) return false;
return order.total > 0;
}
function isRefundIneligible(order: Order, now: Date): boolean {
const outsideWindow = now.getTime() - order.deliveredAt.getTime() > 30 * DAY_MS;
return (
order.paymentMethod === "gift-card" ||
order.refundedAt !== null ||
outsideWindow ||
order.items.every((item) => item.finalSale)
);
}Why
- The list of disqualifiers now lives in one place with one name, so adding a fifth is obvious.
- The calling function reads as policy: ineligible orders get
false. - The consolidated predicate is a natural unit to test with a table of cases.
Steps
- Confirm every check in the run has no side effects and produces the identical outcome. If any check logs, mutates or returns a different value, stop.
- Join sequential checks with
||, preserving their original order. For nestedifs with one outcome, join with&&. Run the tests. - Extract the combined expression into a function with Extract Method. Run the tests.
- Rename the function until it describes the rule without restating the clauses.
Watch out
- Do not consolidate checks that are truly independent concerns that happen to share a return value today, such as a permissions check and a validation check. They will diverge, and you will split them again.
- Keep clause order.
||short-circuits left to right, and a later clause may depend on an earlier one guarding againstnull. - If one check needs its own error message or log line, it is not the same outcome. Leave it separate.
- The rough inverse is Decompose Conditional applied to a single large condition; use both together, consolidate first, then name.
Related
- Fixes Duplicate Code in the form of repeated outcomes, and shortens Long Method.
- Often followed by Decompose Conditional or Replace Nested Conditional with Guard Clauses.
Agent checklist
- Every merged check returns or does exactly the same thing.
- No merged clause has side effects.
- Clause order is preserved so null guards still protect later clauses.
- The extracted predicate's name states the rule, not the list of checks.
Treats these smells
See also
- Decompose ConditionalMove a complex condition and each of its branches into well-named functions so the decision reads as intent instead of arithmetic.
- Replace Nested Conditional with Guard ClausesHandle special and invalid cases first with early returns so the normal path runs flat at the end of the function.
- Extract MethodMove a coherent fragment of a long function into its own well-named function and call it from the original place.
Original writing and examples. Topic coverage follows Refactoring.Guru, which explains this topic with its own illustrations.