Skip to content
aipatterns.dev

Refactoring techniqueComposing Methods

Extract Variable

Name a hard-to-read expression or sub-expression by assigning it to a well-named local variable.

Also known as
Introduce Explaining Variable

Problem

A condition or calculation packs several ideas into one expression. You have to evaluate it in your head to learn what it checks.

Solution

Assign each meaningful part of the expression to a local variable whose name says what that part means. Then build the original expression from those names.

Before

TypeScript
function canBookRoom(user: User, room: Room, slot: Slot, now: Date): boolean {
  return (
    (user.role === "admin" || user.teamId === room.ownerTeamId) &&
    slot.start.getTime() - now.getTime() >= 15 * 60_000 &&
    slot.end.getTime() - slot.start.getTime() <= room.maxBookingMinutes * 60_000 &&
    !room.maintenanceWindows.some((w) => w.start < slot.end && slot.start < w.end)
  );
}

After

TypeScript
function canBookRoom(user: User, room: Room, slot: Slot, now: Date): boolean {
  const hasAccess = user.role === "admin" || user.teamId === room.ownerTeamId;
  const leadTimeMs = slot.start.getTime() - now.getTime();
  const bookedFarEnoughAhead = leadTimeMs >= 15 * 60_000;
  const durationMs = slot.end.getTime() - slot.start.getTime();
  const withinMaxDuration = durationMs <= room.maxBookingMinutes * 60_000;
  const clashesWithMaintenance = room.maintenanceWindows.some(
    (w) => w.start < slot.end && slot.start < w.end,
  );

  return hasAccess && bookedFarEnoughAhead && withinMaxDuration && !clashesWithMaintenance;
}

Why

  • The return line now reads as the business rule.
  • Debuggers and log statements can inspect each named part.
  • It is a cheap first step. If a name proves useful beyond this function, promote it to a method later.

Steps

  1. Pick a sub-expression that has a clear meaning and no side effects.
  2. Declare a const with a descriptive name and assign the sub-expression to it, placed before the first use.
  3. Replace the sub-expression with the variable. Run the tests.
  4. Repeat for other parts, working from the innermost meaningful pieces outward.

Watch out

  • Short-circuit evaluation changes. In the original, !room.maintenanceWindows.some(...) never ran if access failed. Hoisting it into a variable runs it every time. That is harmless for pure checks but wrong if a part throws, is expensive, or has side effects. Keep such parts inline or extract them as functions.
  • If the same name would help in other functions, prefer Extract Method or Replace Temp with Query so the logic is shared.
  • Don't name things that are already obvious (const sum = a + b).
  • The inverse is Inline Temp.

Agent checklist

  • Each extracted part has no side effects, or its evaluation order and count are unchanged.
  • Short-circuit guards that protected a later part (null checks, expensive calls) still protect it.
  • Variables are const and named for meaning, not for type.
  • Tests pass with identical results.

Treats these smells

See also

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