Function Extraction Guidelines
Last Updated: 2025-12-06
Overview
This document provides standards and guidelines for function extraction and refactoring in the Claude Night Market plugin ecosystem. Following these guidelines validates maintainable, testable, and readable code.
Principles
1. Single Responsibility Principle (SRP)
A function should have only one reason to change.
2. Keep Functions Small
- Ideal: 10-20 lines of code
- Acceptable: 20-30 lines with clear logic
- Maximum: 50 lines with strong justification
- Never exceed 100 lines without splitting
3. Limited Parameters
- Ideal: 0-3 parameters
- Acceptable: 4-5 parameters with clear types
- Consider object parameter if 6+ parameters
4. Clear Naming
- Functions should be verbs that describe their action
- Use consistent naming conventions across the codebase
- Avoid abbreviations unless widely understood
When to Extract Functions
Immediate Extraction Required
-
Function exceeds 30 lines
# BAD - Too long def process_large_content(content): lines = content.split('\n') filtered_lines = [] for line in lines: if line.strip(): if not line.startswith('#'): if len(line) < 100: filtered_lines.append(line.strip()) # ... 20 more lines -
Function has multiple responsibilities
# BAD - Multiple responsibilities def analyze_and_optimize(content): # Analysis part complexity = calculate_complexity(content) quality = assess_quality(content) # Optimization part optimized = remove_redundancy(content) optimized = shorten_sentences(optimized) return optimized, complexity, quality -
Nested function depth exceeds 3 levels
# BAD - Too nested def process_data(data): if data: for item in data: if item.valid: for subitem in item.children: if subitem.active: # Deep nesting - extract this process_subitem(subitem)
Consider Extraction
-
Function has 4+ parameters
# CONSIDER - Many parameters def create_report(title, content, author, date, format, include_header, include_footer): pass # BETTER - Use configuration object @dataclass class ReportConfig: title: str content: str author: str date: datetime format: str = "pdf" include_header: bool = True include_footer: bool = True def create_report(config: ReportConfig): pass -
Complex conditional logic
# CONSIDER - Complex conditions def calculate_rate(user, product, time, location, special_offer): if user.premium and product.category in ["electronics", "books"]: if time.hour < 12 and location.country == "US": if special_offer and not user.used_recently: return 0.9 # ... more conditions # BETTER - Extract condition checks def _is_eligible_for_discount(user, product, time, location, special_offer): return (user.premium and product.category in ["electronics", "books"] and time.hour < 12 and location.country == "US" and special_offer and not user.used_recently)
Extraction Patterns
1. Extract Method Pattern
Before:
def generate_report(data):
# Validate data
if not data:
raise ValueError("Data cannot be empty")
if not all(isinstance(item, dict) for item in data):
raise TypeError("All items must be dictionaries")
# Process data
processed = []
for item in data:
processed_item = {
'id': item.get('id'),
'name': item.get('name', '').title(),
'value': float(item.get('value', 0))
}
processed.append(processed_item)
# Calculate totals
total = sum(item['value'] for item in processed)
average = total / len(processed) if processed else 0
return {
'items': processed,
'summary': {
'total': total,
'average': average,
'count': len(processed)
}
}
After:
def generate_report(data):
"""Generate a report from data items."""
_validate_data(data)
processed_items = _process_data_items(data)
summary = _calculate_summary(processed_items)
return {
'items': processed_items,
'summary': summary
}
def _validate_data(data):
"""Validate input data."""
if not data:
raise ValueError("Data cannot be empty")
if not all(isinstance(item, dict) for item in data):
raise TypeError("All items must be dictionaries")
def _process_data_items(data):
"""Process individual data items."""
return [
{
'id': item.get('id'),
'name': item.get('name', '').title(),
'value': float(item.get('value', 0))
}
for item in data
]
def _calculate_summary(items):
"""Calculate summary statistics."""
total = sum(item['value'] for item in items)
return {
'total': total,
'average': total / len(items) if items else 0,
'count': len(items)
}
2. Strategy Pattern for Complex Logic
Before:
def optimize_content(content, strategy_type):
if strategy_type == "aggressive":
# Remove all emphasis
lines = content.split('\n')
cleaned = []
for line in lines:
if not line.strip().startswith('**'):
cleaned.append(line)
return '\n'.join(cleaned)
elif strategy_type == "moderate":
# Shorten code blocks
# ... 20 lines of logic
elif strategy_type == "gentle":
# Only remove images
# ... 20 lines of logic
After:
from abc import ABC, abstractmethod
class OptimizationStrategy(ABC):
"""Base class for content optimization strategies."""
@abstractmethod
def optimize(self, content: str) -> str:
"""Optimize content according to strategy."""
pass
class AggressiveOptimizationStrategy(OptimizationStrategy):
"""Aggressive content optimization."""
def optimize(self, content: str) -> str:
lines = content.split('\n')
cleaned = [
line for line in lines
if not line.strip().startswith('**')
]
return '\n'.join(cleaned)
class ModerateOptimizationStrategy(OptimizationStrategy):
"""Moderate content optimization."""
def optimize(self, content: str) -> str:
# Implementation for moderate optimization
pass
class GentleOptimizationStrategy(OptimizationStrategy):
"""Gentle content optimization."""
def optimize(self, content: str) -> str:
# Implementation for gentle optimization
pass
# Strategy registry
OPTIMIZATION_STRATEGIES = {
"aggressive": AggressiveOptimizationStrategy(),
"moderate": ModerateOptimizationStrategy(),
"gentle": GentleOptimizationStrategy()
}
def optimize_content(content: str, strategy_type: str) -> str:
"""Optimize content using specified strategy."""
if strategy_type not in OPTIMIZATION_STRATEGIES:
raise ValueError(f"Unknown strategy: {strategy_type}")
strategy = OPTIMIZATION_STRATEGIES[strategy_type]
return strategy.optimize(content)
3. Builder Pattern for Complex Construction
Before:
def create_complex_object(name, type, config, options, metadata):
obj = ComplexObject()
obj.name = name
obj.type = type
# Complex configuration
if config.get('enabled', True):
obj.enabled = True
obj.timeout = config.get('timeout', 30)
obj.retries = config.get('retries', 3)
# Options processing
for key, value in options.items():
if key.startswith('custom_'):
obj.custom_fields[key[7:]] = value
else:
setattr(obj, key, value)
# Metadata handling
obj.created_at = metadata.get('created_at', datetime.now())
obj.created_by = metadata.get('created_by', 'system')
return obj
After:
class ComplexObjectBuilder:
"""Builder for ComplexObject instances."""
def __init__(self):
self._object = ComplexObject()
def with_name(self, name: str) -> 'ComplexObjectBuilder':
self._object.name = name
return self
def with_type(self, obj_type: str) -> 'ComplexObjectBuilder':
self._object.type = obj_type
return self
def with_config(self, config: Dict[str, Any]) -> 'ComplexObjectBuilder':
self._object.enabled = config.get('enabled', True)
self._object.timeout = config.get('timeout', 30)
self._object.retries = config.get('retries', 3)
return self
def with_options(self, options: Dict[str, Any]) -> 'ComplexObjectBuilder':
for key, value in options.items():
if key.startswith('custom_'):
self._object.custom_fields[key[7:]] = value
else:
setattr(self._object, key, value)
return self
def with_metadata(self, metadata: Dict[str, Any]) -> 'ComplexObjectBuilder':
self._object.created_at = metadata.get('created_at', datetime.now())
self._object.created_by = metadata.get('created_by', 'system')
return self
def build(self) -> ComplexObject:
return self._object
# Usage
def create_complex_object(name, type, config, options, metadata):
return (ComplexObjectBuilder()
.with_name(name)
.with_type(type)
.with_config(config)
.with_options(options)
.with_metadata(metadata)
.build())
Testing Extracted Functions
1. Unit Test Each Extracted Function
# Test for _validate_data
def test_validate_data_valid():
data = [{'id': 1, 'name': 'test'}]
# Should not raise
_validate_data(data)
def test_validate_data_empty():
with pytest.raises(ValueError, match="Data cannot be empty"):
_validate_data([])
def test_validate_data_invalid_type():
with pytest.raises(TypeError, match="All items must be dictionaries"):
_validate_data([{'id': 1}, "invalid"])
2. Test Strategy Implementations
def test_aggressive_optimization():
content = "**Bold text**\nNormal text\n**More bold**"
strategy = AggressiveOptimizationStrategy()
result = strategy.optimize(content)
assert "Normal text" in result
assert "**" not in result
3. Integration Tests
def test_generate_report_integration():
data = [
{'id': 1, 'name': 'test item', 'value': 100},
{'id': 2, 'name': 'another item', 'value': 200}
]
report = generate_report(data)
assert report['summary']['total'] == 300
assert report['summary']['average'] == 150
assert len(report['items']) == 2
Code Review Checklist
When reviewing code for function extraction:
Function Size
- Function is under 30 lines
- If over 30 lines, there’s a clear justification
- No function exceeds 100 lines
Responsibilities
- Function has a single, clear purpose
- Function name describes its purpose accurately
- Function doesn’t mix abstraction levels
Parameters
- Function has 0-5 parameters
- Parameters are well-typed
- Related parameters are grouped into objects
Complexity
- Cyclomatic complexity is under 10
- Nesting depth is under 4 levels
- No deeply nested ternary operators
Testability
- Function can be tested independently
- Function has no hidden dependencies
- Side effects are clearly documented
Documentation
- Function has a clear docstring
- Parameters are documented
- Return value is documented
- Exceptions are documented
Refactoring Workflow
1. Identify Refactoring Candidates
# Find long functions
find . -name "*.py" -exec wc -l {} \; | sort -n | tail -20
# Find complex functions (manual code review)
# Look for functions with:
# - Multiple return statements
# - Deep nesting
# - Many parameters
# - Mixed responsibilities
2. Create Tests First
# Write failing tests for the current behavior
def test_existing_behavior():
# Test the function as it exists now
pass
3. Extract Incrementally
- Extract small, private helper functions
- Run tests after each extraction
- Gradually extract larger functions
- Keep the public API stable
4. Optimize Imports and Dependencies
- Remove unused imports
- Group related imports
- Consider circular dependency issues
5. Update Documentation
- Update function docstrings
- Update API documentation
- Add examples for complex functions
Tools and Automation
1. Complexity Analysis
# Using radon (complexity analyzer)
pip install radon
radon cc your_file.py -a
# Using flake8 with complexity plugin
pip install flake8-mccabe
flake8 --max-complexity 10 your_file.py
2. Automated Refactoring Tools
# Using rope (refactoring library)
pip install rope
rope refactor.py -e
# Using black for formatting (maintains consistency)
pip install black
black your_file.py
3. Pre-commit Hooks
# .pre-commit-config.yaml
repos:
- repo: https://github.com/PyCQA/flake8
rev: 4.0.1
hooks:
- id: flake8
args: [--max-complexity=10, --max-line-length=100]
- repo: https://github.com/psf/black
rev: 22.3.0
hooks:
- id: black
language_version: python3
Examples from the Codebase
Before: GrowthController.generate_control_strategies()
The original function was 60+ lines and handled multiple responsibilities.
After Refactoring:
def generate_control_strategies(self, growth_rate: float) -> StrategyPlan:
"""Generate detailed control strategies for growth management."""
strategies = self._select_control_strategies(growth_rate)
monitoring = self._define_monitoring_needs(strategies)
implementation = self._plan_implementation(strategies, monitoring)
return StrategyPlan(strategies, monitoring, implementation)
def _select_control_strategies(self, growth_rate: float) -> List[Strategy]:
"""Select appropriate control strategies based on growth rate."""
# Extracted strategy selection logic
def _define_monitoring_needs(self, strategies: List[Strategy]) -> MonitoringPlan:
"""Define monitoring requirements for selected strategies."""
# Extracted monitoring logic
def _plan_implementation(self, strategies: List[Strategy],
monitoring: MonitoringPlan) -> ImplementationPlan:
"""Plan implementation steps for strategies and monitoring."""
# Extracted implementation planning
This refactoring:
- Reduced main function to 5 lines
- Created three focused helper functions
- Made each function independently testable
- Improved readability and maintainability
Conclusion
Following these function extraction guidelines will:
- Improve Maintainability: Smaller, focused functions are easier to understand and modify
- Enhance Testability: Each function can be tested in isolation
- Increase Reusability: Extracted functions can be reused in different contexts
- Reduce Bugs: Simpler functions have fewer edge cases and are easier to verify
- Improve Code Review: Smaller functions are easier to review and understand
Remember: The goal is not just to make functions smaller, but to make the code more readable, maintainable, and testable.