Advanced Design and Programming (5-ECTS) - Exam.pdf

Advanced Design and Programming (5-ECTS) - Exam
Advanced Design and Programming (5-ECTS) - Exam Aufgabe 1) In der Softwareentwicklung spielen Entwurfsmuster eine wesentliche Rolle, um wiederkehrende Probleme effizient zu lösen. Angenommen, Du arbeitest in einem Projekt, das einen Cloud-basierten Dienst implementieren möchte, der eine Vielzahl von Anfragen von Benutzern in Echtzeit bearbeitet. Für dieses Projekt möchtest Du die Wiederverwendbark...

© StudySmarter 2024, all rights reserved.

Advanced Design and Programming (5-ECTS) - Exam

Aufgabe 1)

In der Softwareentwicklung spielen Entwurfsmuster eine wesentliche Rolle, um wiederkehrende Probleme effizient zu lösen. Angenommen, Du arbeitest in einem Projekt, das einen Cloud-basierten Dienst implementieren möchte, der eine Vielzahl von Anfragen von Benutzern in Echtzeit bearbeitet. Für dieses Projekt möchtest Du die Wiederverwendbarkeit und Wartbarkeit des Codes sicherstellen, indem Du geeignete Entwurfsmuster einsetzt.

a)

Beschreibe, wie das Singleton-Entwurfsmuster in Deinem Cloud-Dienst-Projekt angewendet werden könnte. Erläutere die Implementierung dieses Musters in einer Programmiersprache Deiner Wahl und begründe, warum dieses Muster für Deine Anwendung sinnvoll ist. Füge dabei sowohl Pseudocode als auch reale Codebeispiele ein.

class SingletonClass {    private static instance: SingletonClass;    private constructor() {        // Initialisierungscode    }    public static getInstance(): SingletonClass {        if (!SingletonClass.instance) {            SingletonClass.instance = new SingletonClass();        }        return SingletonClass.instance;    }}

Lösung:

Das Singleton-Entwurfsmuster im Cloud-Dienst-Projekt

In Deinem Cloud-Dienst-Projekt spielt die Effizienz und die Kontrolle über die Ressourcennutzung eine große Rolle. Ein häufiges Szenario, in dem das Singleton-Entwurfsmuster verwendet wird, ist die Verwaltung einer zentralen Ressource oder die Konfiguration, die nur einmalig initialisiert werden soll und dann in der gesamten Anwendung wiederverwendet wird.

Beschreibung des Singleton-Entwurfsmusters

  • Das Singleton-Entwurfsmuster stellt sicher, dass eine Klasse nur eine einzige Instanz hat und bietet einen global zugänglichen Zugriffspunkt auf diese Instanz.
  • Dies ist besonders in der Cloud-Diensteentwicklung nützlich, wenn Du sicherstellen möchtest, dass nur eine Instanz eines Dienstes oder einer Ressource erstellt wird und jeder Zugriff auf diese spezifische Instanz erfolgt.

Implementierung in TypeScript

Nachfolgend siehst Du eine Implementierung des Singleton-Entwurfsmusters in TypeScript:

class SingletonClass {    private static instance: SingletonClass;    private constructor() {        // Initialisierungscode    }    public static getInstance(): SingletonClass {        if (!SingletonClass.instance) {            SingletonClass.instance = new SingletonClass();        }        return SingletonClass.instance;    }}

Pseudocode

class SingletonClass {    private static instance: SingletonClass    private constructor() {        // Initialisierungscode    }    public static getInstance(): SingletonClass {        if instance is null then            instance = new SingletonClass()        end if        return instance    }}

Begründung für die Verwendung des Singleton-Entwurfsmusters

  • Zentralisierte Konfiguration: Bei der Implementierung eines Cloud-Dienstes ist es oft notwendig, eine einzige Instanz einer Konfigurationsklasse zu haben, die systemweite Einstellungen enthält.
  • Ressourcenkontrolle: Stelle sicher, dass bestimmte Arten von Ressourcen nur einmal instanziiert werden, wie z.B. Datenbankverbindungen oder Warteschlangenmechanismen.
  • Globale Zugänglichkeit: Ermögliche den Zugriff auf eine Instanz dieser Klasse von überall im Code, ohne weitere Initialisierungen durchzuführen.

Durch das Singleton-Entwurfsmuster kannst Du sicherstellen, dass Dein Cloud-Dienst effizient und übersichtlich bleibt. Es verhindert das versehentliche Erstellen mehrerer Instanzen und trägt daher zur besseren Wartbarkeit und Wiederverwendbarkeit des Codes bei.

b)

Analysiere die Vorteile und möglichen Nachteile des Factory-Entwurfsmusters in diesem Kontext. Implementiere ein Beispiel für das Factory-Entwurfsmuster, das in Deinem Cloud-Dienst die Erstellung unterschiedlicher Objekttypen übernimmt. Veranschauliche in Deinem Beispiel, wie diese Objekte zur Laufzeit erzeugt werden und wie dadurch die Flexibilität und Wartbarkeit des Systems gesteigert wird.

interface CloudService {    requestHandler(): void;}class RealTimeService implements CloudService {    requestHandler() {        console.log('Handling real-time request');    }}class BatchService implements CloudService {    requestHandler() {        console.log('Handling batch request');    }}class CloudServiceFactory {    static getService(serviceType: string): CloudService {        if (serviceType === 'RealTime') {            return new RealTimeService();        } else if (serviceType === 'Batch') {            return new BatchService();        } else {            throw new Error('Invalid service type');        }    }}

Lösung:

Analyse der Vorteile und möglichen Nachteile des Factory-Entwurfsmusters

Das Factory-Entwurfsmuster wird verwendet, um Objekte zu erzeugen, ohne die genaue Klasse des erzeugten Objekts anzugeben. Dies sorgt für Flexibilität und eine bessere Strukturierung des Codes.

Vorteile des Factory-Entwurfsmusters

  • Trennung von Erstellungslogik: Die Logik zur Erstellung von Objekten ist von der Logik getrennt, die diese Objekte verwendet. Dies führt zu einer besseren Code-Organisation und sorgt dafür, dass die Erstellungslogik zentral und einfach zu verwalten ist.
  • Flexibilität: Neue Objekttypen können leicht hinzugefügt werden, ohne den vorhandenen Code zu ändern. Dies erleichtert die Erweiterung des Systems.
  • Vermeidung von dupliziertem Code: Wiederverwendbare und einheitliche Objekt-Erstellung kann an einer zentralen Stelle verwaltet werden.
  • Einfachere Tests: Durch die Verwendung von Factory-Methoden wird die Erstellungslogik in einer dedizierten Stelle konzentriert, was das Mocking und Testing vereinfacht.

Mögliche Nachteile des Factory-Entwurfsmusters

  • Komplexität: Das Factory-Muster kann zur Komplexität des Designs führen, da zusätzliche Klassen und Methoden notwendig sind.
  • Vielzahl von Subklassen: Das Hinzufügen neuer Produktklassen erfordert häufig das Erstellen neuer Unterklassen für die Factory, was den Codeumfang erhöhen kann.

Implementierung eines Beispiels für das Factory-Entwurfsmuster

Hier ist ein Beispiel, wie das Factory-Muster in Deinem Cloud-Dienst angewendet werden könnte:

interface CloudService {    requestHandler(): void;}class RealTimeService implements CloudService {    requestHandler() {        console.log('Handling real-time request');    }}class BatchService implements CloudService {    requestHandler() {        console.log('Handling batch request');    }}class CloudServiceFactory {    static getService(serviceType: string): CloudService {        if (serviceType === 'RealTime') {            return new RealTimeService();        } else if (serviceType === 'Batch') {            return new BatchService();        } else {            throw new Error('Invalid service type');        }    }}

Funktionsweise und Vorteile in der Laufzeit

  • Die Factory-Methode (getService) wird verwendet, um abhängig vom serviceType die passende Instanz eines Cloud-Dienstes zu erstellen.
  • Zu Laufzeit können unterschiedliche Dienste flexibel erzeugt werden, indem der Typ des benötigten Dienstes als Parameter an die Factory-Methode übergeben wird.
  • Dies erhöht die Flexibilität, da neue Diensttypen leicht hinzugefügt werden können, ohne den bestehenden Code zu ändern.
  • Die Wartbarkeit wird verbessert, da die Objekterstellung an einer zentralen Stelle verwaltet wird und der Erstellungsprozess konsistent bleibt.

Beispiel für die Nutzung zur Laufzeit

// Nutzung der Factory zur Erstellung eines Dienstes zur Laufzeitconst serviceType = 'RealTime';const service = CloudServiceFactory.getService(serviceType);service.requestHandler(); // Ausgabe: Handling real-time request

Das obige Beispiel zeigt, wie leicht unterschiedliche Dienste erstellt und zur Laufzeit eingesetzt werden können. Dies steigert die Flexibilität und erleichtert die Wartung des gesamten Systems erheblich.

Aufgabe 2)

Design Patterns sind wiederverwendbare Lösungen für häufig auftretende Probleme in der Softwareentwicklung. Zu den wichtigsten Design Patterns zählen Singleton, Factory, Observer und Strategie.

  • Singleton: Dieses Muster stellt sicher, dass eine Klasse nur eine Instanz hat und bietet einen globalen Zugriffspunkt.
  • Factory: Ermöglicht die Erstellung von Objekten, ohne den genauen Klassennamen der zu erstellenden Objekte anzugeben.
  • Observer: Definiert ein 1-zu-N-Abhängigkeitsverhältnis zwischen Objekten, sodass bei einer Zustandsänderung eines Objekts alle abhängigen Objekte benachrichtigt und aktualisiert werden.
  • Strategie: Definiert eine Familie von Algorithmen, kapselt jeden Algorithmus und macht sie untereinander austauschbar.

a)

Angenommen, Du entwickelst ein Textverarbeitungsprogramm, das verschiedene Funktionalitäten wie Speichern, Druckvorbereitungen und Textformatierung umfasst. Bewerte, welches Design Pattern aus Singleton, Factory, Observer oder Strategie für die folgende Situation am besten geeignet ist und begründe Deine Wahl:

Scenario 1: Du möchtest sicherstellen, dass das Programm nur ein einziges Objekt für die Einstellungen (z.B. Benutzerpräferenzen, Konfigurationen) haben soll, so dass alle Teile des Programms dieselben Einstellungen verwenden.

Lösung:

In diesem Szenario, in dem Du sicherstellen möchtest, dass das Textverarbeitungsprogramm nur ein einziges Objekt für die Einstellungen (z.B. Benutzerpräferenzen, Konfigurationen) hat, ist das Singleton-Design Pattern die beste Wahl.

  • Singleton: Dieses Muster gewährleistet, dass eine Klasse nur eine einzige Instanz hat und bietet einen globalen Zugriffspunkt. Dadurch wird sichergestellt, dass alle Teile des Programms dieselben Einstellungen verwenden und es keine inkonsistenten Zustände gibt.
  • Begründung:

    • Mit einem Singleton-Pattern kannst Du sicherstellen, dass es eine zentrale Stelle für die Verwaltung der Einstellungen gibt.
    • Das Singleton-Muster verhindert, dass mehrere Objekte der Einstellungen-Klasse erstellt werden, was zu Inkonsistenzen führen könnte.
    • Es bietet einen globalen Zugriffspunkt, sodass Einstellungen von jedem Teil des Programms einfach abgerufen werden können.

    Ein Beispiel für die Implementierung eines Singleton-Patterns in Python könnte wie folgt aussehen:

 class Einstellungen:      _instance = None      def __new__(cls):          if cls._instance is None:              cls._instance = super(Einstellungen, cls).__new__(cls)          return cls._instance       def __init__(self):          self.benutzerpraeferenzen = {} 

Diese Klasse stellt sicher, dass nur eine Instanz von Einstellungen existiert und dieselbe Instanz in unterschiedlichen Teilen des Programms verwendet wird.

b)

Erstelle eine Implementierung des von Dir gewählten Design Patterns in einer Programmiersprache Deiner Wahl. Das Ziel ist es, eine Klasse Settings zu erstellen, die nur eine Instanz haben kann.

Mach sicher, dass dein Code auch Tests enthält um sicherzustellen, dass nur eine Instanz des Settings Objekts existiert.

Lösung:

Um das Singleton-Design Pattern zu implementieren, erstellen wir eine Klasse Settings in Python, die sicherstellt, dass nur eine Instanz existiert. Zudem werden wir Tests hinzufügen, um sicherzustellen, dass nur eine Instanz des Settings-Objekts existiert.

Hier ist der vollständige Code:

  class Settings:      _instance = None       def __new__(cls):          if cls._instance is None:              cls._instance = super(Settings, cls).__new__(cls)          return cls._instance       def __init__(self):          if not hasattr(self, 'initialized'):              self.benutzerpraeferenzen = {}              self.initialized = True   # Tests  def test_singleton():      instance1 = Settings()      instance2 = Settings()      assert instance1 is instance2, 'Fehler: Mehr als eine Instanz von Settings existiert.'      print('Test bestanden: Nur eine Instanz von Settings existiert.')   if __name__ == '__main__':      test_singleton()   

Erklärung:

  • Die Methode __new__ stellt sicher, dass nur eine Instanz der Settings-Klasse erstellt wird.
  • Die Methode __init__ initialisiert die Einstellungen nur beim ersten Erstellen des Objekts.
  • Der Test test_singleton überprüft, ob nur eine Instanz der Settings-Klasse existiert, indem er zwei Objekte erstellt und vergleicht, ob beide auf dieselbe Instanz verweisen.

Der Test wird am Ende des Skripts ausgeführt und gibt eine entsprechende Meldung aus, wenn der Test bestanden wurde.

Aufgabe 3)

Du arbeitest als Teil eines Softwareentwicklungsteams, das gerade die Prinzipien und Werte des Agilen Manifests übernommen hat. In den letzten Wochen hat das Team verschiedene Methoden angewendet, um agiler zu werden. Dabei sind jedoch einige Herausforderungen und Fragen aufgetaucht.

a)

(a) Beschreibe, wie das Prinzip 'Individuen und Interaktionen über Prozesse und Werkzeuge' in deinem Team umgesetzt wird. Welche konkreten Maßnahmen könnten hierbei ergriffen werden, um dieses Prinzip zu unterstützen? Gehe auf mindestens drei mögliche Maßnahmen ein.

Lösung:

Um das Prinzip 'Individuen und Interaktionen über Prozesse und Werkzeuge' in Deinem Softwareentwicklungsteam umzusetzen, können folgende konkrete Maßnahmen ergriffen werden:

  • Regelmäßige Stand-up Meetings: Diese kurzen, täglichen Besprechungen fördern die Kommunikation zwischen den Teammitgliedern, indem sie sicherstellen, dass jeder auf dem neuesten Stand der Dinge ist. Hierbei kann jedes Teammitglied seine aktuellen Aufgaben vorstellen, Herausforderungen diskutieren und Unterstützung einfordern.
  • Paarprogrammierung (Pair Programming): Diese Methode fördert die Zusammenarbeit und den Wissensaustausch zwischen den Teammitgliedern. Durch das Arbeiten in Zweiergruppen können Ideen ausgetauscht und Probleme schneller gelöst werden. Außerdem hilft es, Wissen zu verbreiten und die Codequalität zu verbessern.
  • Retrospektiven: Dies sind regelmäßige Meetings, in denen das Team reflektiert, was gut gelaufen ist und was verbessert werden kann. Solche Besprechungen fördern offene und ehrliche Kommunikation sowie kontinuierliche Verbesserung. Es ist eine Gelegenheit, die Zusammenarbeit und die Teamdynamik zu verbessern.

Durch diese Maßnahmen wird die Interaktion zwischen den Teammitgliedern gestärkt, und die individuellen Beiträge jedes Einzelnen werden gewürdigt, was im Gesamten zu einem effektiveren und effizienteren Arbeitsumfeld führt.

b)

(b) Ein Teammitglied hat geäußert, dass es sich unsicher fühlt, da es keine detaillierten Dokumentationen gibt, und es Schwierigkeiten hat, sich einen Überblick über den Projektfortschritt zu verschaffen. Welches der 12 Prinzipien des Agilen Manifests könnte helfen, diese Bedenken zu adressieren und wie könnte dies konkret umgesetzt werden?

Lösung:

Das Prinzip des Agilen Manifests, das helfen könnte, die Bedenken des Teammitgliedes zu adressieren, ist: 'Arbeite mit dem Kunden zusammen statt Vertragshandlungen.' Dieses Prinzip betont die Wichtigkeit laufender Zusammenarbeit und Kommunikation über formale Dokumentation und Verträge hinaus. Hier sind einige konkrete Maßnahmen, um dies umzusetzen:

  • Regelmäßige Review Meetings: Diese Meetings bieten einen Überblick über den aktuellen Projektstand und ermöglichen es dem Teammitglied, Fragen zu stellen und Unsicherheiten zu klären. Dabei wird demonstriert, was bereits erreicht wurde und was als nächstes ansteht.
  • Fortlaufend aktualisierte Product Backlog: Ein gut gepflegtes und ständig aktualisiertes Product Backlog kann Transparenz über den Projektfortschritt bieten. Hierbei werden alle anstehenden Aufgaben und deren Prioritäten deutlich dokumentiert.
  • Transparente Kanban-Boards: Die Verwendung von Kanban-Boards (physisch oder digital) hilft dem ganzen Team, den Fortschritt und Status sämtlicher Arbeitspakete visuell nachzuvollziehen. Diese Boards können auch tägliche Updates anzeigen, was besonders hilfreich ist, um den Fortschritt transparent zu machen und Unsicherheiten zu reduzieren.

Durch diese Maßnahmen können Teammitglieder einen besseren Überblick über den Projektfortschritt erhalten, was dazu beiträgt, Unsicherheiten zu verringern und die Zusammenarbeit zu stärken.

c)

(c) Euer Kunde hat kurz vor dem Ende einer Iteration eine neue Anforderung gestellt, die sehr wichtig ist. Diese neue Anforderung erfordert bedeutende Änderungen an der bestehenden Architektur. Wie sollte Dein Team auf diese Anforderung gemäß den Prinzipien des Agilen Manifests reagieren und welche Schritte sollten unternommen werden, um die Änderungen effektiv zu integrieren? Diskutiere insbesondere den Umgang mit der bestehenden Planung und die neuen Anforderungen.

Lösung:

Gemäß den Prinzipien des Agilen Manifests sollte Dein Team flexibel und anpassungsfähig auf neue Anforderungen reagieren, selbst wenn sie zu einem späten Zeitpunkt im Entwicklungsprozess gestellt werden. Ein relevantestes Prinzip ist: 'Reagieren auf Veränderungen ist wichtiger als das Befolgen eines Plans.' Hier ist, wie Dein Team diese neue Anforderung angehen sollte:

  • Evaluierung der neuen Anforderung: Zunächst sollte das Team gemeinsam mit dem Kunden eine gründliche Evaluierung der neuen Anforderung durchführen. Es muss geklärt werden, warum die Änderung notwendig ist und welcher geschäftliche Nutzen daraus entsteht.
  • Neugestaltung der Prioritäten: Überprüfe das aktuelle Product Backlog und die Prioritätenliste und integriere die neue Anforderung als eine der obersten Prioritäten. Dabei sollten andere, weniger wichtige Aufgaben zurückgestellt oder verschoben werden. Eine priorisierte Liste hilft dabei, den Fokus auf das Wesentliche zu legen.
  • Anpassung der Planung: Basierend auf der Priorisierung sollte das Team seine Iterationsplanung anpassen. Dies kann bedeuten, dass die aktuelle Iteration verlängert wird, um genügend Zeit für die Implementierung der neuen Anforderung zu haben, oder dass der Sprint abgebrochen und ein neuer Sprint mit der neuen Anforderung als Ziel gestartet wird.
  • Technische Analyse und Refactoring: Die Architekturänderungen sollten von den Entwicklern gründlich analysiert werden, um mögliche Risiken und technische Herausforderungen zu identifizieren. Falls nötig, sollte ein Refactoring-Plan erstellt werden, um die Architektur entsprechend anzupassen.
  • Kontinuierliche Kommunikation: Halte den Kunden stets über den Fortschritt und etwaige Hindernisse informiert. Dies gewährleistet, dass der Kunde den Impact der Änderungen versteht und das Team entsprechend unterstützen kann.
  • Testing und Qualitätssicherung: Um sicherzustellen, dass die Änderungen keine negativen Auswirkungen auf das bestehende System haben, sollten umfassende Tests durchgeführt werden. Unit-Tests, Integrationstests und ggf. User Acceptance Tests (UAT) sollten sicherstellen, dass die neue Anforderung korrekt implementiert ist.

Durch diese Schritte wird Dein Team in der Lage sein, die neuen Anforderungen effektiv zu integrieren und so den geschäftlichen Nutzen für den Kunden zu maximieren, während gleichzeitig die Flexibilität und Agilität des Entwicklungsprozesses gewahrt bleibt.

Aufgabe 4)

Identifikation von Code-Smells: Beim Erkennen von Programmiermustern geht es darum, Muster im Code zu identifizieren, die auf potenzielle Probleme hinweisen, jedoch keine direkten Bugs sind. Häufige Arten von Code-Smells beinhalten 'Duplicate Code', 'Long Method', und 'Large Class'. Zu den Werkzeugen zur Erkennung dieser Probleme gehören SonarQube und PMD. Das Ziel der Erkennung solcher Muster ist die Verbesserung der Code-Qualität und Wartbarkeit. Diese Probleme werden oft als Erkennungsmuster oder Antipatterns kategorisiert und weisen meist auf tieferliegende Designprobleme hin.

a)

Beschreibe ausführlich, wie 'Duplicate Code' als Code-Smell identifiziert und behoben werden kann. Nenne dabei typische Situationen, in denen 'Duplicate Code' auftritt und erläutere mindestens zwei verschiedene Lösungsansätze zur Entfernung doppelten Codes. Wie helfen Werkzeuge wie SonarQube bei diesem Prozess?

Lösung:

Identifikation und Behebung von Duplicate Code:

  • Was ist Duplicate Code? Duplicate Code tritt auf, wenn zwei oder mehr Code-Segmente identisch oder sehr ähnlich sind. Dieser Code-Smell ist problematisch, da Änderungen an einer Stelle oft auch an den anderen Stellen vorgenommen werden müssen, was zu höherem Wartungsaufwand und erhöhter Fehleranfälligkeit führt.
  • Typische Situationen für Duplicate Code:
    • Copy-Paste-Programmierung: Entwickler kopieren und fügen Code-Segmente ein, ohne diese in eine wiederverwendbare Methode oder Klasse zu extrahieren.
    • Mangelnde Nutzung von Bibliotheken und Frameworks: Anstatt bestehende Bibliotheken oder Frameworks zu nutzen, implementieren Entwickler dieselbe Logik mehrfach.
    • Schnelle Änderungen und Hotfixes: Bei schnellen, oft ungeplanten Änderungen wird Code dupliziert, um kurzfristig ein Problem zu lösen, ohne das Design zu refaktorisieren.
Behebung von Duplicate Code:
  • Extraktionen von Methoden oder Funktionen: Bei sich wiederholenden Code-Segmenten kann der gemeinsame Code in eine Methode oder Funktion extrahiert werden, die dann überall dort aufgerufen wird, wo der duplizierte Code vorher verwendet wurde.
    public void methodA() {  ... }
    public void methodB() { ... }
    Diese beiden Methoden enthalten möglicherweise ähnliche Logik. Um Duplicate Code zu vermeiden, kann man den gemeinsamen Teil in eine separate Methode extrahieren.
    public void extractedMethod() { ... }
    public void methodA() { extractedMethod(); ... }
    public void methodB() { extractedMethod(); ... }
  • Verwendung von Design Patterns: Einige Design Patterns, wie das Template Method Pattern oder das Strategy Pattern, können helfen, Duplicate Code zu minimieren, indem sie wiederkehrende Logik in eine gemeinsame Basis implementieren. Beispiel für das Template Method Pattern:
    abstract class CommonTemplate { public final void templateMethod() { baseOperation(); differentiatedOperation(); } protected abstract void differentiatedOperation(); private void baseOperation() { ...  } }
    class SpecificImplementationA extends CommonTemplate { @Override protected void differentiatedOperation() { ...  } }
    class SpecificImplementationB extends CommonTemplate { @Override protected void differentiatedOperation() { ...  } }
    Durch dieses Muster wird die gemeinsam genutzte Logik in der Basisklasse implementiert, während die differenzierende Logik in den Subklassen behandelt wird.
Werkzeuge zur Erkennung und Behebung:
  • SonarQube: SonarQube ist ein statisches Analysetool, das Duplicate Code erkennt und visuell darstellt. Es kann den Prozentsatz des duplizierten Codes in einem Projekt identifizieren und Entwicklern helfen, problematische Bereiche gezielt anzugehen.
  • PMD: PMD ist ein weiteres statisches Analysetool, das Regeln zur Erkennung von Duplicate Code bereitstellt und Entwicklern Hinweise zur Verbesserung des Codes gibt.
Zusammengefasst helfen diese Tools, Duplicate Code effizienter zu identifizieren, indem sie den Code analysieren und Entwicklern konkrete Hinweise zur Resektion und Refaktorisierung geben. Dies führt zu qualitativ hochwertigerem, wartbarerem Code.

b)

Gegeben sei die folgende Klasse in Java:

 public class CustomerService {   \tpublic void addCustomer(Customer customer) { \t\t// complex logic for adding customer \t} \tpublic void removeCustomer(Customer customer) { \t\t// complex logic for removing customer \t} \tpublic List getAllCustomers() { \t\t// complex logic for getting all customers \t} } 
Identifiziere die potentiellen Code-Smells in dieser Klasse. Begründe deine Auswahl und schlage eine Refaktorierung der Klasse vor, um die identifizierten Probleme zu beheben.

Lösung:

Identifikation von Code-Smells: In der angegebenen Klasse CustomerService fallen mehrere potentielle Code-Smells auf:

  • Large Class: Diese Klasse hat mehrere Methoden und könnte potenziell noch mehr logische Funktionen enthalten. Eine große Klasse kann schwer zu verstehen und zu warten sein.
  • Long Method: Jede Methode enthält 'komplexe Logik', was darauf hinweist, dass die Methoden zu lang und komplex sein könnten. Dies erschwert das Verständnis und die Wartung.
  • Verletzung des Single Responsibility Principles (SRP): Die Klasse übernimmt mehrere Verantwortlichkeiten, wie das Hinzufügen, Entfernen und Abrufen von Kunden. Eine Klasse sollte idealerweise nur eine Verantwortung haben.
Refaktorierung der Klasse: Um die identifizierten Probleme zu beheben, können wir die Klasse in kleinere, spezialisierte Klassen und Methoden aufteilen. Ein möglicher Ansatz ist die Einführung eines Repositorys für die eigentlichen CRUD-Operationen und die Aufteilung der Geschäftslogik in separate Klassen. public class CustomerRepository { public void addCustomer(Customer customer) { // Datenbankzugriffscode zum Hinzufügen des Kunden } public void removeCustomer(Customer customer) { // Datenbankzugriffscode zum Entfernen des Kunden } public List getAllCustomers() { // Datenbankzugriffscode zum Abrufen aller Kunden } } public class CustomerService { private CustomerRepository customerRepository; public CustomerService(CustomerRepository customerRepository) { this.customerRepository = customerRepository; } public void registerCustomer(Customer customer) { // spezielle Geschäftslogik für das Hinzufügen eines Kunden this.customerRepository.addCustomer(customer); } public void deleteCustomer(Customer customer) { // spezielle Geschäftslogik für das Entfernen eines Kunden this.customerRepository.removeCustomer(customer); } public List retrieveAllCustomers() { // spezielle Geschäftslogik für das Abrufen aller Kunden return this.customerRepository.getAllCustomers(); } } Durch diese Refaktorierung haben wir die folgenden Verbesserungen erreicht:
  • Verkleinerung der Klasse: Die ursprüngliche CustomerService-Klasse wurde in zwei Klassen geteilt: CustomerRepository für CRUD-Operationen und CustomerService für geschäftsbezogene Logik.
  • Verkürzung der Methoden: Methoden sind jetzt kürzer und fokussierter, was ihre Lesbarkeit und Wartbarkeit verbessert.
  • Einhaltung des Single Responsibility Principles: Jede Klasse und Methode hat jetzt eine spezifische, klar definierte Aufgabe, was die Kohäsion verbessert.

c)

Stelle die mathematischen Zusammenhänge und die potenziellen Auswirkungen eines 'Long Method' Code-Smells auf die Maintenanz und die Fehlerwahrscheinlichkeit eines Softwareprojekts theoretisch dar. Wie kann dieser Zusammenhang mit Hilfe von statistischen Modellen erklärt werden? Berechne beispielhaft, wie die Wahrscheinlichkeit von Fehlern in einer Methode mit 100 Codezeilen im Vergleich zu einer Methode mit 10 Codezeilen analyisiert werden kann.

Lösung:

Mathematische Zusammenhänge und potenzielle Auswirkungen eines 'Long Method' Code-Smells:

  • Long Method: Eine 'Long Method' ist eine Methode, die zu viele Zeilen Code enthält und dadurch schwieriger zu verstehen und zu warten ist. Dieser Code-Smell erhöht die Wahrscheinlichkeit von Fehlern und verstärkt Wartungsprobleme.
Theoretische Auswirkungen auf die Wartbarkeit und Fehlerwahrscheinlichkeit:Die Wartbarkeit und Fehlerwahrscheinlichkeit von Software hängen eng mit der Komplexität des Codes zusammen. Eine längere Methode kann folgende negative Auswirkungen haben:
  • Erhöhte Komplexität: Mehr Codezeilen bedeuten eine höhere Anzahl von Ausführungswegen und Randfällen, was die Verständlichkeit und Wartbarkeit verringert.
  • Höhere Fehleranfälligkeit: Mit zunehmender Länge und Komplexität steigt die Wahrscheinlichkeit, dass Fehler übersehen oder gemacht werden.
  • Schwierige Wartung: Lange Methoden machen es komplizierter, Änderungen vorzunehmen, weil die Auswirkungen und Nebeneffekte schwieriger abzuschätzen sind.
Statistische Modelle zur Erklärung: Ein gängiges Modell zur Erklärung der Beziehung zwischen der Länge einer Methode und der Fehlerwahrscheinlichkeit basiert auf der Annahme, dass die Anzahl der Fehler in einer Methode proportional zur Anzahl der Codezeilen ist. Ein häufig verwendetes statistisches Modell ist die Poisson-Verteilung, die verwendet wird, um die Wahrscheinlichkeit von Ereignissen (wie Fehlern) pro Zeiteinheit oder pro Einheit (wie Codezeilen) zu modellieren.
  • Poisson-Verteilung: Bei der Anwendung auf Softwarefehler besagt das Modell, dass die Wahrscheinlichkeit \( P(k) \) für k Fehler in einer Methode mit n Codezeilen durch die Poisson-Verteilung gegeben ist: \( P(k) = \frac{{(\lambda n)^k}}{{k!}} e^{-\lambda n} \) Dabei ist \( \lambda \) die durchschnittliche Fehleranzahl pro Codezeile.
Beispielberechnung: Betrachten wir zwei Methoden, eine mit 100 und eine mit 10 Codezeilen. Nehmen wir eine Fehlerrate von 0,01 Fehlern pro Codezeile an.
  • Methode mit 100 Codezeilen: Fehlerwahrscheinlichkeit \( P = 0,01 \)
  • Erwartete Anzahl an Fehlern \(\lambda_1 = 100 \times P = 1 \)
  • Methode mit 10 Codezeilen: Fehlerwahrscheinlichkeit \( P = 0,01 \)
  • Erwartete Anzahl an Fehlern \(\lambda_2 = 10 \times P = 0,1\)
  • Vergleich der Fehlerwahrscheinlichkeiten: Das Verhältnis der erwarteten Anzahl an Fehlern ist: \( \frac{\lambda_1}{\lambda_2} = \frac{1}{0,1} = 10 \) Das bedeutet, dass die Wahrscheinlichkeit von Fehlern in der Methode mit 100 Codezeilen zehnmal höher ist als in der Methode mit 10 Codezeilen.
Zusammenfassung:Lange Methoden sollten aufgeteilt und in kleinere, verständlichere Einheiten zerlegt werden. Dies reduziert die Fehlerwahrscheinlichkeit und verbessert die Wartbarkeit des Codes erheblich.
Sign Up

Melde dich kostenlos an, um Zugriff auf das vollständige Dokument zu erhalten

Mit unserer kostenlosen Lernplattform erhältst du Zugang zu Millionen von Dokumenten, Karteikarten und Unterlagen.

Kostenloses Konto erstellen

Du hast bereits ein Konto? Anmelden