name: refactor-decompose-function description: "For long functions: break into smaller pieces, extract helper functions, reduce nesting, improve testability and readability."
refactor-decompose-function
When to Use
- Function is longer than 10-15 lines
- Multiple levels of nesting
- Hard to understand at a glance
- Difficult to test parts independently
- Code has natural "chunks" with different purposes
When NOT to Use
- Function is already simple and clear
- Decomposition would obscure the algorithm
- Helpers would only be used once with no clarity gain
The Pattern
Extract cohesive chunks into named helper functions. Each function should do one thing.
# BEFORE: Long, hard to test
def process_data(data):
# Validate
if not data:
raise ValueError("Empty data")
if not all(isinstance(x, int) for x in data):
raise TypeError("Non-integer data")
# Transform
result = []
for x in data:
if x > 0:
result.append(x * 2)
else:
result.append(0)
# Summarize
total = sum(result)
avg = total / len(result)
return {'data': result, 'total': total, 'average': avg}
# AFTER: Decomposed, each part testable
def process_data(data):
validate(data)
transformed = transform(data)
return summarize(transformed)
def validate(data):
if not data:
raise ValueError("Empty data")
if not all(isinstance(x, int) for x in data):
raise TypeError("Non-integer data")
def transform(data):
return [x * 2 if x > 0 else 0 for x in data]
def summarize(data):
total = sum(data)
return {'data': data, 'total': total, 'average': total / len(data)}
Example (from pytudes)
# Spelling correction (spell.py) - beautifully decomposed
def correction(word):
"""Most probable spelling correction for word."""
return max(candidates(word), key=P)
def candidates(word):
"""Generate possible spelling corrections for word."""
return known([word]) or known(edits1(word)) or known(edits2(word)) or [word]
def known(words):
"""The subset of words that appear in the dictionary."""
return set(w for w in words if w in WORDS)
def edits1(word):
"""All edits one edit away from word."""
letters = 'abcdefghijklmnopqrstuvwxyz'
splits = [(word[:i], word[i:]) for i in range(len(word) + 1)]
deletes = [L + R[1:] for L, R in splits if R]
transposes = [L + R[1] + R[0] + R[2:] for L, R in splits if len(R) > 1]
replaces = [L + c + R[1:] for L, R in splits if R for c in letters]
inserts = [L + c + R for L, R in splits for c in letters]
return set(deletes + transposes + replaces + inserts)
def edits2(word):
"""All edits two edits away from word."""
return (e2 for e1 in edits1(word) for e2 in edits1(e1))
Key Principles
- Each function = one job: Name describes the job
- 1-5 lines ideal: If longer, consider splitting
- Testable units: Each helper can be tested alone
- Read top-down: High-level function shows the story
- Compose, don't nest:
f(g(h(x)))beats deeply nested code