Skip to content
aipatterns.dev

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

TypeScript
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

TypeScript
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

  1. 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.
  2. Join sequential checks with ||, preserving their original order. For nested ifs with one outcome, join with &&. Run the tests.
  3. Extract the combined expression into a function with Extract Method. Run the tests.
  4. 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 against null.
  • 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.

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

Original writing and examples. Topic coverage follows Refactoring.Guru, which explains this topic with its own illustrations.