Skip to content

Latest commit

 

History

History
141 lines (113 loc) · 4.65 KB

File metadata and controls

141 lines (113 loc) · 4.65 KB

Code Complexity Reduction Tutorial

This tutorial demonstrates how to systematically reduce code complexity through 5 progressive versions of a user registration function.

Overview of Improvements

Version Cyclomatic Cognitive Maintainability Halstead Volume Key Technique
V1 ❌ 68 ❌ 589 ❌ 44.89 ❌ 1567 Initial bad code (deeply nested)
V2 ❌ 68 ❌ 62 ❌ 49.52 ❌ 1666 Early returns (90% cognitive reduction!)
V3 ✅ Pass ✅ Pass ❌ 49.13 ❌ 1040 Extract validation functions
V4 ✅ Pass ✅ Pass ✅ Pass ✅ Pass Data classes & configuration
V5 ✅ Pass ✅ Pass ❌ 45.56 ✅ Pass Enterprise patterns (slight trade-off)

Key Lessons

1. Early Returns (V1 → V2)

Technique: Replace nested if-else with guard clauses Impact: Massive 90% reduction in cognitive complexity!

# Bad: Deeply nested
if condition1:
    if condition2:
        if condition3:
            do_something()

# Good: Early returns
if not condition1:
    return error
if not condition2:
    return error
if not condition3:
    return error
do_something()

2. Extract Functions (V2 → V3)

Technique: Single Responsibility Principle Impact: Distributes complexity across multiple small functions

# Bad: Everything in one function
def register_user(username, password, email, ...):
    # 200 lines of validation and logic

# Good: Separate concerns
def validate_username(username):
    # 10 lines
def validate_password(password):
    # 10 lines
def register_user(...):
    # 20 lines coordinating the validators

3. Use Data Classes (V3 → V4)

Technique: Group related parameters Impact: Reduces parameter count and Halstead volume

# Bad: Too many parameters
def register_user(username, password, email, age, country, phone, address, city, state, zip, ...):

# Good: Logical grouping
@dataclass
class UserRegistrationData:
    username: str
    password: str
    # ...

def register_user(data: UserRegistrationData):

4. Configuration & Constants (V4)

Technique: Extract magic numbers and repeated values Impact: Improves maintainability and reusability

# Bad: Magic numbers everywhere
if len(username) < 3 or len(username) > 20:

# Good: Named constants
MIN_USERNAME_LENGTH = 3
MAX_USERNAME_LENGTH = 20
if not MIN_USERNAME_LENGTH <= len(username) <= MAX_USERNAME_LENGTH:

5. Professional Patterns (V5)

Technique: Dependency injection, interfaces, service layer Impact: Highly testable and extensible (with slight complexity trade-off)

  • Validator classes for reusability
  • Repository pattern for data access
  • Service layer for business logic
  • Each component is independently testable

When to Stop Refactoring

Version 5 shows that sometimes adding proper architecture can slightly increase some metrics. This is a trade-off:

Benefits of V5:

  • Highly testable (can mock dependencies)
  • Easy to extend (add new validators)
  • Clear separation of concerns
  • Production-ready patterns

⚠️ Trade-offs:

  • More files and classes
  • Slightly lower maintainability index
  • More abstraction layers

Key Insight: The goal isn't to minimize all metrics at any cost, but to find the right balance for your project's needs.

Best Practices Summary

  1. Start with early returns - Biggest bang for your buck
  2. Extract functions when they do one clear thing - Improves testability
  3. Group related data - Reduces parameter lists
  4. Use constants for magic values - Improves maintainability
  5. Consider architecture based on project size - Don't over-engineer small scripts
  6. Measure progress - Use tools like antipasta to track improvements
  7. Know when to stop - Perfect metrics aren't always the goal

Running the Examples

# Check metrics for all versions
antipasta metrics --files DEMOS/TUTORIAL/*.py

# See the progression
antipasta metrics --files DEMOS/TUTORIAL/01_user_management_v1.py
antipasta metrics --files DEMOS/TUTORIAL/02_user_management_v2.py
# ... and so on

Applying These Techniques

When you encounter high complexity in your code:

  1. First: Can you use early returns to reduce nesting?
  2. Second: Can you extract clear, single-purpose functions?
  3. Third: Can you group related parameters into objects?
  4. Fourth: Are there magic numbers to extract as constants?
  5. Finally: Does the code warrant a more sophisticated architecture?

Remember: The goal is maintainable, understandable code that passes reasonable complexity thresholds, not necessarily the lowest possible numbers.