cargo_coupling/balance/score.rs
1// ===== Balance Scoring =====
2
3use crate::metrics::coupling::CouplingMetrics;
4
5/// Balance score for a coupling relationship
6#[derive(Debug, Clone)]
7pub struct BalanceScore {
8 /// The coupling being scored
9 pub coupling: CouplingMetrics,
10 /// Overall balance score (0.0 - 1.0, higher = better balanced)
11 pub score: f64,
12 /// Whether strength and distance are aligned
13 pub alignment: f64,
14 /// Impact of volatility
15 pub volatility_impact: f64,
16 /// Interpretation of the score
17 pub interpretation: BalanceInterpretation,
18}
19
20/// How to interpret a balance score
21#[derive(Debug, Clone, Copy, PartialEq, Eq)]
22pub enum BalanceInterpretation {
23 /// Well-balanced, no action needed
24 Balanced,
25 /// Acceptable but could be improved
26 Acceptable,
27 /// Should be reviewed
28 NeedsReview,
29 /// Should be refactored
30 NeedsRefactoring,
31 /// Critical issue, must fix
32 Critical,
33}
34
35impl std::fmt::Display for BalanceInterpretation {
36 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
37 match self {
38 BalanceInterpretation::Balanced => write!(f, "Balanced"),
39 BalanceInterpretation::Acceptable => write!(f, "Acceptable"),
40 BalanceInterpretation::NeedsReview => write!(f, "Needs Review"),
41 BalanceInterpretation::NeedsRefactoring => write!(f, "Needs Refactoring"),
42 BalanceInterpretation::Critical => write!(f, "Critical"),
43 }
44 }
45}
46
47impl BalanceScore {
48 /// Calculate balance score for a coupling
49 ///
50 /// The formula implements: BALANCE = (STRENGTH XOR DISTANCE) OR NOT VOLATILITY
51 ///
52 /// Ideal patterns:
53 /// - Strong (1.0) + Close (0.0) → High alignment (cohesion)
54 /// - Weak (0.0) + Far (1.0) → High alignment (loose coupling)
55 ///
56 /// Problematic patterns:
57 /// - Strong (1.0) + Far (1.0) → Low alignment (global complexity)
58 /// - Any + High volatility → Reduced by volatility impact
59 pub fn calculate(coupling: &CouplingMetrics) -> Self {
60 let strength = coupling.strength_value();
61 let distance = coupling.distance_value();
62 let volatility = coupling.volatility_value();
63
64 // Alignment: how well strength and distance match the ideal patterns
65 // Ideal: (strong + close) OR (weak + far)
66 // XOR-like: difference between strength and distance
67 // If both high or both low = misaligned, if opposite = aligned
68 let alignment = 1.0 - (strength - (1.0 - distance)).abs();
69
70 // Volatility impact: high volatility with strong coupling is bad
71 // Only applies when there's actual coupling (strength > 0)
72 let volatility_penalty = volatility * strength;
73 let volatility_impact = 1.0 - volatility_penalty;
74
75 // Combined score: both alignment AND stability matter
76 // Using AND (multiplication) instead of OR (max) for stricter scoring
77 let score = alignment * volatility_impact;
78
79 // Determine interpretation based on score
80 let interpretation = match score {
81 s if s >= 0.8 => BalanceInterpretation::Balanced,
82 s if s >= 0.6 => BalanceInterpretation::Acceptable,
83 s if s >= 0.4 => BalanceInterpretation::NeedsReview,
84 s if s >= 0.2 => BalanceInterpretation::NeedsRefactoring,
85 _ => BalanceInterpretation::Critical,
86 };
87
88 Self {
89 coupling: coupling.clone(),
90 score,
91 alignment,
92 volatility_impact,
93 interpretation,
94 }
95 }
96
97 /// Check if this coupling is well-balanced (no action needed)
98 pub fn is_balanced(&self) -> bool {
99 matches!(
100 self.interpretation,
101 BalanceInterpretation::Balanced | BalanceInterpretation::Acceptable
102 )
103 }
104
105 /// Check if this coupling needs refactoring
106 pub fn needs_refactoring(&self) -> bool {
107 matches!(
108 self.interpretation,
109 BalanceInterpretation::NeedsRefactoring | BalanceInterpretation::Critical
110 )
111 }
112}
113
114/// Thresholds for identifying issues
115#[derive(Debug, Clone)]
116pub struct IssueThresholds {
117 /// Minimum strength value considered "strong"
118 pub strong_coupling: f64,
119 /// Minimum distance value considered "far"
120 pub far_distance: f64,
121 /// Minimum volatility value considered "high"
122 pub high_volatility: f64,
123 /// Number of dependencies to consider "high efferent coupling"
124 pub max_dependencies: usize,
125 /// Number of dependents to consider "high afferent coupling"
126 pub max_dependents: usize,
127 /// Maximum functions before flagging God Module
128 pub max_functions: usize,
129 /// Maximum types before flagging God Module
130 pub max_types: usize,
131 /// Maximum implementations before flagging God Module
132 pub max_impls: usize,
133 /// Minimum primitive parameter count for Primitive Obsession
134 pub min_primitive_params: usize,
135 /// Strict mode: only show Medium/High/Critical issues
136 pub strict_mode: bool,
137 /// Show explanations in Japanese
138 pub japanese: bool,
139 /// Exclude test code from function counts
140 pub exclude_tests: bool,
141 /// Prelude module patterns (for reporting purposes)
142 pub prelude_module_count: usize,
143}
144
145impl Default for IssueThresholds {
146 fn default() -> Self {
147 Self {
148 strong_coupling: 0.75, // Functional strength or higher (was 0.5)
149 far_distance: 0.5, // DifferentModule or higher
150 high_volatility: 0.75, // High volatility only (was 0.5)
151 max_dependencies: 20, // More than 20 outgoing dependencies (was 15)
152 max_dependents: 30, // More than 30 incoming dependencies (was 20)
153 max_functions: 30, // More than 30 functions = God Module
154 max_types: 15, // More than 15 types = God Module
155 max_impls: 20, // More than 20 implementations = God Module
156 min_primitive_params: 3, // 3+ primitive params = Primitive Obsession
157 strict_mode: true, // Show only important issues by default
158 japanese: false, // English by default
159 exclude_tests: false, // Include test code by default
160 prelude_module_count: 0, // No prelude modules configured
161 }
162 }
163}