#!/usr/bin/env python3 """ Compare cyclomatic complexity of code before and after changes. Helps identify if refactoring actually simplifies code structure. """ import re import sys class ComplexityAnalyzer: """Analyze code complexity metrics.""" def __init__(self, code: str): self.code = code self.lines = code.split("\n") def calculate_cyclomatic_complexity(self) -> int: """ Calculate cyclomatic complexity using McCabe's method. Count decision points: if, elif, else, for, while, except, and, or """ complexity = 1 # Base complexity # Count decision points decision_patterns = [ r"\bif\b", r"\belif\b", r"\bfor\b", r"\bwhile\b", r"\bexcept\b", r"\band\b(?!$)", r"\bor\b(?!$)", ] for pattern in decision_patterns: matches = re.findall(pattern, self.code) complexity += len(matches) return complexity def calculate_cognitive_complexity(self) -> int: """ Calculate cognitive complexity - how hard is it to understand? Based on nesting depth and control flow. """ cognitive = 0 nesting_depth = 0 for line in self.lines: # Track nesting depth if re.search(r"^\s*(if|for|while|def|class|try)\b", line): nesting_depth += 1 cognitive += nesting_depth elif re.search(r"^\s*(elif|else|except|finally)\b", line): cognitive += nesting_depth # Reduce nesting when unindenting if line and not line[0].isspace(): nesting_depth = 0 return cognitive def calculate_maintainability_index(self) -> float: """ Maintainability Index ranges from 0-100. > 85: Excellent > 65: Good > 50: Fair < 50: Poor """ lines = len(self.lines) cyclomatic = self.calculate_cyclomatic_complexity() cognitive = self.calculate_cognitive_complexity() # Simplified MI calculation mi = ( 171 - 5.2 * (cyclomatic / lines) - 0.23 * (cognitive) - 16.2 * (lines / 1000) ) return max(0, min(100, mi)) def get_complexity_report(self) -> dict: """Generate comprehensive complexity report.""" return { "cyclomatic_complexity": self.calculate_cyclomatic_complexity(), "cognitive_complexity": self.calculate_cognitive_complexity(), "maintainability_index": round(self.calculate_maintainability_index(), 2), "lines_of_code": len(self.lines), "avg_line_length": round( sum(len(l) for l in self.lines) / len(self.lines), 2 ) if self.lines else 0, } def compare_files(before_file: str, after_file: str) -> None: """Compare complexity metrics between two code versions.""" with open(before_file) as f: before_code = f.read() with open(after_file) as f: after_code = f.read() before_analyzer = ComplexityAnalyzer(before_code) after_analyzer = ComplexityAnalyzer(after_code) before_metrics = before_analyzer.get_complexity_report() after_metrics = after_analyzer.get_complexity_report() print("=" * 60) print("CODE COMPLEXITY COMPARISON") print("=" * 60) print("\nBEFORE:") print(f" Cyclomatic Complexity: {before_metrics['cyclomatic_complexity']}") print(f" Cognitive Complexity: {before_metrics['cognitive_complexity']}") print(f" Maintainability Index: {before_metrics['maintainability_index']}") print(f" Lines of Code: {before_metrics['lines_of_code']}") print(f" Avg Line Length: {before_metrics['avg_line_length']}") print("\nAFTER:") print(f" Cyclomatic Complexity: {after_metrics['cyclomatic_complexity']}") print(f" Cognitive Complexity: {after_metrics['cognitive_complexity']}") print(f" Maintainability Index: {after_metrics['maintainability_index']}") print(f" Lines of Code: {after_metrics['lines_of_code']}") print(f" Avg Line Length: {after_metrics['avg_line_length']}") print("\nCHANGES:") cyclomatic_change = ( after_metrics["cyclomatic_complexity"] - before_metrics["cyclomatic_complexity"] ) cognitive_change = ( after_metrics["cognitive_complexity"] - before_metrics["cognitive_complexity"] ) mi_change = ( after_metrics["maintainability_index"] - before_metrics["maintainability_index"] ) loc_change = after_metrics["lines_of_code"] - before_metrics["lines_of_code"] print(f" Cyclomatic Complexity: {cyclomatic_change:+d}") print(f" Cognitive Complexity: {cognitive_change:+d}") print(f" Maintainability Index: {mi_change:+.2f}") print(f" Lines of Code: {loc_change:+d}") print("\nASSESSMENT:") if mi_change > 0: print(" ✅ Code is MORE maintainable") elif mi_change < 0: print(" ⚠️ Code is LESS maintainable") else: print(" ➡️ Maintainability unchanged") if cyclomatic_change < 0: print(" ✅ Complexity DECREASED") elif cyclomatic_change > 0: print(" ⚠️ Complexity INCREASED") else: print(" ➡️ Complexity unchanged") print("=" * 60) if __name__ == "__main__": if len(sys.argv) != 3: print("Usage: python compare-complexity.py ") sys.exit(1) compare_files(sys.argv[1], sys.argv[2])