top of page

Extract Function — Refactoring Patterns, Part 2

  • Contributor
  • 5 hours ago
  • 3 min read
Refactoring Patterns · Part 2

Extract function is the bread-and-butter refactoring. When a block does something nameable, give it a name.

Step 1: Spot the Opportunity (10 min)

Signs to extract:

  • Comment above a block of code explaining what it does

  • A repeated block

  • Block of 5+ lines doing one logical thing

  • A complex expression nested in a larger function

The comment is often the function name.

Step 2: Extract a Named Block (10 min)

# Before
def render_invoice(order):
    print(f"Invoice for {order.customer.name}")
    print(f"Date: {order.date}")
    print(f"---")
    # calculate totals
    subtotal = sum(item.price for item in order.items)
    tax = subtotal * 0.08
    total = subtotal + tax
    print(f"Subtotal: {subtotal}")
    print(f"Tax: {tax}")
    print(f"Total: {total}")

The comment "calculate totals" signals the extraction.

# After
def render_invoice(order):
    print(f"Invoice for {order.customer.name}")
    print(f"Date: {order.date}")
    print(f"---")
    subtotal, tax, total = calculate_totals(order)
    print(f"Subtotal: {subtotal}")
    print(f"Tax: {tax}")
    print(f"Total: {total}")

def calculate_totals(order):
    subtotal = sum(item.price for item in order.items)
    tax = subtotal * 0.08
    total = subtotal + tax
    return subtotal, tax, total

Function name replaces comment. Reads as story.

Step 3: Name Captures Intent (10 min)

# Bad name
def do_stuff(x):
    ...

# Good name
def calculate_shipping_cost(order):
    ...

Name tells you what without reading the body.

If you can't name it: maybe the extraction isn't right (no single concept inside).

Step 4: Identify Inputs and Outputs (10 min)

For the block you want to extract:

  • What variables does it read? → parameters

  • What variables does it write that are used later? → return values

  • What does it mutate? → consider returning new value instead

If too many params: maybe object that bundles related fields.

Step 5: Small Functions Win (10 min)

"Functions should do one thing."

Heuristic: 5-15 lines is comfortable. Longer = candidate for further extraction.

Not religion: some functions are naturally longer.

But suspect long functions. They usually have multiple concerns.

Step 6: Extract Without Behavior Change (15 min)

Process:

  1. Add the new function with the extracted code

  2. Replace original block with call

  3. Run tests

  4. Refine names

Each step small. Tests after each.

# Step 1: add
def calculate_totals(order):
    subtotal = sum(...)
    ...

# Step 2: replace
def render_invoice(order):
    ...
    subtotal, tax, total = calculate_totals(order)
    ...

Don't change names + behavior at once. Confusion + bugs.

Step 7: Use IDE Refactoring (5 min)

Most IDEs:

  • VS Code: select block → "Extract function"

  • PyCharm / IntelliJ: select → Refactor → Extract method

  • Visual Studio: same

IDE handles parameter passing, return values, scoping.

Use it. Manual extraction = typos.

Step 8: Beware Premature Extraction (10 min)

def is_positive(n):
    return n > 0

Extracting n > 0 into is_positive(n): usually NOT helpful. The original is just as clear.

Extract when:

  • Block is complex enough that a name adds clarity

  • It's reused

  • It's testable independently

Don't extract trivial expressions.

Step 9: After Extract, Look at the Caller (10 min)

Often, after extraction, the caller becomes much clearer:

def render_invoice(order):
    print(invoice_header(order))
    print(invoice_lines(order))
    print(invoice_totals(order))
    print(invoice_footer(order))

Now reads as table of contents. Each helper does its thing.

This is the win. Many small functions, top-level reads like a story.

Step 10: Refactor Tests in Parallel (10 min)

# Now you can unit-test the helper
def test_calculate_totals():
    order = make_order(items=[Item(10), Item(20)])
    subtotal, tax, total = calculate_totals(order)
    assert subtotal == 30
    assert tax == 2.4
    assert total == 32.4

Tests are easier with small functions. Less mocking.

What You Just Did

Extract function: spot opportunities, name well, IO, small, no-behavior-change, IDE, when not to, caller clarity, parallel tests.

Common Failure Modes

Bad names. do_calculation, handle_thing.

Too many parameters. Extract a struct.

Behavior change snuck in. Tests catch — if they exist.

Extracted at wrong scope. Module function when method belongs in class.

Over-extracted. 100 two-line functions; harder to follow.

Continue the Refactoring Patterns path

Part of the Refactoring Patterns learning path.

bottom of page