- 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
6.0 KiB
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
- Consistency - Every handler follows the same pattern
- Simpler Routing - Handler map lookup instead of massive if/elif chain
- Easy to Extend - Add new handlers by just adding to the map
- Auto-Discovery Ready - Could implement auto-discovery in the future
- 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- Addeddata=Noneto 3 handlersbot/inventory_handlers.py- Addeddata=Noneto 1 handlerbot/combat_handlers.py- Addeddata=Noneto 4 handlersbot/profile_handlers.py- Addeddata=Noneto 2 handlersbot/pickup_handlers.py- Already haddataparameterbot/corpse_handlers.py- Already haddataparameter
Router
bot/handlers.py- Complete router rewrite:- Added
HANDLER_MAPregistry (50 lines) - Simplified
button_handler()from 125 → 35 lines - Reduced code by ~90 lines
- Improved readability and maintainability
- Added
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!