Files
Projektarbeit-MYP/backend/function_analysis_tool.py
Till Tomczak 7a99af7ace 🎉 Feat: Import & Function Analysis Tool Enhancements 🎉
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
2025-06-19 18:13:18 +02:00

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()