--- title: "Programmentwurf AdvancedSoftwareEngineering" author: - Luca Müller - Paul Martin date: 05/31/2025 date-format: "DD.MM.YYYY" lang: de format: pdf: toc: true number-sections: true colorlinks: true --- # Code Smell 1: Long Method ## Beschreibung des Problems Das **Long Method** Anti-Pattern tritt auf, wenn eine einzelne Methode zu viele Zeilen Code enthält und multiple Verantwortlichkeiten übernimmt. Eine solche Methode verstößt gegen das **Single Responsibility Principle (SRP)** und führt zu verschiedenen Problemen: - **Schwere Verständlichkeit**: Entwickler müssen viel Zeit aufwenden, um die gesamte Methode zu durchdringen - **Reduzierte Testbarkeit**: Verschiedene Logikbereiche können nicht isoliert getestet werden - **Mangelnde Wiederverwendbarkeit**: Teilfunktionalitäten sind nicht separat nutzbar - **Erhöhte Fehleranfälligkeit**: Bugs sind schwerer zu lokalisieren und zu beheben ## Praktisches Beispiel Die folgende Methode `stopRunFromBackend()` zeigt ein typisches Long Method Problem: ### Vorher (Problematischer Code) ```java public static void stopRunFromBackend(String message){ // Zustandsänderungen isAutoRunActive = false; if (isSleeping) wakeUpFromSleep(); else DataRegister.resetPC(); // UI-Erstellung und -Konfiguration Stage stoppedStage = new Stage(); stoppedStage.setTitle("Programm unterbrochen!"); VBox vbox = new VBox(); vbox.setAlignment(javafx.geometry.Pos.CENTER); Label grundlabel = new Label("Grund: " + message); grundlabel.setStyle("-fx-font-size: 16px; -fx-font-weight: bold;"); Label ueberlabel = new Label("Programm unterbrochen!"); vbox.getChildren().add(ueberlabel); vbox.getChildren().add(grundlabel); VBox.setMargin(grundlabel, new javafx.geometry.Insets(10, 10, 10, 10)); Scene scene = new Scene(vbox, 300, 90); stoppedStage.setAlwaysOnTop(true); stoppedStage.setScene(scene); stoppedStage.show(); } ``` ### Identifizierte Probleme - **Vermischte Verantwortlichkeiten**: Die Methode kombiniert Geschäftslogik (Zustandsänderungen) mit UI-Code - **Schwere Testbarkeit**: UI-Code kann nicht isoliert von der Geschäftslogik getestet werden - **Code-Duplikation**: Wird die Dialog-Funktionalität anderswo benötigt, muss der gesamte UI-Code kopiert werden ## Lösung: Extract Method Refactoring Die **Extract Method** Technik löst das Problem durch Aufspaltung der langen Methode in kleinere, fokussierte Methoden: ### Nachher (Refactorierter Code) ```java public static void stopRunFromBackend(String message){ handleExecutionStop(); showInterruptionDialog(message); } private static void handleExecutionStop(){ isAutoRunActive = false; if (isSleeping) wakeUpFromSleep(); else DataRegister.resetPC(); } private static void showInterruptionDialog(String message){ Stage stoppedStage = new Stage(); stoppedStage.setTitle("Programm unterbrochen!"); VBox vbox = new VBox(); vbox.setAlignment(javafx.geometry.Pos.CENTER); Label ueberlabel = new Label("Programm unterbrochen!"); Label grundlabel = new Label("Grund: " + message); grundlabel.setStyle("-fx-font-size: 16px; -fx-font-weight: bold;"); vbox.getChildren().addAll(ueberlabel, grundlabel); VBox.setMargin(grundlabel, new javafx.geometry.Insets(10, 10, 10, 10)); Scene scene = new Scene(vbox, 300, 90); stoppedStage.setAlwaysOnTop(true); stoppedStage.setScene(scene); stoppedStage.show(); } ``` ## Vorteile der Lösung - **Klare Trennung der Verantwortlichkeiten**: Geschäftslogik und UI-Code sind getrennt - **Verbesserte Lesbarkeit**: Jede Methode hat einen klaren, fokussierten Zweck - **Erhöhte Wiederverwendbarkeit**: `showInterruptionDialog()` kann in anderen Kontexten genutzt werden - **Bessere Testbarkeit**: Geschäftslogik und UI können separat getestet werden --- # Code Smell 2: Large Class ## Beschreibung des Problems Eine **Large Class** entsteht, wenn eine Klasse zu viele Verantwortlichkeiten übernimmt und dadurch überladen wird. Typische Kennzeichen sind: - **Hohe Anzahl an Instanzvariablen**: Die Klasse verwaltet zu viele verschiedene Datentypen - **Viele Methoden**: Unterschiedliche Funktionsbereiche werden in einer Klasse gemischt - **Multiple Domänenaspekte**: Logik, Darstellung, Benutzerinteraktion und Statusverwaltung in einer Klasse ## Praktisches Beispiel Die Klasse `Controller_Frontend` zeigt typische Large Class Probleme: ### Identifizierte Probleme ```java public class Controller_Frontend { // GUI-Elemente @FXML private Button startButton; @FXML private Button pauseButton; @FXML private Label statusLabel; // Zustandsverwaltung (gehört nicht hierher!) private static boolean isAutoRunActive = false; private static boolean isSleeping = false; private static double executionTimeMultiplier = 1.0; // GUI-Steuerung public void handleStart() { /* ... */ } public void handlePause() { /* ... */ } // Zustandslogik (gehört nicht hierher!) public void sleep() { isSleeping = true; } public void wakeUpFromSleep() { isSleeping = false; } // ... viele weitere Methoden } ``` ### Auswirkungen - **Schwere Wartbarkeit**: Änderungen in einem Bereich können unbeabsichtigte Nebeneffekte verursachen - **Reduzierte Testbarkeit**: Verschiedene Aspekte können nicht isoliert getestet werden - **Unübersichtlichkeit**: Die Klasse wird schnell unhandlich und schwer verständlich - **Violierung des Single Responsibility Principle**: Eine Klasse sollte nur einen Grund zur Änderung haben ## Lösung: Extract Class Refactoring Die Lösung besteht in der Auslagerung der Zustandsverwaltung in eine separate, spezialisierte Klasse: ### Neue ExecutionState Klasse ```java /** * Zentrale Verwaltung aller Ausführungszustände. * Diese Klasse kapselt alle zustandsbezogenen Operationen * und bietet eine saubere API für den Zugriff darauf. */ public class ExecutionState { private static boolean isAutoRunActive = false; private static boolean isSleeping = false; private static double executionTimeMultiplier = 1.0; // AutoRun-Zustand public static boolean isAutoRunActive() { return isAutoRunActive; } public static void setAutoRunActive(boolean active) { isAutoRunActive = active; } // Sleep-Zustand public static boolean isSleeping() { return isSleeping; } public static void sleep() { isSleeping = true; } public static void wakeUp() { isSleeping = false; } // Ausführungsgeschwindigkeit public static double getExecutionTimeMultiplier() { return executionTimeMultiplier; } public static void setExecutionTimeMultiplier(double multiplier) { if (multiplier > 0) { executionTimeMultiplier = multiplier; } } // Hilfsmethoden public static void reset() { isAutoRunActive = false; isSleeping = false; executionTimeMultiplier = 1.0; } } ``` ### Refactorierte Controller Klasse ```java public class Controller_Frontend { // Nur noch GUI-Elemente @FXML private Button startButton; @FXML private Button pauseButton; @FXML private Label statusLabel; // Verwendung der ExecutionState Klasse public void stopRunFromBackend(String message) { ExecutionState.setAutoRunActive(false); if (ExecutionState.isSleeping()) { ExecutionState.wakeUp(); } else { DataRegister.resetPC(); } showInterruptionDialog(message); } // Weitere GUI-Methoden... } ``` ## Vorteile der Lösung - **Klare Verantwortlichkeiten**: Controller fokussiert sich auf GUI, ExecutionState auf Zustandsverwaltung - **Verbesserte Testbarkeit**: Zustandslogik kann isoliert getestet werden - **Erhöhte Wiederverwendbarkeit**: ExecutionState kann in anderen Klassen genutzt werden - **Bessere Wartbarkeit**: Änderungen an der Zustandslogik betreffen nur eine Klasse --- # Code Smell 3: Shotgun Surgery ## Beschreibung des Problems **Shotgun Surgery** tritt auf, wenn eine kleine fachliche Änderung Modifikationen an vielen verschiedenen Stellen im Code erfordert. Dieses Anti-Pattern entsteht durch: - **Zu starke Verteilung**: Zusammengehörige Funktionalität ist über viele Klassen und Methoden verstreut - **Mangelnde Kapselung**: Ähnliche Operationen sind nicht zentral gebündelt - **Duplizierte Logik**: Gleiche oder ähnliche Code-Fragmente existieren an mehreren Stellen ## Praktisches Beispiel Die Schlafmodus-Funktionalität war ursprünglich über mehrere Bereiche verteilt: ### Vor dem Refactoring (Problematische Verteilung) ```java // In Controller_Frontend private boolean isSleeping = false; public void enterSleepMode() { isSleeping = true; // Logging hier System.out.println("Entering sleep mode"); } // In ExecutionEngine private boolean sleepState = false; public void pauseExecution() { sleepState = true; // Ähnliches Logging dort System.out.println("Execution paused - sleep mode"); } // In StatusManager public void checkSleepStatus() { if (someCondition) { // Wieder ähnlicher Code setSleeping(true); System.out.println("Sleep mode activated"); } } ``` ### Identifizierte Probleme - **Mehrfache Implementierung**: Sleep-Logik existiert in verschiedenen Varianten - **Inkonsistente Zustände**: Verschiedene Klassen können unterschiedliche Sleep-Zustände haben - **Hoher Änderungsaufwand**: Neue Sleep-Features müssen an mehreren Stellen implementiert werden - **Fehleranfälligkeit**: Beim Hinzufügen neuer Funktionen können leicht Stellen vergessen werden ## Lösung: Zentralisierung durch Extract Class Die Lösung besteht in der Konsolidierung aller sleep-bezogenen Operationen in der `ExecutionState` Klasse: ### Zentralisierte Lösung ```java public class ExecutionState { private static boolean isSleeping = false; private static final Logger logger = LoggerFactory.getLogger(ExecutionState.class); /** * Aktiviert den Schlafmodus mit einheitlichem Logging und Zustandsmanagement */ public static void sleep() { if (!isSleeping) { isSleeping = true; logger.info("Sleep mode activated"); notifyStateChange("SLEEP_ACTIVATED"); } } /** * Deaktiviert den Schlafmodus */ public static void wakeUp() { if (isSleeping) { isSleeping = false; logger.info("Waking up from sleep mode"); notifyStateChange("SLEEP_DEACTIVATED"); } } /** * Überprüft den aktuellen Schlafzustand */ public static boolean isSleeping() { return isSleeping; } /** * Erweiterte Sleep-Funktionalität mit Timeout */ public static void sleepWithTimeout(long milliseconds) { sleep(); Timer timer = new Timer(); timer.schedule(new TimerTask() { @Override public void run() { wakeUp(); } }, milliseconds); } private static void notifyStateChange(String event) { // Zentrale Benachrichtigung für alle interessierten Komponenten EventBus.getInstance().publish(new StateChangeEvent(event)); } } ``` ### Vereinfachte Nutzung in anderen Klassen ```java // In Controller_Frontend public void handleSleepButton() { ExecutionState.sleep(); updateUI(); } // In ExecutionEngine public void pauseIfNeeded() { if (shouldPause()) { ExecutionState.sleep(); } } // In StatusManager public void checkAndActivateSleep() { if (criticalCondition()) { ExecutionState.sleepWithTimeout(5000); // 5 Sekunden Sleep } } ``` ## Vorteile der Lösung - **Einheitliche Implementierung**: Alle sleep-bezogenen Operationen verwenden dieselbe Logik - **Zentrale Wartung**: Änderungen müssen nur an einer Stelle vorgenommen werden - **Konsistente Zustände**: Nur eine Quelle der Wahrheit für den Sleep-Zustand - **Erweiterte Funktionalität**: Neue Features (wie Timeout) können zentral hinzugefügt werden - **Bessere Nachverfolgbarkeit**: Einheitliches Logging und Event-System --- --- title: "Anwendung von Programmierprinzipien im Projekt" format: html --- ## Einleitung Im Rahmen der Refaktorisierung und Weiterentwicklung des Projekts wurde besonderer Fokus auf die Einhaltung zentraler Programmierprinzipien gelegt. Die folgenden Prinzipien wurden gezielt analysiert und angewendet: ## 1. SOLID-Prinzipien ### Single Responsibility Principle (SRP) Die Methode `stopRunFromBackend()` wurde in zwei unabhängige Methoden aufgeteilt: ```java public void stopRunFromBackend(String message) { ExecutionState.setAutoRunActive(false); handleSleepOrReset(); showStopDialog(message); } private void handleSleepOrReset() { if (ExecutionState.isSleeping()) { ExecutionState.wakeUp(); } else { dataRegister.resetPC(); } } private static void showStopDialog(String message) { // GUI-Erzeugung... } ``` ### Open/Closed Principle (OCP) Die Klasse `ExecutionState` kapselt erweiterbare Zustandslogik, ohne dass bestehende Methoden geändert werden müssen: ```java public class ExecutionState { private static boolean isAutoRunActive; private static boolean isSleeping; public static boolean isSleeping() { return isSleeping; } public static void wakeUp() { isSleeping = false; } public static void setAutoRunActive(boolean value) { isAutoRunActive = value; } } ``` ### Liskov Substitution Principle (LSP) `Controller_Frontend` implementiert ein Interface, wodurch die Substituierbarkeit gemäß LSP gewährleistet ist: ```java public class Controller_Frontend extends PICComponent implements FrontendControllerInterface { } public void resetPC() { programCounter = 0; } ``` ### Interface Segregation Principle (ISP) Spezialisierte Interfaces sorgen dafür, dass Klassen nur relevante Funktionen implementieren: ```java public interface TimerInterface { void start(); void stop(); int getCurrentTime(); } ``` ### Dependency Inversion Principle (DIP) Zentrale Komponenten wie `PICComponentLocator` und Interfaces lösen die Abhängigkeit von konkreten Klassen: ```java private PICComponentLocator picComponents; ``` ## 2. GRASP-Prinzipien ### Low Coupling Die Kopplung im Projekt ist durch Verwendung zentraler Zugriffsklassen wie `PICComponentLocator` gering gehalten. ```java private PICComponentLocator picComponents; ``` ### High Cohesion Funktionen übernehmen jeweils thematisch zusammenhängende Aufgaben: ```java public static void showStopDialog(String message) { Stage stoppedStage = new Stage(); VBox vbox = new VBox(); Label grundlabel = new Label("Grund: " + message); // GUI-Details ausgelassen stoppedStage.show(); } ``` ## 3. DRY – Don’t Repeat Yourself Wiederverwendbare Logik zur Statuskontrolle wurde in `ExecutionState` gekapselt. GUI-Erzeugung findet zentral in `showStopDialog()` statt: ```java public class ExecutionState { private static boolean isSleeping; public static boolean isSleeping() { return isSleeping; } public static void wakeUp() { isSleeping = false; } } private static void showStopDialog(String message) { // einmal zentral definierte GUI-Logik }