Skip to main content

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}