#!/usr/bin/env python3 """ Automatisiertes Tool zur Analyse redundanter und ungenutzter Funktionen Analysiert Python-Dateien auf: 1. Funktionen die nie aufgerufen werden (Dead Code) 2. Doppelte/ähnliche Funktionen 3. Überflüssige Hilfsfunktionen 4. Veraltete Funktionen """ import os import ast import re import json from collections import defaultdict from datetime import datetime class FunctionAnalyzer: def __init__(self, base_path): self.base_path = base_path self.functions = {} # Alle gefundenen Funktionen self.calls = defaultdict(set) # Funktionsaufrufe self.imports = defaultdict(set) # Import-Statements self.similar_functions = [] # Ähnliche Funktionen def analyze_file(self, file_path): """Analysiert eine einzelne Python-Datei""" try: with open(file_path, 'r', encoding='utf-8') as f: content = f.read() tree = ast.parse(content) # Relative Path für bessere Lesbarkeit rel_path = os.path.relpath(file_path, self.base_path) # Funktionen extrahieren for node in ast.walk(tree): if isinstance(node, ast.FunctionDef): func_name = node.name key = f"{rel_path}:{func_name}" # Funktions-Metadaten sammeln self.functions[key] = { 'name': func_name, 'file': rel_path, 'line': node.lineno, 'args': [arg.arg for arg in node.args.args], 'docstring': ast.get_docstring(node), 'is_private': func_name.startswith('_'), 'is_dunder': func_name.startswith('__') and func_name.endswith('__'), 'body_lines': len(node.body), 'decorators': [d.id if isinstance(d, ast.Name) else str(d) for d in node.decorator_list] } # Funktionsaufrufe extrahieren elif isinstance(node, ast.Call): if isinstance(node.func, ast.Name): self.calls[rel_path].add(node.func.id) elif isinstance(node.func, ast.Attribute): self.calls[rel_path].add(node.func.attr) # Import-Statements extrahieren elif isinstance(node, ast.Import): for alias in node.names: self.imports[rel_path].add(alias.name) elif isinstance(node, ast.ImportFrom): if node.module: for alias in node.names: self.imports[rel_path].add(f"{node.module}.{alias.name}") except Exception as e: print(f"Fehler beim Analysieren von {file_path}: {e}") def find_unused_functions(self): """Findet ungenutzte Funktionen""" unused = [] all_calls = set() # Alle Funktionsaufrufe sammeln for file_calls in self.calls.values(): all_calls.update(file_calls) for key, func in self.functions.items(): func_name = func['name'] # Spezielle Funktionen ausschließen if (func['is_dunder'] or # __init__, __str__, etc. func_name in ['main', 'create_app'] or # Entry points any(d in ['app.route', 'login_required', 'admin_required'] for d in func['decorators']) or # Route handlers func_name.startswith('test_')): # Test-Funktionen continue # Prüfe ob Funktion aufgerufen wird if func_name not in all_calls: unused.append({ 'key': key, 'name': func_name, 'file': func['file'], 'line': func['line'], 'is_private': func['is_private'], 'body_lines': func['body_lines'], 'docstring': func['docstring'][:100] if func['docstring'] else None }) return unused def find_similar_functions(self, threshold=0.7): """Findet ähnliche Funktionen basierend auf Namen und Argumenten""" similar = [] functions_list = list(self.functions.items()) for i, (key1, func1) in enumerate(functions_list): for key2, func2 in functions_list[i+1:]: similarity = self._calculate_similarity(func1, func2) if similarity >= threshold: similar.append({ 'func1': {'key': key1, 'name': func1['name'], 'file': func1['file'], 'line': func1['line']}, 'func2': {'key': key2, 'name': func2['name'], 'file': func2['file'], 'line': func2['line']}, 'similarity': similarity, 'reason': self._get_similarity_reason(func1, func2) }) return similar def find_redundant_helpers(self): """Findet überflüssige Hilfsfunktionen die nur einmal verwendet werden""" redundant = [] call_counts = defaultdict(int) # Zähle Funktionsaufrufe for file_calls in self.calls.values(): for call in file_calls: call_counts[call] += 1 for key, func in self.functions.items(): func_name = func['name'] # Nur Hilfsfunktionen betrachten (private oder kurze Funktionen) if (func['is_private'] or func['body_lines'] <= 5) and not func['is_dunder']: usage_count = call_counts.get(func_name, 0) if usage_count <= 1: # Nur einmal oder gar nicht verwendet redundant.append({ 'key': key, 'name': func_name, 'file': func['file'], 'line': func['line'], 'usage_count': usage_count, 'body_lines': func['body_lines'], 'is_private': func['is_private'] }) return redundant def _calculate_similarity(self, func1, func2): """Berechnet Ähnlichkeit zwischen zwei Funktionen""" score = 0.0 # Namen-Ähnlichkeit (Levenshtein-ähnlich) name_similarity = self._string_similarity(func1['name'], func2['name']) score += name_similarity * 0.4 # Argument-Ähnlichkeit args1, args2 = set(func1['args']), set(func2['args']) if args1 or args2: arg_similarity = len(args1 & args2) / len(args1 | args2) score += arg_similarity * 0.3 # Zeilen-Ähnlichkeit line_diff = abs(func1['body_lines'] - func2['body_lines']) if max(func1['body_lines'], func2['body_lines']) > 0: line_similarity = 1 - (line_diff / max(func1['body_lines'], func2['body_lines'])) score += line_similarity * 0.3 return score def _string_similarity(self, s1, s2): """Einfache String-Ähnlichkeit""" if not s1 or not s2: return 0.0 # Gemeinsame Zeichen common = len(set(s1.lower()) & set(s2.lower())) total = len(set(s1.lower()) | set(s2.lower())) return common / total if total > 0 else 0.0 def _get_similarity_reason(self, func1, func2): """Gibt den Grund für die Ähnlichkeit zurück""" reasons = [] if self._string_similarity(func1['name'], func2['name']) > 0.7: reasons.append("ähnliche Namen") if set(func1['args']) & set(func2['args']): reasons.append("gemeinsame Argumente") if abs(func1['body_lines'] - func2['body_lines']) <= 2: reasons.append("ähnliche Länge") return ", ".join(reasons) if reasons else "unbekannt" def analyze_project(self): """Analysiert das gesamte Projekt""" print(f"Analysiere Projekt: {self.base_path}") # Alle Python-Dateien finden python_files = [] for root, dirs, files in os.walk(self.base_path): # Bestimmte Verzeichnisse ausschließen dirs[:] = [d for d in dirs if d not in ['.git', '__pycache__', 'node_modules', '.pytest_cache']] for file in files: if file.endswith('.py') and not file.startswith('.'): python_files.append(os.path.join(root, file)) print(f"Gefundene Python-Dateien: {len(python_files)}") # Jede Datei analysieren for file_path in python_files: self.analyze_file(file_path) print(f"Analysierte Funktionen: {len(self.functions)}") # Verschiedene Analysen durchführen unused = self.find_unused_functions() similar = self.find_similar_functions() redundant = self.find_redundant_helpers() return { 'summary': { 'total_files': len(python_files), 'total_functions': len(self.functions), 'unused_functions': len(unused), 'similar_functions': len(similar), 'redundant_helpers': len(redundant) }, 'unused_functions': unused, 'similar_functions': similar, 'redundant_helpers': redundant, 'all_functions': self.functions } def generate_report(self, output_file=None): """Generiert einen detaillierten Bericht""" results = self.analyze_project() report = { 'analysis_date': datetime.now().isoformat(), 'project_path': self.base_path, 'summary': results['summary'], 'findings': { 'unused_functions': results['unused_functions'], 'similar_functions': results['similar_functions'], 'redundant_helpers': results['redundant_helpers'] }, 'recommendations': self._generate_recommendations(results) } if output_file: with open(output_file, 'w', encoding='utf-8') as f: json.dump(report, f, indent=2, ensure_ascii=False) print(f"Bericht gespeichert: {output_file}") return report def _generate_recommendations(self, results): """Generiert Empfehlungen basierend auf den Analyseergebnissen""" recommendations = [] # Ungenutzte Funktionen unused = results['unused_functions'] if unused: high_priority = [f for f in unused if not f['is_private'] and f['body_lines'] > 10] if high_priority: recommendations.append({ 'type': 'high_priority_removal', 'description': f"Entfernen Sie {len(high_priority)} ungenutzte öffentliche Funktionen", 'functions': [f['key'] for f in high_priority] }) low_priority = [f for f in unused if f['is_private'] or f['body_lines'] <= 10] if low_priority: recommendations.append({ 'type': 'low_priority_removal', 'description': f"Prüfen Sie {len(low_priority)} ungenutzte private/kleine Funktionen", 'functions': [f['key'] for f in low_priority] }) # Ähnliche Funktionen similar = results['similar_functions'] if similar: high_similarity = [s for s in similar if s['similarity'] > 0.8] if high_similarity: recommendations.append({ 'type': 'consolidation', 'description': f"Konsolidieren Sie {len(high_similarity)} sehr ähnliche Funktionen", 'pairs': [(s['func1']['key'], s['func2']['key']) for s in high_similarity] }) # Redundante Hilfsfunktionen redundant = results['redundant_helpers'] if redundant: unused_helpers = [r for r in redundant if r['usage_count'] == 0] if unused_helpers: recommendations.append({ 'type': 'helper_removal', 'description': f"Entfernen Sie {len(unused_helpers)} ungenutzte Hilfsfunktionen", 'functions': [h['key'] for h in unused_helpers] }) return recommendations def main(): """Hauptfunktion für die Analyse""" base_path = "/cbin/C0S1-cernel/C02L2/Dateiverwaltung/nextcloud/core/files/3_Beruf_Ausbildung_und_Schule/IHK-Abschlussprüfung/Projektarbeit-MYP/backend" analyzer = FunctionAnalyzer(base_path) # Analyse durchführen und Bericht generieren output_file = os.path.join(base_path, 'function_analysis_report.json') report = analyzer.generate_report(output_file) # Zusammenfassung ausgeben print("\n" + "="*80) print("FUNKTIONSANALYSE - ZUSAMMENFASSUNG") print("="*80) summary = report['summary'] print(f"📁 Analysierte Dateien: {summary['total_files']}") print(f"⚙️ Gefundene Funktionen: {summary['total_functions']}") print(f"💀 Ungenutzte Funktionen: {summary['unused_functions']}") print(f"👯 Ähnliche Funktionen: {summary['similar_functions']}") print(f"🔧 Redundante Hilfsfunktionen: {summary['redundant_helpers']}") # Top-Findings ausgeben print("\n" + "-"*60) print("TOP UNGENUTZTE FUNKTIONEN (Kandidaten für Löschung)") print("-"*60) unused = report['findings']['unused_functions'] unused_sorted = sorted(unused, key=lambda x: (not x['is_private'], x['body_lines']), reverse=True) for i, func in enumerate(unused_sorted[:10]): status = "🔴 ÖFFENTLICH" if not func['is_private'] else "🟡 PRIVAT" print(f"{i+1:2d}. {status} {func['file']}:{func['line']} - {func['name']}() ({func['body_lines']} Zeilen)") # Ähnliche Funktionen print("\n" + "-"*60) print("ÄHNLICHE FUNKTIONEN (Kandidaten für Konsolidierung)") print("-"*60) similar = report['findings']['similar_functions'] similar_sorted = sorted(similar, key=lambda x: x['similarity'], reverse=True) for i, pair in enumerate(similar_sorted[:5]): similarity_percent = int(pair['similarity'] * 100) print(f"{i+1}. {similarity_percent}% Ähnlichkeit ({pair['reason']})") print(f" 📍 {pair['func1']['file']}:{pair['func1']['line']} - {pair['func1']['name']}()") print(f" 📍 {pair['func2']['file']}:{pair['func2']['line']} - {pair['func2']['name']}()") print() # Empfehlungen print("-"*60) print("EMPFEHLUNGEN") print("-"*60) for rec in report['recommendations']: print(f"🎯 {rec['type'].upper()}: {rec['description']}") print(f"\n📄 Detaillierter Bericht: {output_file}") if __name__ == "__main__": main()