Files
echoes-of-the-ash/docs/development/HANDLER_REFACTORING_V2.md
Joan c0783340b0 Unify all handler signatures and simplify router
- Standardize all handlers to signature: (query, user_id, player, data=None)
- Replace 125-line if/elif chain with HANDLER_MAP dictionary
- Reduce router code by 90 lines (72% reduction)
- Add HANDLER_MAP registry for cleaner organization
- Enable future auto-discovery and decorator patterns
- Maintain full backward compatibility
- Document changes in HANDLER_REFACTORING_V2.md

Benefits:
- O(1) handler lookup vs O(n) if/elif chain
- Add new handlers by just updating the map
- Consistent signature makes code easier to understand
- Opens doors for middleware and hooks
2025-10-20 12:22:07 +02:00

6.0 KiB

Handler Refactoring V2 - Unified Signatures

Date: October 20, 2025
Status: Complete

Overview

Standardized all handler functions to use the same signature, enabling cleaner routing and better maintainability.

Changes

Unified Handler Signature

All handlers now have the same signature:

async def handle_*(query, user_id: int, player: dict, data: list = None) -> None:
    """Handler docstring."""
    # Implementation

Benefits

  1. Consistency - Every handler follows the same pattern
  2. Simpler Routing - Handler map lookup instead of massive if/elif chain
  3. Easy to Extend - Add new handlers by just adding to the map
  4. Auto-Discovery Ready - Could implement auto-discovery in the future
  5. Better Type Safety - IDE can validate all handlers have correct signature

Handler Map

Replaced 100+ lines of if/elif statements with a clean handler map:

HANDLER_MAP = {
    'inspect_area': handle_inspect_area,
    'attack_wandering': handle_attack_wandering,
    'inventory_menu': handle_inventory_menu,
    # ... etc
}

Router Simplification

Before (125 lines):

if action_type == "inspect_area":
    await handle_inspect_area(query, user_id, player)
elif action_type == "attack_wandering":
    await handle_attack_wandering(query, user_id, player, data)
elif action_type == "inventory_menu":
    await handle_inventory_menu(query, user_id, player)
# ... 40+ more elif branches

After (10 lines):

handler = HANDLER_MAP.get(action_type)
if handler:
    await handler(query, user_id, player, data)
else:
    logger.warning(f"Unknown action type: {action_type}")

Files Modified

Handler Modules

  • bot/action_handlers.py - Added data=None to 3 handlers
  • bot/inventory_handlers.py - Added data=None to 1 handler
  • bot/combat_handlers.py - Added data=None to 4 handlers
  • bot/profile_handlers.py - Added data=None to 2 handlers
  • bot/pickup_handlers.py - Already had data parameter
  • bot/corpse_handlers.py - Already had data parameter

Router

  • bot/handlers.py - Complete router rewrite:
    • Added HANDLER_MAP registry (50 lines)
    • Simplified button_handler() from 125 → 35 lines
    • Reduced code by ~90 lines
    • Improved readability and maintainability

Handlers Updated

Previously Without data Parameter

These handlers now accept data: list = None but ignore it:

# Action Handlers
handle_inspect_area()
handle_main_menu()
handle_move_menu()

# Inventory Handlers  
handle_inventory_menu()

# Combat Handlers
handle_combat_attack()
handle_combat_flee()
handle_combat_use_item_menu()
handle_combat_back()

# Profile Handlers
handle_profile()
handle_spend_points_menu()

Already Had data Parameter

These handlers use the data list for callback parameters:

# Action Handlers
handle_attack_wandering(data)  # [type, npc_id]
handle_inspect_interactable(data)  # [type, interactable_id]
handle_action(data)  # [type, action_type, interactable_id]
handle_move(data)  # [type, destination_id]

# Inventory Handlers
handle_inventory_item(data)  # [type, item_id]
handle_inventory_use(data)  # [type, item_id]
handle_inventory_drop(data)  # [type, item_id]
handle_inventory_equip(data)  # [type, item_id]
handle_inventory_unequip(data)  # [type, item_id]

# Pickup Handlers
handle_pickup_menu(data)  # [type, item_name]
handle_pickup(data)  # [type, item_name]

# Combat Handlers
handle_combat_use_item(data)  # [type, item_id]

# Profile Handlers
handle_spend_point(data)  # [type, stat_name]

# Corpse Handlers
handle_loot_player_corpse(data)  # [type, corpse_id]
handle_take_corpse_item(data)  # [type, corpse_id, item_id]
handle_scavenge_npc_corpse(data)  # [type, npc_corpse_id]
handle_scavenge_corpse_item(data)  # [type, npc_corpse_id, item_index]

Code Metrics

Metric Before After Change
Router Lines 125 35 -90 (-72%)
Handler Map 0 50 +50
If/Elif Branches 40+ 2 -38 (-95%)
Net Change - - -40 lines

Future Possibilities

With unified signatures, we could implement:

1. Auto-Discovery

def discover_handlers(package):
    handlers = {}
    for _, modname, _ in pkgutil.iter_modules(package.__path__):
        module = importlib.import_module(package.__name__ + "." + modname)
        for name, func in inspect.getmembers(module, inspect.iscoroutinefunction):
            if name.startswith("handle_"):
                action_name = name.replace("handle_", "")
                handlers[action_name] = func
    return handlers

2. Decorator-Based Registration

handlers = {}

def register_handler(action_name):
    def decorator(func):
        handlers[action_name] = func
        return func
    return decorator

@register_handler('inspect_area')
async def handle_inspect_area(query, user_id, player, data=None):
    ...

3. Middleware/Hooks

async def with_logging(handler):
    async def wrapper(query, user_id, player, data):
        logger.info(f"Handling {handler.__name__} for user {user_id}")
        result = await handler(query, user_id, player, data)
        logger.info(f"Completed {handler.__name__}")
        return result
    return wrapper

Testing

All handlers tested and working:

  • Handlers without data still work (data is ignored)
  • Handlers with data receive it correctly
  • Router lookup is instant (O(1) dict lookup)
  • Unknown actions handled gracefully
  • Error handling works correctly

Backward Compatibility

Fully backward compatible

  • All existing handler calls work identically
  • No changes to callback data format
  • No changes to handler behavior
  • Only internal signature standardization

Conclusion

This refactoring:

  • Reduces code complexity by 72%
  • Improves maintainability significantly
  • Makes adding new handlers trivial
  • Opens doors for future enhancements
  • Maintains full backward compatibility
  • No performance impact (actually faster with dict lookup)

Result: Cleaner, more maintainable, and more extensible code!