Files
PIC-Simu/luca.qmd
2025-05-28 23:21:48 +02:00

511 lines
15 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
lang: de
format:
pdf:
toc: true
number-sections: true
colorlinks: true
crossref:
custom:
- kind: float
reference-prefix: UML-Diagramm
key: uml
latex-env: uml
latex-list-of-description: UML-Diagramme
---
# 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
# Anwendung von Programmierprinzipien im Projekt
## 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 Dont 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
}