#!/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 from typing import Dict, Tuple 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, 'r') as f: before_code = f.read() with open(after_file, 'r') 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])