Recent chats
ASKGEEZ Q&A GUIDETechnology & devices

How can I refactor repeated function code without changing its behavior?

Quick answer

Extract repeated code only when the duplicated parts truly do the same job. First record what the current code returns for ordinary and boundary inputs. Then move the shared logic into a small helper, replace one copy at a time, and rerun the same checks. Keep differences in inputs or side effects outside the helper, and compare the refactored output with the original before removing the old implementation.

LET’S WORK THROUGH IT

The question, the details, the next step.

Question

I have the same few lines in two functions. What should I check before turning them into a helper?

Geez

Check whether the lines have the same meaning, not just whether they look alike. Compare their inputs, output, side effects, and the conditions under which they run. If one copy trims a value but the other preserves spaces, or one runs only after a permission check, they may not be interchangeable.

For a small example, assume both functions receive a string and currently turn blank or whitespace-only names into "Guest". This example does not define behavior for non-string values. Before changing anything, record a few representative inputs and the result each copy currently produces. Include ordinary text, an empty string, and text made only of spaces. That gives you something concrete to compare after the refactor.

Follow-up question

Can you show the change without making the example depend on a particular test library?

Geez

Suppose these two functions contain identical name handling. Before, the relevant lines might look like this:

function greeting(name) {
  const label = name.trim() || "Guest";
  return `Hello, ${label}`;
}

function welcome(name) {
  const label = name.trim() || "Guest";
  return `Welcome, ${label}`;
}

Extract just the shared decision, leaving each function's different wording alone:

function displayName(name) {
  return name.trim() || "Guest";
}

function greeting(name) {
  return `Hello, ${displayName(name)}`;
}

function welcome(name) {
  return `Welcome, ${displayName(name)}`;
}

The helper returns a value; it does not print, mutate an object, or choose the greeting. That keeps its job narrow. This is a teaching example, not proof that a particular codebase handles these inputs this way. If your original functions behave differently, preserve those differences rather than copying this example literally.

Follow-up question

How do I check that I did not accidentally change what callers see?

Geez

Run the same inputs through the old and new versions and compare the complete results callers rely on. For this example, simple checks can be written without a test framework:

function assertEqual(actual, expected) {
  if (actual !== expected) {
    throw new Error(`Expected ${expected}, got ${actual}`);
  }
}

assertEqual(greeting("Mina"), "Hello, Mina");
assertEqual(greeting(""), "Hello, Guest");
assertEqual(greeting("   "), "Hello, Guest");
assertEqual(welcome("Mina"), "Welcome, Mina");

These checks cover a few inputs under the stated string-input assumption; they do not establish behavior for every possible input or test the rest of an application. If the old implementation is still available, run both versions against the same chosen inputs and compare their outputs. Also check observable side effects or error behavior that callers depend on: comparing return values alone will not catch those.

Follow-up question

What is a safe order for making the change, especially if the two copies are almost—but not quite—identical?

Geez

Make the smallest change that preserves the distinction. First add or record checks for each copy's existing behavior, including the case that differs. Then extract only the identical part. If the difference is an intentional option, pass that option explicitly to the helper only when it makes the helper clearer; otherwise, keep the separate logic. Replace one call site, compare results, then replace the next and compare again. If a check fails, undo the last small change and inspect the difference rather than adding a special case blindly.

For example, if one function currently keeps whitespace and the other trims it, do not force both through a helper that always trims. You could leave the differing transformation in each function and extract a genuinely common step, or confirm the intended behavior with the relevant project owner before changing it. The refactor itself cannot tell you which behavior is intended.

A practical checklist

  • Confirm the repeated code has the same inputs, output, side effects, and conditions.

  • Record current results for ordinary, empty, boundary, and differing-case inputs.

  • Extract the smallest shared operation into a clearly named helper.

  • Keep intentional differences and caller-specific work outside the helper.

  • Replace one call site at a time and compare the same checks after each change.

  • Check relevant side effects and error behavior, not just returned values.

A few more useful answers

Should I extract code that appears only twice?

Not automatically. Extract it when the duplicated behavior is genuinely the same and a helper makes the code easier to understand or maintain; otherwise, two clear copies may be safer.

What if the helper needs several options to support both copies?

Pause and check whether you are combining distinct behaviors. A helper with many switches can hide the original differences; keep separate logic or extract only the smaller shared portion if that reads more clearly.

Do matching output checks prove the refactor is behavior-preserving?

They provide evidence for the inputs and observable behavior you checked, not every possible case. Add checks for relevant edge cases, side effects, and errors, and use the checks available in your project.

YOUR QUESTION. YOUR DETAILS.

Let’s work through your situation.

Get a tailored answer or help with your next task. Starts at $1, with a quote before you pay.

Ask your own question
← Back to Geez at Work