RoundViewModel.kt Comprehensive Architectural Audit
File: app/src/main/java/com/archeryapprentice/ui/roundScoring/RoundViewModel.kt
Size: 2,058 lines
Methods: 87 functions
Classes: 5 classes in one file
Audit Date: 2025-01-22
Executive Summary
VERDICT: CONFIRMED GOD CLASS ANTIPATTERN π¨
RoundViewModel.kt is a textbook god class violating multiple SOLID principles and architectural boundaries. Despite containing some legitimate business logic, it manages 7 distinct architectural domains that should be separated into specialized components.
Key Metrics:
- Complexity Score: 9/10 (Critical)
- Maintainability: 2/10 (Poor)
- Testability: 3/10 (Poor)
- Refactoring Priority: IMMEDIATE
1. Responsibility Analysis
7 Distinct Architectural Domains Identified:
1. Round Creation & Setup (Lines 150-400)
// Methods like:
fun loadDefaultSetupDialog()
fun createRound()
fun validateRoundCreation()Assessment: Should be RoundCreationViewModel
2. Live Scoring & Arrow Input (Lines 600-1200)
// Methods like:
fun recordArrowScore()
fun finalizeEnd()
fun updateLiveStatistics()Assessment: Should be LiveScoringViewModel
3. Analytics & Statistics (Lines 1200-1600)
// Methods like:
fun calculateRoundStatistics()
fun generateAggregateStats()
fun computeRankings()Assessment: Should be RoundAnalyticsViewModel
4. Data Loading & Persistence (Lines 400-600)
// Methods like:
fun loadRound()
fun saveRoundData()
fun refreshRoundList()Assessment: Should be extracted to repositories/services
5. UI State Management (Lines 100-300)
// Multiple StateFlow declarations
private val _uiState = MutableStateFlow()
private val _scoringState = MutableStateFlow()
private val _progressState = MutableStateFlow()Assessment: Partially legitimate, but overly complex
6. Session Management (Lines 1600-1900)
// Methods like:
fun startSession()
fun pauseSession()
fun endSession()Assessment: Should be SessionManager service
7. Round Management (Lines 1900-2058)
// Methods like:
fun deleteRound()
fun archiveRound()
fun duplicateRound()Assessment: Should be RoundManagementViewModel
2. Dependency Analysis
Layer Violations Identified:
UI Framework Dependencies in ViewModel π¨
import androidx.compose.ui.geometry.Offset // Line 6Violation: ViewModels should not depend on UI framework specifics
Direct Database Dependencies π¨
import com.archeryapprentice.data.db.ArcheryDatabase // Line 9Violation: Should use repository abstractions
Android Framework Dependencies β οΈ
import android.app.Application // Line 3
import android.content.Context // Line 4Assessment: Acceptable for AndroidViewModel, but suggests too much platform coupling
Dependency Injection Issues:
// Line 180-200: Manual repository creation
private val repositoryFactory = RepositoryFactory(database)Problem: Hard-coded dependency creation instead of injection
3. Method Analysis & Cohesion Assessment
Method Complexity Distribution:
Highly Complex Methods (>50 lines):
loadRound()- 89 linescalculateDisplayData()- 67 linesupdateLiveStatistics()- 78 linesfinalizeRound()- 91 lines
Methods with Excessive Parameters:
fun updateParticipantScore(
participantId: String,
endNumber: Int,
arrowScores: List<Int>,
timestamp: Long,
source: UpdateSource,
validateRules: Boolean,
updateUI: Boolean,
broadcastChange: Boolean,
auditLog: Boolean,
recalculateStats: Boolean,
triggerRecomposition: Boolean,
saveToDatabase: Boolean,
updateCache: Boolean,
notifyObservers: Boolean
) // 14 parameters!Problem: Clear violation of Single Responsibility Principle
Cohesion Analysis:
Low Cohesion Indicators:
- Methods operating on completely different data sets
- No shared state between method groups
- Different error handling patterns across domains
- Inconsistent parameter patterns
High Coupling Indicators:
- 28 different import statements
- Direct repository instantiation
- Platform-specific dependencies
- UI framework coupling
4. SOLID Principle Violations
Single Responsibility Principle π¨ SEVERE
Evidence: 7 distinct architectural domains in one class
// Lines 150-400: Round creation logic
// Lines 600-1200: Live scoring logic
// Lines 1200-1600: Analytics logic
// Lines 1600-1900: Session managementOpen/Closed Principle π¨ SEVERE
Evidence: Adding new features requires modifying the core class
- New scoring rules require ViewModel changes
- New analytics require ViewModel changes
- New UI states require ViewModel changes
Liskov Substitution Principle β οΈ MODERATE
Evidence: Not applicable (no inheritance), but would be impossible to substitute
Interface Segregation Principle π¨ SEVERE
Evidence: Clients forced to depend on methods they donβt use
// UI only needs: loadRound(), getCurrentState()
// But must depend on: createRound(), deleteRound(), calculateStats(), etc.Dependency Inversion Principle π¨ SEVERE
Evidence: Depends on concrete implementations
// Direct database dependency
private val database = ArcheryDatabase.getInstance(application)
// Should depend on abstractions5. Why Itβs 2,058 Lines: Root Cause Analysis
Legitimate Complexity (~600-800 lines):
- Round Session Coordination - Managing round lifecycle, participant coordination
- Core UI State - Essential state management for round scoring
- Business Rules - Core archery scoring logic that belongs in this domain
Architectural Debt (~1,400+ lines):
1. Feature Accumulation (Est. 800 lines)
- Originally started as simple round display
- Gradually accumulated scoring, analytics, creation, management
- No refactoring during feature additions
2. Incomplete Refactoring (Est. 400 lines)
// Evidence of attempted refactoring:
// Moved to state package: RoundInputState, RealTimeProgress
// Moved to types package: ScoreSubject
// But core class never actually cleaned up3. Compatibility Code (Est. 200+ lines)
// Lines 68-70: Legacy compatibility
val scorePercentage: Int, // Legacy compatibility
val participantDisplayNames: String, // Legacy compatibility
val accuracyPercentage: Double // Legacy compatibility4. Embedded Services (Est. 300+ lines)
- Statistics calculation should be separate service
- Session management should be separate service
- Data transformation should be in repositories
6. Refactoring Strategy & Roadmap
Phase 1: Extract Domain ViewModels (2 weeks)
Extract RoundCreationViewModel
// Move these responsibilities:
- loadDefaultSetupDialog()
- createRound()
- validateRoundCreation()
- Equipment selection logic
// Estimated size reduction: 400 linesExtract RoundAnalyticsViewModel
// Move these responsibilities:
- calculateRoundStatistics()
- generateAggregateStats()
- computeRankings()
- Performance analysis
// Estimated size reduction: 400 linesPhase 2: Extract Services (2 weeks)
Create SessionManager Service
// Move these responsibilities:
- startSession()
- pauseSession()
- endSession()
- Session state tracking
// Estimated size reduction: 300 linesCreate StatisticsCalculator Service
// Move complex calculation logic
// Remove business logic from ViewModel
// Estimated size reduction: 200 linesPhase 3: Architectural Cleanup (1 week)
Fix Dependency Injection
// Replace manual creation:
private val repositoryFactory = RepositoryFactory(database)
// With proper injection:
@Inject lateinit var roundRepository: RoundRepositoryRemove Layer Violations
// Remove UI dependencies:
import androidx.compose.ui.geometry.Offset
// Use abstractions instead of concrete typesInterface Segregation
// Create focused interfaces:
interface RoundDisplayContract
interface ScoringInputContract
interface ProgressTrackingContract7. Expected Outcomes
Target Architecture:
RoundViewModel.kt: ~600 lines (core coordination only)
βββ RoundCreationViewModel.kt: ~300 lines
βββ LiveScoringViewModel.kt: ~400 lines (already exists)
βββ RoundAnalyticsViewModel.kt: ~400 lines
βββ SessionManager.kt: ~200 lines
βββ StatisticsCalculator.kt: ~200 lines
βββ RoundRepository.kt: Enhanced with extracted logic
Benefits:
- Testability: Each component can be unit tested independently
- Maintainability: Changes isolated to specific domains
- Reusability: Services can be shared across features
- Performance: Smaller ViewModels = faster instantiation
- Developer Experience: Easier to understand and modify
Metrics Improvement:
- Complexity Score: 9/10 β 4/10
- Maintainability: 2/10 β 8/10
- Testability: 3/10 β 9/10
- Line Count: 2,058 β ~600 (70% reduction)
8. Immediate Action Items
Priority 1 (This Week):
- Create extraction interfaces for each domain
- Start with RoundCreationViewModel extraction (safest)
- Add comprehensive tests before refactoring
Priority 2 (Next Week):
- Extract StatisticsCalculator service
- Fix dependency injection setup
- Remove UI framework dependencies
Priority 3 (Following Weeks):
- Complete ViewModel extraction
- Implement interface segregation
- Add architectural tests to prevent regression
9. Risk Assessment
Refactoring Risks:
- High: Complex state dependencies between domains
- Medium: Potential breaking changes to existing UI
- Low: Well-tested business logic (79% test coverage)
Mitigation Strategies:
- Incremental Extraction: One domain at a time
- Interface-First: Define contracts before implementation
- Test Coverage: Maintain/improve during refactoring
- Feature Flags: Gradual rollout of refactored components
Conclusion
RoundViewModel.kt is definitively a god class antipattern that requires immediate architectural refactoring. While it contains legitimate business complexity, the 7 distinct architectural domains violate fundamental design principles and create a maintenance nightmare.
The 2,058 lines are not justified by legitimate complexity - approximately 70% should be extracted into specialized components. This refactoring is critical for:
- Tournament Scalability: Current architecture wonβt scale to 500+ users
- Feature Development: New features require modifying the monolith
- Bug Isolation: Issues affect multiple unrelated domains
- Developer Productivity: Complex architecture slows development
Recommendation: Begin immediate refactoring following the phased approach outlined above.
Source: docs/architecture/ROUNDVIEWMODEL_AUDIT.md
Status: Historical audit (refactoring progress documented in Refactoring-Reality-Check)