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:
Add the new function with the extracted code
Replace original block with call
Run tests
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
Previous — Part 1: Why Refactor
Next — Part 3: Extract Variable
Part of the Refactoring Patterns learning path.


