Defensive Programming Guide

July 22, 2025 · View on GitHub

Quick Reference

This guide provides defensive programming patterns used throughout the Claude PM framework to prevent common runtime errors.

Lazy Initialization Pattern

The Problem

Components initialized lazily can be None when accessed, causing AttributeError.

The Solution

Always check component existence before use.

✅ Correct Pattern

class Service:
    def __init__(self):
        self._component = None  # Lazy init
    
    def use_component(self):
        # Defensive check
        if not self._component:
            self._component = Component()
            self._setup_component()
        
        # Safe to use
        return self._component.method()

❌ Anti-Patterns to Avoid

Direct Property Access Without Check:

@property
def component(self):
    return self._component  # Can be None!

# Later...
self.component.method()  # AttributeError!

Assuming Initialization Order:

def __init__(self):
    self._bus = None
    self._use_bus()  # Error! Not initialized yet

def _use_bus(self):
    self._bus.send()  # AttributeError!

Real-World Example: Message Bus Fix

Before (Broken)

async def orchestrate(self, agent_type, request_data):
    # Direct use without check
    response = await self._message_bus.send_request(
        agent_id=agent_type,
        request_data=request_data
    )  # AttributeError if _message_bus is None

After (Fixed)

async def orchestrate(self, agent_type, request_data):
    # Defensive initialization
    if not self._message_bus:
        self._message_bus = SimpleMessageBus()
        self._register_default_agent_handlers()
        logger.debug("message_bus_initialized")
    
    # Now safe to use
    response = await self._message_bus.send_request(
        agent_id=agent_type,
        request_data=request_data
    )

Common Patterns in Claude PM

1. Component Initialization

# Pattern used in orchestration
if not self._context_manager:
    self._context_manager = create_context_manager()

if not self._agent_registry:
    self._agent_registry = AgentRegistry()

2. Safe Property Access

@property
def cache(self):
    """Safe lazy property access."""
    if self._cache is None:
        self._cache = SharedPromptCache()
    return self._cache

3. Initialization Validation

def validate_setup(self):
    """Ensure all components are initialized."""
    required = {
        "message_bus": self._message_bus,
        "context_manager": self._context_manager,
        "registry": self._agent_registry
    }
    
    missing = [k for k, v in required.items() if v is None]
    if missing:
        raise RuntimeError(f"Missing components: {missing}")

Best Practices

1. Initialize on First Use

def get_component(self):
    if not self._component:
        self._component = self._create_component()
    return self._component
def _ensure_messaging_ready(self):
    """Initialize all messaging components together."""
    if not self._message_bus:
        self._message_bus = SimpleMessageBus()
        self._register_handlers()
        self._setup_routing()

3. Log Initialization Events

if not self._service:
    logger.debug("Initializing service")
    self._service = Service()
    logger.info("Service initialized successfully")

4. Fail Fast with Clear Errors

def require_component(self):
    if not self._component:
        raise RuntimeError(
            "Component not initialized. Call initialize() first."
        )
    return self._component

Testing Defensive Code

Unit Test Pattern

def test_lazy_initialization():
    """Test component initializes on first use."""
    service = Service()
    assert service._component is None
    
    # First use triggers initialization
    result = service.use_component()
    assert service._component is not None
    
    # Subsequent uses reuse component
    component_ref = service._component
    service.use_component()
    assert service._component is component_ref

Integration Test Pattern

async def test_orchestration_initialization():
    """Test orchestrator handles uninitialized state."""
    orch = BackwardsCompatibleOrchestrator()
    
    # Should not fail even if components not initialized
    response = await orch.orchestrate_agent(
        "documentation",
        {"task": "test"}
    )
    
    # Verify components were initialized
    assert orch._message_bus is not None
    assert len(orch._message_bus.registered_agents) > 0

Checklist for Code Review

When reviewing code with lazy initialization:

  • All nullable components have defensive checks before use
  • Initialization happens in logical groups
  • Initialization is logged for debugging
  • Error messages clearly indicate what needs initialization
  • Tests verify both initialized and uninitialized states
  • No assumptions about initialization order
  • Thread-safety considered for concurrent access