Files
claude-howto/03-skills/code-review/scripts/compare-complexity.py
T
Luong NGUYEN 540508f392 ci: Add DevOps quality assurance with pre-commit hooks and GitHub Actions
- Add pre-commit hooks: Ruff lint/format, Bandit security scan, YAML/TOML validation
- Add GitHub Actions CI workflow: lint, security, test, and build jobs
- Configure Ruff and Bandit in pyproject.toml
- Add pytest test suite for build_epub.py (25 tests)
- Fix code issues: exception chaining, httpx timeout, formatting
- Add requirements.txt and requirements-dev.txt
2025-12-10 23:49:52 +01:00

175 lines
5.6 KiB
Python

#!/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 <before_file> <after_file>")
sys.exit(1)
compare_files(sys.argv[1], sys.argv[2])