This commit introduces a suite of tools for analyzing and optimizing imports and functions within the backend codebase. The following files have been updated: - backend/FRONTEND_ASSETS_ANALYSE.md - backend/REDUNDANZ_ANALYSE_FINAL.md - backend/SOFORT_L\303\226SCHBARE_FUN
376 lines
15 KiB
Python
376 lines
15 KiB
Python
#!/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() |