5.3 KiB
sessionId
| sessionId |
|---|
| session-260905-173932-36te |
Requirements
Overview & Goals
Improve the MusicManager class for better maintainability, testability, and separation of concerns.
Scope
In Scope:
- Refactor
MusicManagerto reduce responsibilities - Extract vote-skip logic into a dedicated service
- Fix synchronization strategy
- Improve error handling
- Eliminate magic numbers
Out of Scope:
- Changing
QueueManagerorTrackSchedulerinternals (beyond reducing circular refs) - Changing external behavior or command interfaces
Technical Design
Current Issues
-
Vote-skip business logic in MusicManager (lines 113–145): The
voteSkip()method contains complex voting logic (threshold calculation, member counting, skip decisions) that doesn't belong in a player manager. Magic number2.1is used as a vote divisor with no explanation. -
Circular dependencies:
MusicManager→TrackScheduler→QueueManager→TrackScheduler→MusicManager. This makes testing and reasoning about the code difficult. -
Overly broad
synchronizedmethods: Nearly every public method issynchronizedonthis, even simple getters likegetCurrentlyPlaying()andisCurrentlyPlaying(). This creates unnecessary contention. Methods likegetQueueSize()aren't synchronized at all, creating inconsistency. -
Poor error handling in
connect()(line 80): Throws a bareRuntimeExceptionwhenaudioChannelis null instead of usingIllegalArgumentExceptionor returning aCommandExecutionResult. -
baseVolumeparsed from String without error handling (line 44):Integer.parseInt()can throwNumberFormatExceptionwith no fallback.
Proposed Changes
1. Extract VoteSkipService
- New class:
me.bobert.netneutrality.music.VoteSkipService - Moves all vote-skip logic out of
MusicManager - Replace magic
2.1with a named constantVOTE_THRESHOLD_DIVISORor configurable guild setting MusicManager.voteSkip()delegates to the new service
2. Reduce circular dependencies
TrackSchedulerandQueueManagershould not need to call back intoMusicManagerfordisconnect(). Instead, use a callback/listener interface (e.g.,MusicManagerCallback) thatTrackSchedulerholds, breaking the direct circular reference.
3. Fix synchronization
- Remove
synchronizedfrom simple delegating methods (getCurrentlyPlaying(),isCurrentlyPlaying(),resume(),stopTrack()) —AudioPlayeris already thread-safe. - Keep
synchronizedonly on methods that coordinate multiple state changes (connect(),disconnect(),play()). - Make
getQueueSize()consistent with the chosen strategy.
4. Improve error handling
connect(): ReplaceRuntimeExceptionwithIllegalArgumentException("audioChannel must not be null")baseVolume: Add try-catch with a sensible default (e.g., 50) and a warning log.
5. Extract constants
VOTE_THRESHOLD_DIVISOR = 2.1(or make configurable)MIN_MEMBERS_FOR_VOTE = 3DEFAULT_VOLUME = 50
File Structure
| File | Action |
|---|---|
music/MusicManager.java |
Modify — remove vote logic, fix sync, fix error handling |
music/VoteSkipService.java |
New — extracted vote-skip logic |
music/MusicManagerCallback.java |
New — interface to break circular dependency |
music/TrackScheduler.java |
Modify — use callback interface instead of direct MusicManager reference where possible |
music/QueueManager.java |
Modify — remove getMusicManager() call chain for disconnect |
Delivery Steps
✓ Step 1: Extract VoteSkipService and constants
Vote-skip logic is isolated in a new service class and magic numbers are replaced with named constants.
- Create
VoteSkipService.javainme.bobert.netneutrality.music - Move the vote-skip logic from
MusicManager.voteSkip()intoVoteSkipService.voteSkip(CommandContext, QueueManager, AudioChannel) - Define constants:
VOTE_THRESHOLD_DIVISOR,MIN_MEMBERS_FOR_VOTE - Update
MusicManager.voteSkip()to delegate to the new service - Add
DEFAULT_VOLUMEconstant and wrapbaseVolumeparsing in try-catch with fallback
✓ Step 2: Fix synchronization and error handling
Synchronization is applied only where needed and error handling is improved.
- Remove
synchronizedfrom simple delegating methods:getCurrentlyPlaying(),isCurrentlyPlaying(),resume(),stopTrack(),setVolume() - Keep
synchronizedonconnect(),disconnect(),play(),isNotConnectedTo() - Replace
RuntimeExceptioninconnect()withIllegalArgumentException - Ensure
getQueueSize()follows the same synchronization strategy as other methods
✓ Step 3: Reduce circular dependencies between MusicManager, TrackScheduler, and QueueManager
The circular reference chain is broken using a callback interface.
- Create
MusicManagerCallback.javainterface with methodvoid onQueueEmpty()(or similar) to replace directMusicManager.disconnect()calls fromQueueManager - Have
MusicManagerimplement the callback interface - Update
TrackSchedulerconstructor to accept the callback interface instead of the fullMusicManagerwhere only disconnect/connection behavior is needed - Update
QueueManager.clearQueue()to use the callback instead ofscheduler.getMusicManager().disconnect()