| name | complexity-refactoring |
| description | Refactoring complex functions into smaller, pure helper functions. Apply when function complexity is exceeded or when extracting helper functions during refactoring. If tasked with fixing ruff lint errors related to complexity, ALWAYS trigger this skill. |
| user-invocable | false |
Refactoring Complex Functions
When a function exceeds McCabe complexity limits (C901), extract helper functions that are pure and return values.
Quick Reference
| Principle | Pattern |
|---|
| Pure helpers | Extract functions that return values, never mutate inputs |
| No void mutators | NEVER create helpers that modify a collection and return None |
| Transform, don't mutate | result = transform(data) not mutate(data) |
| Comprehensions first | Prefer comprehensions over loops when building collections |
| Single return value | Each helper computes and returns one thing |
The Anti-Pattern: Void Mutator Functions
NEVER create helper functions that mutate a collection passed as an argument.
def _add_user_fields(result: dict[str, Any], user: User) -> None:
"""Mutates result dict - THIS IS WRONG."""
result["name"] = user.name
result["email"] = user.email
result["role"] = user.role
def _add_metadata(result: dict[str, Any], metadata: Metadata) -> None:
"""Mutates result dict - THIS IS WRONG."""
result["created_at"] = metadata.created_at
result["version"] = metadata.version
def build_response(user: User, metadata: Metadata) -> dict[str, Any]:
result: dict[str, Any] = {}
_add_user_fields(result, user)
_add_metadata(result, metadata)
return result
Why this is wrong:
- Hidden side effects make code harder to reason about
- Can't compose or test helpers in isolation
- Violates command-query separation
- Makes the data flow invisible
The Correct Pattern: Pure Helpers That Return Values
def _build_user_fields(user: User) -> dict[str, Any]:
"""Return user fields as a dict."""
return {
"name": user.name,
"email": user.email,
"role": user.role,
}
def _build_metadata_fields(metadata: Metadata) -> dict[str, Any]:
"""Return metadata fields as a dict."""
return {
"created_at": metadata.created_at,
"version": metadata.version,
}
def build_response(user: User, metadata: Metadata) -> dict[str, Any]:
return {
**_build_user_fields(user),
**_build_metadata_fields(metadata),
}
Refactoring Patterns
Pattern 1: Extract Computation
When a function has complex logic that computes a value, extract it.
def process_order(order: Order) -> Summary:
total = 0
for item in order.items:
price = item.unit_price * item.quantity
if item.discount:
price *= (1 - item.discount)
if order.member:
price *= 0.9
total += price
def _calculate_item_price(item: OrderItem, is_member: bool) -> Decimal:
"""Calculate final price for a single item."""
price = item.unit_price * item.quantity
if item.discount:
price *= (1 - item.discount)
if is_member:
price *= Decimal("0.9")
return price
def _calculate_order_total(order: Order) -> Decimal:
"""Calculate total price for all items."""
return sum(
_calculate_item_price(item, order.member)
for item in order.items
)
def process_order(order: Order) -> Summary:
total = _calculate_order_total(order)
Pattern 2: Extract Collection Building
When building a list or dict, extract the transformation logic.
def _add_valid_items(results: list[Item], items: list[Item]) -> None:
for item in items:
if item.is_valid and item.score > 0.5:
results.append(item)
def _filter_valid_items(items: list[Item]) -> list[Item]:
"""Return items that are valid with score > 0.5."""
return [item for item in items if item.is_valid and item.score > 0.5]
valid_items = [item for item in items if item.is_valid and item.score > 0.5]
Pattern 3: Extract Conditional Logic
When a function has complex branching, extract the decision or transformation.
def format_value(value: Any, config: Config) -> str:
if isinstance(value, datetime):
if config.use_iso:
return value.isoformat()
else:
return value.strftime(config.date_format)
elif isinstance(value, Decimal):
if config.currency:
return f"${value:.2f}"
else:
return str(value)
def _format_datetime(value: datetime, config: Config) -> str:
"""Format datetime according to config."""
if config.use_iso:
return value.isoformat()
return value.strftime(config.date_format)
def _format_decimal(value: Decimal, config: Config) -> str:
"""Format decimal according to config."""
if config.currency:
return f"${value:.2f}"
return str(value)
def format_value(value: Any, config: Config) -> str:
if isinstance(value, datetime):
return _format_datetime(value, config)
if isinstance(value, Decimal):
return _format_decimal(value, config)
Pattern 4: Extract Validation
When validation logic is complex, extract validators that return results.
def _validate_email(user: User, errors: list[str]) -> None:
if not user.email:
errors.append("Email is required")
elif "@" not in user.email:
errors.append("Invalid email format")
def _validate_email(email: str | None) -> list[str]:
"""Return list of validation errors for email (empty if valid)."""
if not email:
return ["Email is required"]
if "@" not in email:
return ["Invalid email format"]
return []
def validate_user(user: User) -> list[str]:
"""Return all validation errors for user."""
return [
*_validate_email(user.email),
*_validate_name(user.name),
*_validate_role(user.role),
]
Checklist When Extracting Helpers
Before creating a helper function during refactoring, verify:
If any answer is "no", redesign the helper.
Validation
Check complexity after refactoring via the validate-code skill:
uv run .claude/scripts/validate_code.py --lint <file>
The --lint flag runs ruff check, which includes C901 complexity checks. The refactored code should have no functions exceeding McCabe complexity of 5.