From 714a908a7a18370944f75b113daa96dbec9e2254 Mon Sep 17 00:00:00 2001 From: ncrypted | Oliver <15632467+ncryptedV1@users.noreply.github.com> Date: Wed, 29 Mar 2023 18:54:16 +0200 Subject: [PATCH 01/39] add: basic template for program design --- README.md | 166 ++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 160 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 90e2fc3..4f4fbb4 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,160 @@ -# Ziel -Wir wollen eine Anwendung zur Verwaltung von Essensgerichten für einen Haushalt entwickeln. Ziel ist es, Essenspläne und -dazugehörige Einkaufslisten für einen bestimmten Zeitraum (z.B. die folgende Woche) einfach erstellen und generieren -lassen zu können. Das soll anhand verschiedener Faktoren passieren, z.B. favorisierte Zutaten (z.B. Reis, Nudeln) oder -auch Ernährungsweisen. Natürlich involviert das auch die Speicherung von Gerichten inklusive dazugehöriger Rezepte und -Zutaten. Denkbar wäre aber auch eine Abfrage von APIs wie etwa Chefkoch.de, um weitere Rezepte einzubeziehen. \ No newline at end of file +# Programmentwurf - AutoChef +Name: Schirmer, Oliver \ +Martrikelnummer: XXX + +Name: Müller, Luca \ +Martrikelnummer: XXX + +Abgabedatum: 28. Mai 2023 + +## Allgemeine Anmerkungen +- es darf nicht auf andere Kapitel als Leistungsnachweis verwiesen werden (z.B. in der Form “XY wurde schon in Kapitel 2 behandelt, daher hier keine Ausführung”) +- alles muss in UTF-8 codiert sein (Text und Code) +- sollten mündliche Aussagen den schriftlichen Aufgaben widersprechen, gelten die schriftlichen Aufgaben (ggf. an Anpassung der schriftlichen Aufgaben erinnern!) +- alles muss ins Repository (Code, Ausarbeitung und alles was damit zusammenhängt) +- die Beispiele sollten wenn möglich vom aktuellen Stand genommen werden + - finden sich dort keine entsprechenden Beispiele, dürfen auch ältere Commits unter Verweis auf den Commit verwendet werden + - Ausnahme: beim Kapitel “Refactoring” darf von vorne herein aus allen Ständen frei gewählt werden (mit Verweis auf den entsprechenden Commit) +- falls verlangte Negativ-Beispiele nicht vorhanden sind, müssen entsprechend mehr Positiv-Beispiele gebracht werden + - Achtung: werden im Code entsprechende Negativ-Beispiele gefunden, gibt es keine Punkte für die zusätzlichen Positiv-Beispiele + - Beispiel: “Nennen Sie jeweils eine Klasse, die das SRP einhält bzw. verletzt.” + - -> Antwort: Es gibt keine Klasse, die SRP verletzt, daher hier 2 Klassen, die SRP einhalten: [Klasse 1], [Klasse 2] + - -> Bewertung: falls im Code tatsächlich keine Klasse das SRP verletzt: volle Punktzahl ODER falls im Code mind. eine Klasse SRP verletzt: halbe Punktzahl +- verlangte Positiv-Beispiele müssen gebracht werden +- Code-Beispiel = Code in das Dokument kopieren + +## 1. Einführung +### Übersicht über die Applikation +AutoChef ist eine Anwendung zur einfachen und effizienten Verwaltung und Erstellung von Essensplänen, sowie dazugehöriger Einkaufslisten. Zugrunde liegt dabei eine Datenbank an Rezepten. Diese ist beliebig erweiterbar durch eine Chefkoch (chefkoch.de)-Integration. Diese ermöglicht es, einen Link zu einem Rezept auf Chefkoch an die Anwendung zu übergeben, woraufhin diese das entsprechende Rezept herunterlädt und persistiert. Diese Datenbank an Rezepten, sowie jeweilige Rezept-Details, können jederzeit eingesehen werden. Hauptfunktion ist jedoch die Generierung von Essensplänen. Dafür kann der Nutzer einen Zeitraum, sowie die Anzahl an Personen je Mahlzeit angeben und die Anwendung generiert anhand der Rezept-Datenbank, einen zufälligen Essensplan. Für diesen Essensplan wird ebenfalls eine Einkaufsliste generiert, die an die Anzahl an Personen angepasst ist. Mit diesen Funktionalitäten ist es einfach möglich, seine Woche kulinarisch zu planen und die Einkaufliste für den Wocheneinkauf zu erstellen. + +*[Was macht die Applikation? Wie funktioniert sie? Welches Problem löst sie/welchen Zweck hat sie?]* + +### Wie startet man die Applikation? +Bei AutoChef handelt es sich um eine CLI-Anwendung, geschrieben in Java 19. Zum Starten wird daher lediglich ein Desktop-Rechner mit Java 19 aufwärts benötigt. Die Anwendung kann dann über ein Konsolenfenster mit dem Befehl java -jar AutoChef.jar gestartet werden. + +*[Wie startet man die Applikation? Welche Voraussetzungen werden benötigt? Schritt-für-Schritt-Anleitung]* + +### Wie testet man die Applikation? +Nach dem Start der Anwendung wird der Nutzer durch einen intuitiven Dialog-Prozess begrüßt und geleitet. Der Nutzer interagiert dabei mit der Anwendung mittels des Konsolenfensters. Über dieses werden sowohl Informationen ausgegeben, als auch Eingaben vom Nutzer eingeholt. Der Dialog-Prozess ist so gestaltet, dass der Nutzer in der Regel mehrere nummerierte Optionen zur Auswahl hat und nur die Nummer der gewünschten Option eingeben und mit Enter absenden muss. Für den Import von Rezepten muss der Nutzer sich zuvor ein Rezept von Chefkoch aussuchen und im entsprechenden Dialog-Prozess-Schritt den dazugehörigen Link einfügen. Zu Beginn der Nutzung ist die Rezept-Datenbank noch leer, weshalb es sich anbietet anfangs ein paar Rezepte zu importieren. Erst danach können die Funktionen der Rezept-Anzeige und Essensplan-Generierung sinnvoll genutzt werden. + +Hinweis: Zur Persistierung der Rezepte erstellt die Anwendung im aktuellen Arbeitsverzeichnis (des Konsolenfensters) einen persistence-Ordner. Falls der Nutzer keine Rechte hat im aktuellen Arbeitsverzeichnis Ordner & Dateien zu erstellen, sowie in diese zu schreiben, kann das Programm nicht ordnungsgemäß arbeiten und terminiert. Versuche in dem Fall die Anwendung mit erhöhten Berechtigungen zu starten oder in einem Arbeitsverzeichnis mit Schreibzugriff auszuführen. + +*[Wie testet man die Applikation? Welche Voraussetzungen werden benötigt? Schritt-für-Schritt-Anleitung]* + +## 2. Clean Architecture +### Was ist Clean Architecture? +Clean Architecture ist eine Architektur- und Designphilosophie, die darauf abzielt, komplexe Softwaresysteme in leicht verständliche, wartbare und erweiterbare Komponenten zu unterteilen. Es wurde von Robert C. Martin entwickelt und basiert auf den Prinzipien der SOLID-Prinzipien. + +Im Wesentlichen sieht Clean Architecture vor, dass eine Software in mehrere Schichten unterteilt wird, wobei jede Schicht eine klare Abhängigkeitshierarchie aufweist und nur von der nächstgelegenen Schicht abhängt. Die äußerste Schicht ist die Benutzerschnittstelle, die direkt mit dem Benutzer interagiert, gefolgt von einer oder mehreren Schichten mit Geschäftslogik, Datenzugriff und Infrastruktur. + +Durch diese Aufteilung kann jede Schicht unabhängig von den anderen Schichten getestet und gewartet werden, was zu einer höheren Flexibilität und Skalierbarkeit des gesamten Systems führt. Darüber hinaus ist es einfacher, Änderungen an einem Teil des Systems vorzunehmen, ohne Auswirkungen auf den Rest des Systems zu haben. + +Clean Architecture ermutigt auch zur Verwendung von Schnittstellen, um die Abhängigkeiten zwischen den Komponenten zu minimieren. Durch die Verwendung von Schnittstellen können verschiedene Implementierungen ausgetauscht werden, ohne dass dies Auswirkungen auf den Rest des Systems hat. + +Zusammenfassend lässt sich sagen, dass Clean Architecture eine Methode ist, um große, komplexe Softwaresysteme in einfachere, leichter zu wartende Komponenten aufzuteilen, indem eine klare Abhängigkeitshierarchie zwischen den Komponenten eingeführt wird. + +*[allgemeine Beschreibung der Clean Architecture in eigenen Worten]* + +### Analyse der Dependency Rule +*[(1 Klasse, die die Dependency Rule einhält und eine Klasse, die die Dependency Rule verletzt); jeweils UML der Klasse und Analyse der Abhängigkeiten in beide Richtungen (d.h., von wem hängt die Klasse ab und wer hängt von der Klasse ab) in Bezug auf die Dependency Rule]* + +Positiv-Beispiel: Dependency Rule +Negativ-Beispiel: Dependency Rule + +### Analyse der Schichten +*[jeweils 1 Klasse zu 2 unterschiedlichen Schichten der Clean-Architecture: jeweils UML der Klasse (ggf. auch zusammenspielenden Klassen), Beschreibung der Aufgabe, Einordnung mit Begründung in die Clean-Architecture]* + +Schicht: [Name] +Schicht: [Name] + +## 3. SOLID +### Analyse Single-Responsibility-Principle (SRP) +*[jeweils eine Klasse als positives und negatives Beispiel für SRP; jeweils UML der Klasse und Beschreibung der Aufgabe bzw. der Aufgaben und möglicher Lösungsweg des Negativ-Beispiels (inkl. UML)]* + +Positiv-Beispiel +Negativ-Beispiel + +### Analyse Open-Closed-Principle (OCP) +*[jeweils eine Klasse als positives und negatives Beispiel für OCP; jeweils UML der Klasse und Analyse mit Begründung, warum das OCP erfüllt/nicht erfüllt wurde – falls erfüllt: warum hier sinnvoll/welches Problem gab es? Falls nicht erfüllt: wie könnte man es lösen (inkl. UML)?]* + +Positiv-Beispiel +Negativ-Beispiel + +### Analyse Liskov-Substitution- (LSP), Interface-Segreggation- (ISP), Dependency-Inversion-Principle (DIP) +*[jeweils eine Klasse als positives und negatives Beispiel für entweder LSP oder ISP oder DIP); jeweils UML der Klasse und Begründung, warum man hier das Prinzip erfüllt/nicht erfüllt wird]* \ +*[Anm.: es darf nur ein Prinzip ausgewählt werden; es darf NICHT z.B. ein positives Beispiel für LSP und ein negatives Beispiel für ISP genommen werden]* + +Positiv-Beispiel +Negativ-Beispiel + +## 4. Weitere Prinzipien +### Analyse GRASP: Geringe Kopplung +*[jeweils eine bis jetzt noch nicht behandelte Klasse als positives und negatives Beispiel geringer Kopplung; jeweils UML Diagramm mit zusammenspielenden Klassen, Aufgabenbeschreibung und Begründung für die Umsetzung der geringen Kopplung bzw. Beschreibung, wie die Kopplung aufgelöst werden kann]* + +Positiv-Beispiel +Negativ-Beispiel + +### Analyse GRASP: Hohe Kohäsion +*[eine Klasse als positives Beispiel hoher Kohäsion; UML Diagramm und Begründung, warum die Kohäsion hoch ist] +Don’t Repeat Yourself (DRY)* \ +*[ein Commit angeben, bei dem duplizierter Code/duplizierte Logik aufgelöst wurde; Code-Beispiele (vorher/nachher); begründen und Auswirkung beschreiben]* + +## 5. Unit Tests +### 10 Unit Tests +*[Nennung von 10 Unit-Tests und Beschreibung, was getestet wird]* +| Unit Test | Beschreibung | +|------------------|--------------| +| *Klasse#Methode* | | +| | | +| | | +| | | + +### ATRIP: Automatic +*[Begründung/Erläuterung, wie ‘Automatic’ realisiert wurde]* + +### ATRIP: Thorough +*[jeweils 1 positives und negatives Beispiel zu ‘Thorough’; jeweils Code-Beispiel, Analyse und Begründung, was professionell/nicht professionell ist]* + +### ATRIP: Professional +*[jeweils 1 positives und negatives Beispiel zu ‘Professional’; jeweils Code-Beispiel, Analyse und Begründung, was professionell/nicht professionell ist]* + +### Code Coverage +*[Code Coverage im Projekt analysieren und begründen]* + +### Fakes und Mocks +*[Analyse und Begründung des Einsatzes von 2 Fake/Mock-Objekten; zusätzlich jeweils UML Diagramm der Klasse]* + +## 6. Domain Driven Design +### Ubiquitous Language +*[4 Beispiele für die Ubiquitous Language; jeweils Bezeichung, Bedeutung und kurze Begründung, warum es zur Ubiquitous Language gehört]* +| Bezeichnung | Bedeutung | Begründung | +|-------------|-----------|------------| +| | | | +| | | | +| | | | +| | | | + +### Entities +*[UML, Beschreibung und Begründung des Einsatzes einer Entity; falls keine Entity vorhanden: ausführliche Begründung, warum es keines geben kann/hier nicht sinnvoll ist]* + +### Value Objects +*[UML, Beschreibung und Begründung des Einsatzes eines Value Objects; falls kein Value Object vorhanden: ausführliche Begründung, warum es keines geben kann/hier nicht sinnvoll ist]* + +### Repositories +*[UML, Beschreibung und Begründung des Einsatzes eines Repositories; falls kein Repository vorhanden: ausführliche Begründung, warum es keines geben kann/hier nicht sinnvoll ist]* + +### Aggregates +*[UML, Beschreibung und Begründung des Einsatzes eines Aggregates; falls kein Aggregate vorhanden: ausführliche Begründung, warum es keines geben kann/hier nicht sinnvoll ist]* + +## 7. Refactoring +### Code Smells +*[jeweils 1 Code-Beispiel zu 2 Code Smells aus der Vorlesung; jeweils Code-Beispiel und einen möglichen Lösungsweg bzw. den genommen Lösungsweg beschreiben (inkl. (Pseudo-)Code)]* + +### 2 Refactorings +*[2 unterschiedliche Refactorings aus der Vorlesung anwenden, begründen, sowie UML vorher/nachher liefern; jeweils auf die Commits verweisen]* + +## 8. Entwurfsmuster +*[2 unterschiedliche Entwurfsmuster aus der Vorlesung (oder nach Absprache auch andere) jeweils sinnvoll einsetzen, begründen und UML-Diagramm]* + +Entwurfsmuster: [Name] +Entwurfsmuster: [Name] From 81e983a5eec19e38c6fc341f75438b1c9fa4363d Mon Sep 17 00:00:00 2001 From: ncryptedV1 <15632467+ncryptedV1@users.noreply.github.com> Date: Wed, 29 Mar 2023 21:20:23 +0200 Subject: [PATCH 02/39] add: required PlantUML files --- uml/aggregate.iuml | 0 uml/cohesion.iuml | 0 uml/coupling-neg.iuml | 0 uml/coupling-pos.iuml | 0 uml/dependency-rule-neg.iuml | 0 uml/dependency-rule-pos.iuml | 35 ++++++++++++++++++++++++++++++ uml/design-pattern-1.iuml | 0 uml/design-pattern-2.iuml | 0 uml/entity.iuml | 0 uml/fake-mock-1.iuml | 0 uml/fake-mock-2.iuml | 0 uml/layer-1.iuml | 0 uml/layer-2.iuml | 0 uml/liskov-neg.iuml | 0 uml/liskov-pos.iuml | 0 uml/open-closed-neg.iuml | 0 uml/open-closed-pos.iuml | 0 uml/refactoring-1-post.iuml | 0 uml/refactoring-1-pre.iuml | 0 uml/refactoring-2-post.iuml | 0 uml/refactoring-2-pre.iuml | 0 uml/repository.iuml | 0 uml/single-responsibility-neg.iuml | 0 uml/single-responsibility-pos.iuml | 0 uml/value-object.iuml | 0 25 files changed, 35 insertions(+) create mode 100644 uml/aggregate.iuml create mode 100644 uml/cohesion.iuml create mode 100644 uml/coupling-neg.iuml create mode 100644 uml/coupling-pos.iuml create mode 100644 uml/dependency-rule-neg.iuml create mode 100644 uml/dependency-rule-pos.iuml create mode 100644 uml/design-pattern-1.iuml create mode 100644 uml/design-pattern-2.iuml create mode 100644 uml/entity.iuml create mode 100644 uml/fake-mock-1.iuml create mode 100644 uml/fake-mock-2.iuml create mode 100644 uml/layer-1.iuml create mode 100644 uml/layer-2.iuml create mode 100644 uml/liskov-neg.iuml create mode 100644 uml/liskov-pos.iuml create mode 100644 uml/open-closed-neg.iuml create mode 100644 uml/open-closed-pos.iuml create mode 100644 uml/refactoring-1-post.iuml create mode 100644 uml/refactoring-1-pre.iuml create mode 100644 uml/refactoring-2-post.iuml create mode 100644 uml/refactoring-2-pre.iuml create mode 100644 uml/repository.iuml create mode 100644 uml/single-responsibility-neg.iuml create mode 100644 uml/single-responsibility-pos.iuml create mode 100644 uml/value-object.iuml diff --git a/uml/aggregate.iuml b/uml/aggregate.iuml new file mode 100644 index 0000000..e69de29 diff --git a/uml/cohesion.iuml b/uml/cohesion.iuml new file mode 100644 index 0000000..e69de29 diff --git a/uml/coupling-neg.iuml b/uml/coupling-neg.iuml new file mode 100644 index 0000000..e69de29 diff --git a/uml/coupling-pos.iuml b/uml/coupling-pos.iuml new file mode 100644 index 0000000..e69de29 diff --git a/uml/dependency-rule-neg.iuml b/uml/dependency-rule-neg.iuml new file mode 100644 index 0000000..e69de29 diff --git a/uml/dependency-rule-pos.iuml b/uml/dependency-rule-pos.iuml new file mode 100644 index 0000000..495e34a --- /dev/null +++ b/uml/dependency-rule-pos.iuml @@ -0,0 +1,35 @@ +@startuml + +left to right direction + +class AutoChef { + + main(args: String[]) +} + +class DialogService { + - recipeRepository: RecipeRepository + + DialogService(recipeRepository: RecipeRepository) +} + +interface RecipeRepository { + + saveRecipe(recipe: Recipe) + + deleteRecipe(recipe: Recipe): boolean + + getRecipes(): List + + getRecipe(id: String): Recipe +} + +class RecipeFileRepository { + - recipesFolder: File + + RecipeFileRepository(recipesFolder: File) + + saveRecipe(recipe: Recipe) + + deleteRecipe(recipe: Recipe): boolean + + getRecipes(): List + + getRecipe(id: String): Recipe +} + +AutoChef -> RecipeFileRepository +AutoChef -> DialogService +DialogService -> RecipeRepository +RecipeRepository <|.. RecipeFileRepository + +@enduml \ No newline at end of file diff --git a/uml/design-pattern-1.iuml b/uml/design-pattern-1.iuml new file mode 100644 index 0000000..e69de29 diff --git a/uml/design-pattern-2.iuml b/uml/design-pattern-2.iuml new file mode 100644 index 0000000..e69de29 diff --git a/uml/entity.iuml b/uml/entity.iuml new file mode 100644 index 0000000..e69de29 diff --git a/uml/fake-mock-1.iuml b/uml/fake-mock-1.iuml new file mode 100644 index 0000000..e69de29 diff --git a/uml/fake-mock-2.iuml b/uml/fake-mock-2.iuml new file mode 100644 index 0000000..e69de29 diff --git a/uml/layer-1.iuml b/uml/layer-1.iuml new file mode 100644 index 0000000..e69de29 diff --git a/uml/layer-2.iuml b/uml/layer-2.iuml new file mode 100644 index 0000000..e69de29 diff --git a/uml/liskov-neg.iuml b/uml/liskov-neg.iuml new file mode 100644 index 0000000..e69de29 diff --git a/uml/liskov-pos.iuml b/uml/liskov-pos.iuml new file mode 100644 index 0000000..e69de29 diff --git a/uml/open-closed-neg.iuml b/uml/open-closed-neg.iuml new file mode 100644 index 0000000..e69de29 diff --git a/uml/open-closed-pos.iuml b/uml/open-closed-pos.iuml new file mode 100644 index 0000000..e69de29 diff --git a/uml/refactoring-1-post.iuml b/uml/refactoring-1-post.iuml new file mode 100644 index 0000000..e69de29 diff --git a/uml/refactoring-1-pre.iuml b/uml/refactoring-1-pre.iuml new file mode 100644 index 0000000..e69de29 diff --git a/uml/refactoring-2-post.iuml b/uml/refactoring-2-post.iuml new file mode 100644 index 0000000..e69de29 diff --git a/uml/refactoring-2-pre.iuml b/uml/refactoring-2-pre.iuml new file mode 100644 index 0000000..e69de29 diff --git a/uml/repository.iuml b/uml/repository.iuml new file mode 100644 index 0000000..e69de29 diff --git a/uml/single-responsibility-neg.iuml b/uml/single-responsibility-neg.iuml new file mode 100644 index 0000000..e69de29 diff --git a/uml/single-responsibility-pos.iuml b/uml/single-responsibility-pos.iuml new file mode 100644 index 0000000..e69de29 diff --git a/uml/value-object.iuml b/uml/value-object.iuml new file mode 100644 index 0000000..e69de29 From 17cfe25ec97cfe4a780ab81acbc56e5139455eaf Mon Sep 17 00:00:00 2001 From: ncrypted | Oliver <15632467+ncryptedV1@users.noreply.github.com> Date: Wed, 29 Mar 2023 23:27:09 +0200 Subject: [PATCH 03/39] add: PlantUML embeddings --- README.md | 61 +++++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 46 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 4f4fbb4..d58dd85 100644 --- a/README.md +++ b/README.md @@ -58,45 +58,60 @@ Zusammenfassend lässt sich sagen, dass Clean Architecture eine Methode ist, um ### Analyse der Dependency Rule *[(1 Klasse, die die Dependency Rule einhält und eine Klasse, die die Dependency Rule verletzt); jeweils UML der Klasse und Analyse der Abhängigkeiten in beide Richtungen (d.h., von wem hängt die Klasse ab und wer hängt von der Klasse ab) in Bezug auf die Dependency Rule]* -Positiv-Beispiel: Dependency Rule -Negativ-Beispiel: Dependency Rule +#### Positiv-Beispiel: Dependency Rule +![Dependency Rule positives Beispiel UML](http://www.plantuml.com/plantuml/proxy?cache=no&src=https://raw.githubusercontent.com/ncryptedV1/AutoChef/docs/uml/dependency-rule-pos.iuml) + +#### Negativ-Beispiel: Dependency Rule +![Dependency Rule negatives Beispiel UML](http://www.plantuml.com/plantuml/proxy?cache=no&src=https://raw.githubusercontent.com/ncryptedV1/AutoChef/docs/uml/dependency-rule-neg.iuml) ### Analyse der Schichten *[jeweils 1 Klasse zu 2 unterschiedlichen Schichten der Clean-Architecture: jeweils UML der Klasse (ggf. auch zusammenspielenden Klassen), Beschreibung der Aufgabe, Einordnung mit Begründung in die Clean-Architecture]* -Schicht: [Name] -Schicht: [Name] +#### Schicht: [Name] +![Schicht 1 UML](http://www.plantuml.com/plantuml/proxy?cache=no&src=https://raw.githubusercontent.com/ncryptedV1/AutoChef/docs/uml/layer-1.iuml) +#### Schicht: [Name] +![Schicht 2 UML](http://www.plantuml.com/plantuml/proxy?cache=no&src=https://raw.githubusercontent.com/ncryptedV1/AutoChef/docs/uml/layer-2.iuml) ## 3. SOLID ### Analyse Single-Responsibility-Principle (SRP) *[jeweils eine Klasse als positives und negatives Beispiel für SRP; jeweils UML der Klasse und Beschreibung der Aufgabe bzw. der Aufgaben und möglicher Lösungsweg des Negativ-Beispiels (inkl. UML)]* -Positiv-Beispiel -Negativ-Beispiel +#### Positiv-Beispiel +![Single Responsibility positives Beispiel UML](http://www.plantuml.com/plantuml/proxy?cache=no&src=https://raw.githubusercontent.com/ncryptedV1/AutoChef/docs/uml/single-responsibility-pos.iuml) +#### Negativ-Beispiel +![Single Responsibility negatives Beispiel UML](http://www.plantuml.com/plantuml/proxy?cache=no&src=https://raw.githubusercontent.com/ncryptedV1/AutoChef/docs/uml/single-responsibility-neg.iuml) ### Analyse Open-Closed-Principle (OCP) *[jeweils eine Klasse als positives und negatives Beispiel für OCP; jeweils UML der Klasse und Analyse mit Begründung, warum das OCP erfüllt/nicht erfüllt wurde – falls erfüllt: warum hier sinnvoll/welches Problem gab es? Falls nicht erfüllt: wie könnte man es lösen (inkl. UML)?]* -Positiv-Beispiel -Negativ-Beispiel +#### Positiv-Beispiel +![Open-Closed positives Beispiel UML](http://www.plantuml.com/plantuml/proxy?cache=no&src=https://raw.githubusercontent.com/ncryptedV1/AutoChef/docs/uml/open-closed-pos.iuml) +#### Negativ-Beispiel +![Open-Closed negatives Beispiel UML](http://www.plantuml.com/plantuml/proxy?cache=no&src=https://raw.githubusercontent.com/ncryptedV1/AutoChef/docs/uml/open-closed-neg.iuml) ### Analyse Liskov-Substitution- (LSP), Interface-Segreggation- (ISP), Dependency-Inversion-Principle (DIP) *[jeweils eine Klasse als positives und negatives Beispiel für entweder LSP oder ISP oder DIP); jeweils UML der Klasse und Begründung, warum man hier das Prinzip erfüllt/nicht erfüllt wird]* \ *[Anm.: es darf nur ein Prinzip ausgewählt werden; es darf NICHT z.B. ein positives Beispiel für LSP und ein negatives Beispiel für ISP genommen werden]* -Positiv-Beispiel -Negativ-Beispiel +#### Positiv-Beispiel +![Liskov positives Beispiel UML](http://www.plantuml.com/plantuml/proxy?cache=no&src=https://raw.githubusercontent.com/ncryptedV1/AutoChef/docs/uml/liskov-pos.iuml) +#### Negativ-Beispiel +![Liskov negatives Beispiel UML](http://www.plantuml.com/plantuml/proxy?cache=no&src=https://raw.githubusercontent.com/ncryptedV1/AutoChef/docs/uml/liskov-neg.iuml) ## 4. Weitere Prinzipien ### Analyse GRASP: Geringe Kopplung *[jeweils eine bis jetzt noch nicht behandelte Klasse als positives und negatives Beispiel geringer Kopplung; jeweils UML Diagramm mit zusammenspielenden Klassen, Aufgabenbeschreibung und Begründung für die Umsetzung der geringen Kopplung bzw. Beschreibung, wie die Kopplung aufgelöst werden kann]* -Positiv-Beispiel -Negativ-Beispiel +#### Positiv-Beispiel +![Kopplung positives Beispiel UML](http://www.plantuml.com/plantuml/proxy?cache=no&src=https://raw.githubusercontent.com/ncryptedV1/AutoChef/docs/uml/coupling-pos.iuml) +#### Negativ-Beispiel +![Kopplung negatives Beispiel UML](http://www.plantuml.com/plantuml/proxy?cache=no&src=https://raw.githubusercontent.com/ncryptedV1/AutoChef/docs/uml/coupling-neg.iuml) ### Analyse GRASP: Hohe Kohäsion *[eine Klasse als positives Beispiel hoher Kohäsion; UML Diagramm und Begründung, warum die Kohäsion hoch ist] -Don’t Repeat Yourself (DRY)* \ +![Kohäsion Beispiel UML](http://www.plantuml.com/plantuml/proxy?cache=no&src=https://raw.githubusercontent.com/ncryptedV1/AutoChef/docs/uml/cohesion.iuml) + +### Don’t Repeat Yourself (DRY)* *[ein Commit angeben, bei dem duplizierter Code/duplizierte Logik aufgelöst wurde; Code-Beispiele (vorher/nachher); begründen und Auswirkung beschreiben]* ## 5. Unit Tests @@ -123,6 +138,8 @@ Don’t Repeat Yourself (DRY)* \ ### Fakes und Mocks *[Analyse und Begründung des Einsatzes von 2 Fake/Mock-Objekten; zusätzlich jeweils UML Diagramm der Klasse]* +![Fakes und Mocks Beispiel 1 UML](http://www.plantuml.com/plantuml/proxy?cache=no&src=https://raw.githubusercontent.com/ncryptedV1/AutoChef/docs/uml/fake-mock-1.iuml) +![Fakes und Mocks Beispiel 2 UML](http://www.plantuml.com/plantuml/proxy?cache=no&src=https://raw.githubusercontent.com/ncryptedV1/AutoChef/docs/uml/fake-mock-2.iuml) ## 6. Domain Driven Design ### Ubiquitous Language @@ -136,15 +153,19 @@ Don’t Repeat Yourself (DRY)* \ ### Entities *[UML, Beschreibung und Begründung des Einsatzes einer Entity; falls keine Entity vorhanden: ausführliche Begründung, warum es keines geben kann/hier nicht sinnvoll ist]* +![Entity Beispiel UML](http://www.plantuml.com/plantuml/proxy?cache=no&src=https://raw.githubusercontent.com/ncryptedV1/AutoChef/docs/uml/entity.iuml) ### Value Objects *[UML, Beschreibung und Begründung des Einsatzes eines Value Objects; falls kein Value Object vorhanden: ausführliche Begründung, warum es keines geben kann/hier nicht sinnvoll ist]* +![Entity Beispiel UML](http://www.plantuml.com/plantuml/proxy?cache=no&src=https://raw.githubusercontent.com/ncryptedV1/AutoChef/docs/uml/value-object.iuml) ### Repositories *[UML, Beschreibung und Begründung des Einsatzes eines Repositories; falls kein Repository vorhanden: ausführliche Begründung, warum es keines geben kann/hier nicht sinnvoll ist]* +![Repository Beispiel UML](http://www.plantuml.com/plantuml/proxy?cache=no&src=https://raw.githubusercontent.com/ncryptedV1/AutoChef/docs/uml/repository.iuml) ### Aggregates *[UML, Beschreibung und Begründung des Einsatzes eines Aggregates; falls kein Aggregate vorhanden: ausführliche Begründung, warum es keines geben kann/hier nicht sinnvoll ist]* +![Aggregate Beispiel UML](http://www.plantuml.com/plantuml/proxy?cache=no&src=https://raw.githubusercontent.com/ncryptedV1/AutoChef/docs/uml/aggregate.iuml) ## 7. Refactoring ### Code Smells @@ -153,8 +174,18 @@ Don’t Repeat Yourself (DRY)* \ ### 2 Refactorings *[2 unterschiedliche Refactorings aus der Vorlesung anwenden, begründen, sowie UML vorher/nachher liefern; jeweils auf die Commits verweisen]* +#### Refactoring 1 +![Refactoring Beispiel 1 Pre UML](http://www.plantuml.com/plantuml/proxy?cache=no&src=https://raw.githubusercontent.com/ncryptedV1/AutoChef/docs/uml/refactoring-1-pre.iuml) +![Refactoring Beispiel 1 Post UML](http://www.plantuml.com/plantuml/proxy?cache=no&src=https://raw.githubusercontent.com/ncryptedV1/AutoChef/docs/uml/refactoring-1-post.iuml) + +#### Refactoring 2 +![Refactoring Beispiel 2 Pre UML](http://www.plantuml.com/plantuml/proxy?cache=no&src=https://raw.githubusercontent.com/ncryptedV1/AutoChef/docs/uml/refactoring-2-pre.iuml) +![Refactoring Beispiel 2 Post UML](http://www.plantuml.com/plantuml/proxy?cache=no&src=https://raw.githubusercontent.com/ncryptedV1/AutoChef/docs/uml/refactoring-2-post.iuml) + ## 8. Entwurfsmuster *[2 unterschiedliche Entwurfsmuster aus der Vorlesung (oder nach Absprache auch andere) jeweils sinnvoll einsetzen, begründen und UML-Diagramm]* -Entwurfsmuster: [Name] -Entwurfsmuster: [Name] +#### Entwurfsmuster: [Name] +![Entwurfstmuster 1 Beispiel UML](http://www.plantuml.com/plantuml/proxy?cache=no&src=https://raw.githubusercontent.com/ncryptedV1/AutoChef/docs/uml/design-pattern-1.iuml) +#### Entwurfsmuster: [Name] +![Entwurfstmuster 2 Beispiel UML](http://www.plantuml.com/plantuml/proxy?cache=no&src=https://raw.githubusercontent.com/ncryptedV1/AutoChef/docs/uml/design-pattern-1.iuml) From ad5c5899b1272c036418f41b2a01aa798a697af8 Mon Sep 17 00:00:00 2001 From: ncrypted | Oliver <15632467+ncryptedV1@users.noreply.github.com> Date: Wed, 29 Mar 2023 23:36:55 +0200 Subject: [PATCH 04/39] edit: improve formatting --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index d58dd85..57be84f 100644 --- a/README.md +++ b/README.md @@ -25,25 +25,25 @@ Abgabedatum: 28. Mai 2023 ## 1. Einführung ### Übersicht über die Applikation -AutoChef ist eine Anwendung zur einfachen und effizienten Verwaltung und Erstellung von Essensplänen, sowie dazugehöriger Einkaufslisten. Zugrunde liegt dabei eine Datenbank an Rezepten. Diese ist beliebig erweiterbar durch eine Chefkoch (chefkoch.de)-Integration. Diese ermöglicht es, einen Link zu einem Rezept auf Chefkoch an die Anwendung zu übergeben, woraufhin diese das entsprechende Rezept herunterlädt und persistiert. Diese Datenbank an Rezepten, sowie jeweilige Rezept-Details, können jederzeit eingesehen werden. Hauptfunktion ist jedoch die Generierung von Essensplänen. Dafür kann der Nutzer einen Zeitraum, sowie die Anzahl an Personen je Mahlzeit angeben und die Anwendung generiert anhand der Rezept-Datenbank, einen zufälligen Essensplan. Für diesen Essensplan wird ebenfalls eine Einkaufsliste generiert, die an die Anzahl an Personen angepasst ist. Mit diesen Funktionalitäten ist es einfach möglich, seine Woche kulinarisch zu planen und die Einkaufliste für den Wocheneinkauf zu erstellen. +AutoChef ist eine Anwendung zur einfachen und effizienten Verwaltung und Erstellung von Essensplänen, sowie dazugehöriger Einkaufslisten. Zugrunde liegt dabei eine Datenbank an Rezepten. Diese ist beliebig erweiterbar durch eine [Chefkoch](chefkoch.de)-Integration. Diese ermöglicht es, einen Link zu einem Rezept auf Chefkoch an die Anwendung zu übergeben, woraufhin diese das entsprechende Rezept herunterlädt und persistiert. Diese Datenbank an Rezepten, sowie jeweilige Rezept-Details, können jederzeit eingesehen werden. Hauptfunktion ist jedoch die Generierung von Essensplänen. Dafür kann der Nutzer einen Zeitraum, sowie die Anzahl an Personen je Mahlzeit angeben und die Anwendung generiert anhand der Rezept-Datenbank, einen zufälligen Essensplan. Für diesen Essensplan wird ebenfalls eine Einkaufsliste generiert, die an die Anzahl an Personen angepasst ist. Mit diesen Funktionalitäten ist es einfach möglich, seine Woche kulinarisch zu planen und die Einkaufliste für den Wocheneinkauf zu erstellen. *[Was macht die Applikation? Wie funktioniert sie? Welches Problem löst sie/welchen Zweck hat sie?]* ### Wie startet man die Applikation? -Bei AutoChef handelt es sich um eine CLI-Anwendung, geschrieben in Java 19. Zum Starten wird daher lediglich ein Desktop-Rechner mit Java 19 aufwärts benötigt. Die Anwendung kann dann über ein Konsolenfenster mit dem Befehl java -jar AutoChef.jar gestartet werden. +Bei AutoChef handelt es sich um eine CLI-Anwendung, geschrieben in Java 19. Zum Starten wird daher lediglich ein Desktop-Rechner mit **Java 19 aufwärts** benötigt. Die Anwendung kann dann über ein Konsolenfenster mit dem Befehl `java -jar AutoChef.jar` gestartet werden. *[Wie startet man die Applikation? Welche Voraussetzungen werden benötigt? Schritt-für-Schritt-Anleitung]* ### Wie testet man die Applikation? Nach dem Start der Anwendung wird der Nutzer durch einen intuitiven Dialog-Prozess begrüßt und geleitet. Der Nutzer interagiert dabei mit der Anwendung mittels des Konsolenfensters. Über dieses werden sowohl Informationen ausgegeben, als auch Eingaben vom Nutzer eingeholt. Der Dialog-Prozess ist so gestaltet, dass der Nutzer in der Regel mehrere nummerierte Optionen zur Auswahl hat und nur die Nummer der gewünschten Option eingeben und mit Enter absenden muss. Für den Import von Rezepten muss der Nutzer sich zuvor ein Rezept von Chefkoch aussuchen und im entsprechenden Dialog-Prozess-Schritt den dazugehörigen Link einfügen. Zu Beginn der Nutzung ist die Rezept-Datenbank noch leer, weshalb es sich anbietet anfangs ein paar Rezepte zu importieren. Erst danach können die Funktionen der Rezept-Anzeige und Essensplan-Generierung sinnvoll genutzt werden. -Hinweis: Zur Persistierung der Rezepte erstellt die Anwendung im aktuellen Arbeitsverzeichnis (des Konsolenfensters) einen persistence-Ordner. Falls der Nutzer keine Rechte hat im aktuellen Arbeitsverzeichnis Ordner & Dateien zu erstellen, sowie in diese zu schreiben, kann das Programm nicht ordnungsgemäß arbeiten und terminiert. Versuche in dem Fall die Anwendung mit erhöhten Berechtigungen zu starten oder in einem Arbeitsverzeichnis mit Schreibzugriff auszuführen. +Hinweis: Zur Persistierung der Rezepte erstellt die Anwendung im aktuellen Arbeitsverzeichnis (des Konsolenfensters) einen `recipes`-Ordner. Falls der Nutzer keine Rechte hat im aktuellen Arbeitsverzeichnis Ordner & Dateien zu erstellen, sowie in diese zu schreiben, kann das Programm nicht ordnungsgemäß arbeiten und terminiert. Versuche in dem Fall die Anwendung mit erhöhten Berechtigungen zu starten oder in einem Arbeitsverzeichnis mit Schreibzugriff auszuführen. *[Wie testet man die Applikation? Welche Voraussetzungen werden benötigt? Schritt-für-Schritt-Anleitung]* ## 2. Clean Architecture ### Was ist Clean Architecture? -Clean Architecture ist eine Architektur- und Designphilosophie, die darauf abzielt, komplexe Softwaresysteme in leicht verständliche, wartbare und erweiterbare Komponenten zu unterteilen. Es wurde von Robert C. Martin entwickelt und basiert auf den Prinzipien der SOLID-Prinzipien. +Clean Architecture ist eine Architektur- und Designphilosophie, die darauf abzielt, komplexe Softwaresysteme in leicht verständliche, wartbare und erweiterbare Komponenten zu unterteilen. Es wurde von Robert C. Martin entwickelt und basiert auf den SOLID-Prinzipien. Im Wesentlichen sieht Clean Architecture vor, dass eine Software in mehrere Schichten unterteilt wird, wobei jede Schicht eine klare Abhängigkeitshierarchie aufweist und nur von der nächstgelegenen Schicht abhängt. Die äußerste Schicht ist die Benutzerschnittstelle, die direkt mit dem Benutzer interagiert, gefolgt von einer oder mehreren Schichten mit Geschäftslogik, Datenzugriff und Infrastruktur. From d98a810387481aa2508d89707572742614f417fe Mon Sep 17 00:00:00 2001 From: cuvar Date: Mon, 3 Apr 2023 11:27:54 +0200 Subject: [PATCH 05/39] feat: add test docu section --- README.md | 361 ++++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 326 insertions(+), 35 deletions(-) diff --git a/README.md b/README.md index 57be84f..bd8ce4b 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,5 @@ # Programmentwurf - AutoChef + Name: Schirmer, Oliver \ Martrikelnummer: XXX @@ -8,12 +9,13 @@ Martrikelnummer: XXX Abgabedatum: 28. Mai 2023 ## Allgemeine Anmerkungen + - es darf nicht auf andere Kapitel als Leistungsnachweis verwiesen werden (z.B. in der Form “XY wurde schon in Kapitel 2 behandelt, daher hier keine Ausführung”) - alles muss in UTF-8 codiert sein (Text und Code) - sollten mündliche Aussagen den schriftlichen Aufgaben widersprechen, gelten die schriftlichen Aufgaben (ggf. an Anpassung der schriftlichen Aufgaben erinnern!) - alles muss ins Repository (Code, Ausarbeitung und alles was damit zusammenhängt) - die Beispiele sollten wenn möglich vom aktuellen Stand genommen werden - - finden sich dort keine entsprechenden Beispiele, dürfen auch ältere Commits unter Verweis auf den Commit verwendet werden + - finden sich dort keine entsprechenden Beispiele, dürfen auch ältere Commits unter Verweis auf den Commit verwendet werden - Ausnahme: beim Kapitel “Refactoring” darf von vorne herein aus allen Ständen frei gewählt werden (mit Verweis auf den entsprechenden Commit) - falls verlangte Negativ-Beispiele nicht vorhanden sind, müssen entsprechend mehr Positiv-Beispiele gebracht werden - Achtung: werden im Code entsprechende Negativ-Beispiele gefunden, gibt es keine Punkte für die zusätzlichen Positiv-Beispiele @@ -24,25 +26,31 @@ Abgabedatum: 28. Mai 2023 - Code-Beispiel = Code in das Dokument kopieren ## 1. Einführung + ### Übersicht über die Applikation + AutoChef ist eine Anwendung zur einfachen und effizienten Verwaltung und Erstellung von Essensplänen, sowie dazugehöriger Einkaufslisten. Zugrunde liegt dabei eine Datenbank an Rezepten. Diese ist beliebig erweiterbar durch eine [Chefkoch](chefkoch.de)-Integration. Diese ermöglicht es, einen Link zu einem Rezept auf Chefkoch an die Anwendung zu übergeben, woraufhin diese das entsprechende Rezept herunterlädt und persistiert. Diese Datenbank an Rezepten, sowie jeweilige Rezept-Details, können jederzeit eingesehen werden. Hauptfunktion ist jedoch die Generierung von Essensplänen. Dafür kann der Nutzer einen Zeitraum, sowie die Anzahl an Personen je Mahlzeit angeben und die Anwendung generiert anhand der Rezept-Datenbank, einen zufälligen Essensplan. Für diesen Essensplan wird ebenfalls eine Einkaufsliste generiert, die an die Anzahl an Personen angepasst ist. Mit diesen Funktionalitäten ist es einfach möglich, seine Woche kulinarisch zu planen und die Einkaufliste für den Wocheneinkauf zu erstellen. -*[Was macht die Applikation? Wie funktioniert sie? Welches Problem löst sie/welchen Zweck hat sie?]* +_[Was macht die Applikation, Wie funktioniert sie? Welches Problem löst sie/welchen Zweck hat sie?]_ ### Wie startet man die Applikation? + Bei AutoChef handelt es sich um eine CLI-Anwendung, geschrieben in Java 19. Zum Starten wird daher lediglich ein Desktop-Rechner mit **Java 19 aufwärts** benötigt. Die Anwendung kann dann über ein Konsolenfenster mit dem Befehl `java -jar AutoChef.jar` gestartet werden. -*[Wie startet man die Applikation? Welche Voraussetzungen werden benötigt? Schritt-für-Schritt-Anleitung]* +_[Wie startet man die Applikation? Welche Voraussetzungen werden benötigt? Schritt-für-Schritt-Anleitung]_ ### Wie testet man die Applikation? + Nach dem Start der Anwendung wird der Nutzer durch einen intuitiven Dialog-Prozess begrüßt und geleitet. Der Nutzer interagiert dabei mit der Anwendung mittels des Konsolenfensters. Über dieses werden sowohl Informationen ausgegeben, als auch Eingaben vom Nutzer eingeholt. Der Dialog-Prozess ist so gestaltet, dass der Nutzer in der Regel mehrere nummerierte Optionen zur Auswahl hat und nur die Nummer der gewünschten Option eingeben und mit Enter absenden muss. Für den Import von Rezepten muss der Nutzer sich zuvor ein Rezept von Chefkoch aussuchen und im entsprechenden Dialog-Prozess-Schritt den dazugehörigen Link einfügen. Zu Beginn der Nutzung ist die Rezept-Datenbank noch leer, weshalb es sich anbietet anfangs ein paar Rezepte zu importieren. Erst danach können die Funktionen der Rezept-Anzeige und Essensplan-Generierung sinnvoll genutzt werden. Hinweis: Zur Persistierung der Rezepte erstellt die Anwendung im aktuellen Arbeitsverzeichnis (des Konsolenfensters) einen `recipes`-Ordner. Falls der Nutzer keine Rechte hat im aktuellen Arbeitsverzeichnis Ordner & Dateien zu erstellen, sowie in diese zu schreiben, kann das Programm nicht ordnungsgemäß arbeiten und terminiert. Versuche in dem Fall die Anwendung mit erhöhten Berechtigungen zu starten oder in einem Arbeitsverzeichnis mit Schreibzugriff auszuführen. -*[Wie testet man die Applikation? Welche Voraussetzungen werden benötigt? Schritt-für-Schritt-Anleitung]* +_[Wie testet man die Applikation? Welche Voraussetzungen werden benötigt? Schritt-für-Schritt-Anleitung]_ ## 2. Clean Architecture + ### Was ist Clean Architecture? + Clean Architecture ist eine Architektur- und Designphilosophie, die darauf abzielt, komplexe Softwaresysteme in leicht verständliche, wartbare und erweiterbare Komponenten zu unterteilen. Es wurde von Robert C. Martin entwickelt und basiert auf den SOLID-Prinzipien. Im Wesentlichen sieht Clean Architecture vor, dass eine Software in mehrere Schichten unterteilt wird, wobei jede Schicht eine klare Abhängigkeitshierarchie aufweist und nur von der nächstgelegenen Schicht abhängt. Die äußerste Schicht ist die Benutzerschnittstelle, die direkt mit dem Benutzer interagiert, gefolgt von einer oder mehreren Schichten mit Geschäftslogik, Datenzugriff und Infrastruktur. @@ -53,97 +61,367 @@ Clean Architecture ermutigt auch zur Verwendung von Schnittstellen, um die Abhä Zusammenfassend lässt sich sagen, dass Clean Architecture eine Methode ist, um große, komplexe Softwaresysteme in einfachere, leichter zu wartende Komponenten aufzuteilen, indem eine klare Abhängigkeitshierarchie zwischen den Komponenten eingeführt wird. -*[allgemeine Beschreibung der Clean Architecture in eigenen Worten]* +_[allgemeine Beschreibung der Clean Architecture in eigenen Worten]_ ### Analyse der Dependency Rule -*[(1 Klasse, die die Dependency Rule einhält und eine Klasse, die die Dependency Rule verletzt); jeweils UML der Klasse und Analyse der Abhängigkeiten in beide Richtungen (d.h., von wem hängt die Klasse ab und wer hängt von der Klasse ab) in Bezug auf die Dependency Rule]* + +_[(1 Klasse, die die Dependency Rule einhält und eine Klasse, die die Dependency Rule verletzt); jeweils UML der Klasse und Analyse der Abhängigkeiten in beide Richtungen (d.h., von wem hängt die Klasse ab und wer hängt von der Klasse ab) in Bezug auf die Dependency Rule]_ #### Positiv-Beispiel: Dependency Rule + ![Dependency Rule positives Beispiel UML](http://www.plantuml.com/plantuml/proxy?cache=no&src=https://raw.githubusercontent.com/ncryptedV1/AutoChef/docs/uml/dependency-rule-pos.iuml) #### Negativ-Beispiel: Dependency Rule + ![Dependency Rule negatives Beispiel UML](http://www.plantuml.com/plantuml/proxy?cache=no&src=https://raw.githubusercontent.com/ncryptedV1/AutoChef/docs/uml/dependency-rule-neg.iuml) ### Analyse der Schichten -*[jeweils 1 Klasse zu 2 unterschiedlichen Schichten der Clean-Architecture: jeweils UML der Klasse (ggf. auch zusammenspielenden Klassen), Beschreibung der Aufgabe, Einordnung mit Begründung in die Clean-Architecture]* + +_[jeweils 1 Klasse zu 2 unterschiedlichen Schichten der Clean-Architecture: jeweils UML der Klasse (ggf. auch zusammenspielenden Klassen), Beschreibung der Aufgabe, Einordnung mit Begründung in die Clean-Architecture]_ #### Schicht: [Name] + ![Schicht 1 UML](http://www.plantuml.com/plantuml/proxy?cache=no&src=https://raw.githubusercontent.com/ncryptedV1/AutoChef/docs/uml/layer-1.iuml) + #### Schicht: [Name] + ![Schicht 2 UML](http://www.plantuml.com/plantuml/proxy?cache=no&src=https://raw.githubusercontent.com/ncryptedV1/AutoChef/docs/uml/layer-2.iuml) ## 3. SOLID + ### Analyse Single-Responsibility-Principle (SRP) -*[jeweils eine Klasse als positives und negatives Beispiel für SRP; jeweils UML der Klasse und Beschreibung der Aufgabe bzw. der Aufgaben und möglicher Lösungsweg des Negativ-Beispiels (inkl. UML)]* + +_[jeweils eine Klasse als positives und negatives Beispiel für SRP; jeweils UML der Klasse und Beschreibung der Aufgabe bzw. der Aufgaben und möglicher Lösungsweg des Negativ-Beispiels (inkl. UML)]_ #### Positiv-Beispiel + ![Single Responsibility positives Beispiel UML](http://www.plantuml.com/plantuml/proxy?cache=no&src=https://raw.githubusercontent.com/ncryptedV1/AutoChef/docs/uml/single-responsibility-pos.iuml) + #### Negativ-Beispiel + ![Single Responsibility negatives Beispiel UML](http://www.plantuml.com/plantuml/proxy?cache=no&src=https://raw.githubusercontent.com/ncryptedV1/AutoChef/docs/uml/single-responsibility-neg.iuml) ### Analyse Open-Closed-Principle (OCP) -*[jeweils eine Klasse als positives und negatives Beispiel für OCP; jeweils UML der Klasse und Analyse mit Begründung, warum das OCP erfüllt/nicht erfüllt wurde – falls erfüllt: warum hier sinnvoll/welches Problem gab es? Falls nicht erfüllt: wie könnte man es lösen (inkl. UML)?]* + +_[jeweils eine Klasse als positives und negatives Beispiel für OCP; jeweils UML der Klasse und Analyse mit Begründung, warum das OCP erfüllt/nicht erfüllt wurde – falls erfüllt: warum hier sinnvoll/welches Problem gab es? Falls nicht erfüllt: wie könnte man es lösen (inkl. UML)?]_ #### Positiv-Beispiel + ![Open-Closed positives Beispiel UML](http://www.plantuml.com/plantuml/proxy?cache=no&src=https://raw.githubusercontent.com/ncryptedV1/AutoChef/docs/uml/open-closed-pos.iuml) + #### Negativ-Beispiel + ![Open-Closed negatives Beispiel UML](http://www.plantuml.com/plantuml/proxy?cache=no&src=https://raw.githubusercontent.com/ncryptedV1/AutoChef/docs/uml/open-closed-neg.iuml) ### Analyse Liskov-Substitution- (LSP), Interface-Segreggation- (ISP), Dependency-Inversion-Principle (DIP) -*[jeweils eine Klasse als positives und negatives Beispiel für entweder LSP oder ISP oder DIP); jeweils UML der Klasse und Begründung, warum man hier das Prinzip erfüllt/nicht erfüllt wird]* \ -*[Anm.: es darf nur ein Prinzip ausgewählt werden; es darf NICHT z.B. ein positives Beispiel für LSP und ein negatives Beispiel für ISP genommen werden]* + +_[jeweils eine Klasse als positives und negatives Beispiel für entweder LSP oder ISP oder DIP); jeweils UML der Klasse und Begründung, warum man hier das Prinzip erfüllt/nicht erfüllt wird]_ \ +_[Anm.: es darf nur ein Prinzip ausgewählt werden; es darf NICHT z.B. ein positives Beispiel für LSP und ein negatives Beispiel für ISP genommen werden]_ #### Positiv-Beispiel + ![Liskov positives Beispiel UML](http://www.plantuml.com/plantuml/proxy?cache=no&src=https://raw.githubusercontent.com/ncryptedV1/AutoChef/docs/uml/liskov-pos.iuml) + #### Negativ-Beispiel + ![Liskov negatives Beispiel UML](http://www.plantuml.com/plantuml/proxy?cache=no&src=https://raw.githubusercontent.com/ncryptedV1/AutoChef/docs/uml/liskov-neg.iuml) ## 4. Weitere Prinzipien + ### Analyse GRASP: Geringe Kopplung -*[jeweils eine bis jetzt noch nicht behandelte Klasse als positives und negatives Beispiel geringer Kopplung; jeweils UML Diagramm mit zusammenspielenden Klassen, Aufgabenbeschreibung und Begründung für die Umsetzung der geringen Kopplung bzw. Beschreibung, wie die Kopplung aufgelöst werden kann]* + +_[jeweils eine bis jetzt noch nicht behandelte Klasse als positives und negatives Beispiel geringer Kopplung; jeweils UML Diagramm mit zusammenspielenden Klassen, Aufgabenbeschreibung und Begründung für die Umsetzung der geringen Kopplung bzw. Beschreibung, wie die Kopplung aufgelöst werden kann]_ #### Positiv-Beispiel + ![Kopplung positives Beispiel UML](http://www.plantuml.com/plantuml/proxy?cache=no&src=https://raw.githubusercontent.com/ncryptedV1/AutoChef/docs/uml/coupling-pos.iuml) + #### Negativ-Beispiel + ![Kopplung negatives Beispiel UML](http://www.plantuml.com/plantuml/proxy?cache=no&src=https://raw.githubusercontent.com/ncryptedV1/AutoChef/docs/uml/coupling-neg.iuml) ### Analyse GRASP: Hohe Kohäsion -*[eine Klasse als positives Beispiel hoher Kohäsion; UML Diagramm und Begründung, warum die Kohäsion hoch ist] + +\*[eine Klasse als positives Beispiel hoher Kohäsion; UML Diagramm und Begründung, warum die Kohäsion hoch ist] ![Kohäsion Beispiel UML](http://www.plantuml.com/plantuml/proxy?cache=no&src=https://raw.githubusercontent.com/ncryptedV1/AutoChef/docs/uml/cohesion.iuml) -### Don’t Repeat Yourself (DRY)* -*[ein Commit angeben, bei dem duplizierter Code/duplizierte Logik aufgelöst wurde; Code-Beispiele (vorher/nachher); begründen und Auswirkung beschreiben]* +### Don’t Repeat Yourself (DRY)\* + +_[ein Commit angeben, bei dem duplizierter Code/duplizierte Logik aufgelöst wurde; Code-Beispiele (vorher/nachher); begründen und Auswirkung beschreiben]_ ## 5. Unit Tests + ### 10 Unit Tests -*[Nennung von 10 Unit-Tests und Beschreibung, was getestet wird]* -| Unit Test | Beschreibung | -|------------------|--------------| -| *Klasse#Methode* | | -| | | -| | | -| | | + +_[Nennung von 10 Unit-Tests und Beschreibung, was getestet wird]_ +| Unit Test | Beschreibung | +|---------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------| +| _TestUnit#testHashCodeTrue_ | testet, ob die `hashCode` Funktion der `Unit` Klasse, für zwei Objekte, den selben Hashcode korrekt zurückgibt zurückgibt | +| _TestUnit#testEqualsResSame_ | testet, ob die `equals` Methode der `Unit` Klasse zwei Unit Instanzen mit dem selben `value` als korrekt gleich vergleicht | +| _TestQuantity#testConstructorException_ | testet, ob der Konstruktur der `Quantity` Klasse fehlschlägt, sobald negative (invalide) Werte übergeben werden | +| _TestQuantity#testMultiply_ | testet, ob die `multiply` Methode der `Quantity` Klasse den Wert korrekt multipliziert | +| _TestGroceryItem#testEqualsResSelf_ | testet, ob die `equals` Methode der `GroceryItem` Klasse zwei Objekte mit dem selben `value` als gleich ansieht | +| _TestWebsiteFetcher#testGetWebsiteBodyInvalidUrl_ | testet, ob die `testGetWebsiteBodyInvalidUrl` Methode der `WebsiteFetcher` Klasse eine Exception wirft bei einer invaliden URL | +| _TestChefkochRecipeFetcher#testGetRecipe_ | testet, ob die `testGetRecipe` Methode der `ChefkochRecipeFetcher` Klasse die richtigen Inhalte für ein Chefkoch-Rezept aus dem Internet liefert | +| _TestRecipe#testConstructorHappyPath_ | testet, ob der Konstrutor der `Recipe` Klasse nicht `null` zurückgibt | +| _TestMeal#testGetGroceryList_ | testet, ob die `getGroceryList` Methode der `Meal` Klasse eine korrekt aggregierte Zutatenliste zurückgibt | +| _TestGroceryList#testAddItem_ | testet, ob die `addItem` Methode der `GroceryList` Klasse korrekt eine Itme zur GroceryList hinzufügt | ### ATRIP: Automatic -*[Begründung/Erläuterung, wie ‘Automatic’ realisiert wurde]* +Die Tests wurden mittels Testautomatisierung realisiert. Dabei wurde JUnit 5 verwendet, um automatisierte Tests zu schreiben. Über die IDE IntelliJ IDEA können die Test simpel über einen Knopfdruck ausgeführt werden. Im Anschluss laufen alle Tests automatisch. Das Ergebnis der Testautomatisierung zeigt, ob ein individueller Test erfolgreich (bestanden) oder nicht erfolgreich (fehlgeschlagen) war. ### ATRIP: Thorough -*[jeweils 1 positives und negatives Beispiel zu ‘Thorough’; jeweils Code-Beispiel, Analyse und Begründung, was professionell/nicht professionell ist]* + +_[jeweils 1 positives und negatives Beispiel zu ‘Thorough’; jeweils Code-Beispiel, Analyse und Begründung, was professionell/nicht professionell ist]_ + +#### positives Beispiel + +Test-Klasse: `TestIngredient` + +```java + @Test + void testEqualsResSelf() { + // arrange + Ingredient ingredient1 = mock(Ingredient.class); + + // act + boolean res = ingredient1.equals(ingredient1); + + // assert + assertTrue(res); + } + + @Test + void testEqualsResSame() { + // arrange + String value = "banana"; + Ingredient ingredient1 = new Ingredient(value); + Ingredient ingredient2 = new Ingredient(value); + + // act + boolean res = ingredient1.equals(ingredient2); + + // assert + assertTrue(res); + } + + @Test + void testEqualsDifferent() { + // arrange + Ingredient ingredient1 = new Ingredient("banana"); + Ingredient ingredient2 = new Ingredient("nutella"); + + // act + boolean res = ingredient1.equals(ingredient2); + + // assert + assertFalse(res); + } + + @Test + void testEqualsNull() { + // arrange + Ingredient ingredient1 = mock(Ingredient.class); + + // act + boolean res = ingredient1.equals(null); + + // assert + assertFalse(res); + } + + @Test + void testHashCodeTrue() { + // arrange + String value = "banana"; + Ingredient ingredient1 = new Ingredient(value); + Ingredient ingredient2 = new Ingredient(value); + + // act + int code1 = ingredient1.hashCode(); + int code2 = ingredient2.hashCode(); + + // assert + assertEquals(code1, code2); + } + + @Test + void testHashCodeFalse() { + // arrange + Ingredient ingredient1 = new Ingredient("banana"); + Ingredient ingredient2 = new Ingredient("nutella"); + + // act + int code1 = ingredient1.hashCode(); + int code2 = ingredient2.hashCode(); + + // assert + assertNotEquals(code1, code2); + } +``` +Diese Testklasse mit den dargestellten Methoden ist ein Positivbeispiel für "thorough testing". All diese Testmethoden testen verschiedene Zweige der selbstimplementieren `equals` Methode der Klasse `Ingredient`. Sie testen Vergeliche zwischen +- einer `Ingredient`-Instanz mit sich selbst +- zwei gleichen `Ingredient`-Instanzen +- zwei verschiedenen `Ingredient`-Instanzen +- `null` und einer `Ingredient`-Instanz + +Damit sind alle relevanten Pfade der `equals` Methode abgedeckt. Außerdem wird auch die Umgebung getestet. Da die `equals` Methode, auf die `hashcode` Methode zugreift, können Fehler in der `equals` Methode auf Fehler in der dortigen zurückgeführt werden. Demnentsprechend müssen auch alle relevanten Pfade der `hashcode` Methode getestet werden, was hier gemacht wird. + +#### negatives Beispiel +zu testende Methode: `DialogService#startMealPlanGeneration` + +```java + private void startMealPlanGeneration() { + currentState = DialogState.MEAL_PLAN_GENERATION; + + ConsoleOutputService.rawOut("Wir generieren jetzt zusammen einen Mahlzeiten-Plan. :D"); + LocalDate startDate = ConsoleInputParser.getDate(null, null, + "Wann soll der Plan beginnen? (DD.MM.YYYY)"); + LocalDate endDate = ConsoleInputParser.getDate(startDate, null, + "Bis wann soll der Plan gehen (exklusiv)? (DD.MM.YYYY)"); + int people = ConsoleInputParser.getInteger(1, 99, + "Für wie viele Leute soll der Plan generiert werden?"); + int days = startDate.until(endDate).getDays(); + ConsoleOutputService.rawOut("Ok, ich generiere einen Plan für " + days + " Tage..."); + + List recipes = recipeRepository.getRecipes(); + List meals = new ArrayList<>(); + for (int i = 0; i < days; i++) { + meals.add(new Meal(recipes.get(random.nextInt(recipes.size())), people)); + } + + MealPlan mealPlan = new MealPlan(meals, startDate, endDate); + + startPostMealPlanGeneration(mealPlan, recipes); + } +``` + +Die hier gezeigt Methode `DialogService#startMealPlanGeneration` wurde nicht getestet, obwohl es möglich wäre dies zu tun. Damit stellt sie ein Negativbeispiel für die "thorough testing" dar. Der gezeigt Code startet die Generierung einer Essensplans. Dazu werden verschiedene Nutzereingaben abgefordert, z.B. Start und Enddatum, Anzahl der Personen. Im Anschluss werden die Rezepte geladen und mit einer zufälligen Teilmenge wird der Essensplan erstellt. Der Essensplan und die liste an Rezepten werden im Anschluss an eine weitere Methode weitergegeben. + +Da die Tests für diese Methode vollständig fehlen, werden dementsprechend auch alle Pfade nicht getestet. Dementsprechend kann nicht herausgefunden werden, wo sich logische Fehler befinden. ### ATRIP: Professional -*[jeweils 1 positives und negatives Beispiel zu ‘Professional’; jeweils Code-Beispiel, Analyse und Begründung, was professionell/nicht professionell ist]* + +#### positives Beispiel + +Test-Methode: `TestWebsiteFetcher#testGetWebsiteBodyInvalidUrl` +```java + @Test + public void testGetWebsiteBodyInvalidUrl() { + // Given + String invalidUrl = "any string"; + + // When and Then + assertThrows(IllegalArgumentException.class, () -> WebsiteFetcher.getWebsiteBody(invalidUrl)); + } +``` + +Diese Testmethode testet die `getWebsiteBody` Methode der `WebsiteFetcher` Klasse. Hierbei wird ein String, der eine invalide URL darstellt, in die zu testende Funktion übergeben. Anschließend wird die `getWebsiteBody` Methode aufgerufen und überprüft, ob die richtige Exception geworfen wird. + +Diese Testmethode ist ein Positivbeispiel für professionelle Testklassen aus mehreren Gründen: +1. Der Name der Testmethode beschreibt gut, was genau getestet wird. In diesem Fall die `getWebsiteBody` Methode bei Eingabe einer invaliden URL. +2. Die zugehörige Klasse wurde nur zu Testzwecken angelegt. +3. Im Gegensatz zu Getter- oder Setter-Methoden existiert Logik, die getestetet werden sollte. Ein Test ist dementsprechend notwendig + + +#### negatives Beispiel +Test-Methode: `TestUnit#getValue` + +```java + @Test + void testGetValue() { + // arrange + String expected = "piece"; + Unit unit = new Unit(expected); + + // act + String res = unit.getValue(); + + // assert + assertEquals(expected, res); + } +``` + +Diese Testmethode testet die `getValue` Getter-Methode der `Unit` Klasse. Dabei wird ein `Unit` ValueObject angelegt mit einem initialen Wert. Das Ergebnis der `getValue` wird verglichen mit dem initialen Wert. Beide Werten sollten gleich sein. + +Diese Klasse ist ein Negativbeispiel, da sie einen unnötigen Test darstellt. Getter Methoden sollten nicht getestet werden. Des Weiteren enthält diese Methode keine komplexe Logik, die ein Testen erfordern würde. Es handelt sich hier um einen Test, "der nur wegen des Tests geschrieben wurde". Außerdem ist der Dokumentationswert der Methode nicht vorhanden. ### Code Coverage -*[Code Coverage im Projekt analysieren und begründen]* + +Die folgende Tabelle zeigt die summierten Werte der verschiedenen Arten von Testabdeckung des Projektes. Eine aufgeschlüsselte Version der Testabdeckung ist im folgenden Bild zu sehen. Die Prozentangaben des Bildes folgen derselben Reihenfolge wie in der Tabelle aufgelistet. + +| Art | % | +|-----------------|----| +| Class-Coverage | 70 | +| Method-Coverage | 66 | +| Line-Coverage | 51 | + +![test coverage](./docs/res/cov.png) + +Im Allgemeinen bestand das Ziel, die Testabdeckung so hoch wie möglich zu halten. Deshalb wurden weitesgehend alle Klassen automatisiert getestet, jedoch nicht alle. Das hat vor allem den Grund, dass die automatisierte Testung für einige Klassen unserer Ansicht nicht sinnhaft war. Beispiele hierfür sind: + +- `domain.util.Formats`: Diese Klasse dient lediglich als Format-Klasse ohne weitere Logik. +- `domain.util.io.ConsoleOutputService`: Da es sich hier vor allem um die Formattierung von Konsolenausgaben handelt, wurden hier die Tests ebenso vernachlässigt. + +Unabhängig dessen begründet sich die Code-Coverage wie folgt: +- Class-Coverage: die Mehrheit der Klassen weist Tests auf, weshalb der Wert hier mit 70% vergleichsweise hoch liegt. +- Method-Coverage: Aus genannten Gründen wurden eine Reihe von Methoden nicht getestet, daher liegt die Method-Coverage unter der Class-Coverage. +- Line-Coverage: Die Line-Coverage ist durch die Method-Coverage bedingt und ist deshalb vor allem aus demselbigen Grund niedriger. ### Fakes und Mocks -*[Analyse und Begründung des Einsatzes von 2 Fake/Mock-Objekten; zusätzlich jeweils UML Diagramm der Klasse]* + +In diesem Projekt wurden vor allem Mock-Objekte eingesetzt. Sie wurden genutzt, um benötigte Nebenklassen zu mocken. Nachfolgend sind zwei demonstrative Beispiel für den Einsatz von Mock-Objekten mit dazugehörigen UML Diagrammen zu sehen. + +Beispiel aus: `TestGroceryList#testConstructorVarArgs` +```java + @Test + public void testConstructorVarArgs() { + // arrange + GroceryItem item1 = mock(GroceryItem.class); + GroceryItem item2 = mock(GroceryItem.class); + + // act + GroceryList list = new GroceryList(item1, item2); + + // assert + assertNotNull(list); + } +``` ![Fakes und Mocks Beispiel 1 UML](http://www.plantuml.com/plantuml/proxy?cache=no&src=https://raw.githubusercontent.com/ncryptedV1/AutoChef/docs/uml/fake-mock-1.iuml) + +_[TODO: Analyse und Begründung für Einsatz] + + +Beispiel aus: `TestMealPlan#setUp` +```java + @BeforeEach + public void setUp() { + start = LocalDate.now(); + end = start.plusDays(5); + int totalMeals = start.until(end).getDays(); + + List meals = new ArrayList<>(); + for (int i = 0; i < totalMeals; i++) { + meals.add(mock(Meal.class)); + } + + mealPlan = new MealPlan(meals, start, end); + } +``` + ![Fakes und Mocks Beispiel 2 UML](http://www.plantuml.com/plantuml/proxy?cache=no&src=https://raw.githubusercontent.com/ncryptedV1/AutoChef/docs/uml/fake-mock-2.iuml) +_[TODO: Analyse und Begründung für Einsatz] + +_[Analyse und Begründung des Einsatzes von 2 Fake/Mock-Objekten; zusätzlich jeweils UML Diagramm der Klasse]_ + + + ## 6. Domain Driven Design + ### Ubiquitous Language -*[4 Beispiele für die Ubiquitous Language; jeweils Bezeichung, Bedeutung und kurze Begründung, warum es zur Ubiquitous Language gehört]* + +_[4 Beispiele für die Ubiquitous Language; jeweils Bezeichung, Bedeutung und kurze Begründung, warum es zur Ubiquitous Language gehört]_ | Bezeichnung | Bedeutung | Begründung | |-------------|-----------|------------| | | | | @@ -152,40 +430,53 @@ Zusammenfassend lässt sich sagen, dass Clean Architecture eine Methode ist, um | | | | ### Entities -*[UML, Beschreibung und Begründung des Einsatzes einer Entity; falls keine Entity vorhanden: ausführliche Begründung, warum es keines geben kann/hier nicht sinnvoll ist]* + +_[UML, Beschreibung und Begründung des Einsatzes einer Entity; falls keine Entity vorhanden: ausführliche Begründung, warum es keines geben kann/hier nicht sinnvoll ist]_ ![Entity Beispiel UML](http://www.plantuml.com/plantuml/proxy?cache=no&src=https://raw.githubusercontent.com/ncryptedV1/AutoChef/docs/uml/entity.iuml) ### Value Objects -*[UML, Beschreibung und Begründung des Einsatzes eines Value Objects; falls kein Value Object vorhanden: ausführliche Begründung, warum es keines geben kann/hier nicht sinnvoll ist]* + +_[UML, Beschreibung und Begründung des Einsatzes eines Value Objects; falls kein Value Object vorhanden: ausführliche Begründung, warum es keines geben kann/hier nicht sinnvoll ist]_ ![Entity Beispiel UML](http://www.plantuml.com/plantuml/proxy?cache=no&src=https://raw.githubusercontent.com/ncryptedV1/AutoChef/docs/uml/value-object.iuml) ### Repositories -*[UML, Beschreibung und Begründung des Einsatzes eines Repositories; falls kein Repository vorhanden: ausführliche Begründung, warum es keines geben kann/hier nicht sinnvoll ist]* + +_[UML, Beschreibung und Begründung des Einsatzes eines Repositories; falls kein Repository vorhanden: ausführliche Begründung, warum es keines geben kann/hier nicht sinnvoll ist]_ ![Repository Beispiel UML](http://www.plantuml.com/plantuml/proxy?cache=no&src=https://raw.githubusercontent.com/ncryptedV1/AutoChef/docs/uml/repository.iuml) ### Aggregates -*[UML, Beschreibung und Begründung des Einsatzes eines Aggregates; falls kein Aggregate vorhanden: ausführliche Begründung, warum es keines geben kann/hier nicht sinnvoll ist]* + +_[UML, Beschreibung und Begründung des Einsatzes eines Aggregates; falls kein Aggregate vorhanden: ausführliche Begründung, warum es keines geben kann/hier nicht sinnvoll ist]_ ![Aggregate Beispiel UML](http://www.plantuml.com/plantuml/proxy?cache=no&src=https://raw.githubusercontent.com/ncryptedV1/AutoChef/docs/uml/aggregate.iuml) ## 7. Refactoring + ### Code Smells -*[jeweils 1 Code-Beispiel zu 2 Code Smells aus der Vorlesung; jeweils Code-Beispiel und einen möglichen Lösungsweg bzw. den genommen Lösungsweg beschreiben (inkl. (Pseudo-)Code)]* + +_[jeweils 1 Code-Beispiel zu 2 Code Smells aus der Vorlesung; jeweils Code-Beispiel und einen möglichen Lösungsweg bzw. den genommen Lösungsweg beschreiben (inkl. (Pseudo-)Code)]_ ### 2 Refactorings -*[2 unterschiedliche Refactorings aus der Vorlesung anwenden, begründen, sowie UML vorher/nachher liefern; jeweils auf die Commits verweisen]* + +_[2 unterschiedliche Refactorings aus der Vorlesung anwenden, begründen, sowie UML vorher/nachher liefern; jeweils auf die Commits verweisen]_ #### Refactoring 1 + ![Refactoring Beispiel 1 Pre UML](http://www.plantuml.com/plantuml/proxy?cache=no&src=https://raw.githubusercontent.com/ncryptedV1/AutoChef/docs/uml/refactoring-1-pre.iuml) ![Refactoring Beispiel 1 Post UML](http://www.plantuml.com/plantuml/proxy?cache=no&src=https://raw.githubusercontent.com/ncryptedV1/AutoChef/docs/uml/refactoring-1-post.iuml) #### Refactoring 2 + ![Refactoring Beispiel 2 Pre UML](http://www.plantuml.com/plantuml/proxy?cache=no&src=https://raw.githubusercontent.com/ncryptedV1/AutoChef/docs/uml/refactoring-2-pre.iuml) ![Refactoring Beispiel 2 Post UML](http://www.plantuml.com/plantuml/proxy?cache=no&src=https://raw.githubusercontent.com/ncryptedV1/AutoChef/docs/uml/refactoring-2-post.iuml) ## 8. Entwurfsmuster -*[2 unterschiedliche Entwurfsmuster aus der Vorlesung (oder nach Absprache auch andere) jeweils sinnvoll einsetzen, begründen und UML-Diagramm]* + +_[2 unterschiedliche Entwurfsmuster aus der Vorlesung (oder nach Absprache auch andere) jeweils sinnvoll einsetzen, begründen und UML-Diagramm]_ #### Entwurfsmuster: [Name] + ![Entwurfstmuster 1 Beispiel UML](http://www.plantuml.com/plantuml/proxy?cache=no&src=https://raw.githubusercontent.com/ncryptedV1/AutoChef/docs/uml/design-pattern-1.iuml) + #### Entwurfsmuster: [Name] + ![Entwurfstmuster 2 Beispiel UML](http://www.plantuml.com/plantuml/proxy?cache=no&src=https://raw.githubusercontent.com/ncryptedV1/AutoChef/docs/uml/design-pattern-1.iuml) From d89dcb38a0e45759dd3e689593870d1e9ed0da96 Mon Sep 17 00:00:00 2001 From: cuvar Date: Mon, 3 Apr 2023 12:34:58 +0200 Subject: [PATCH 06/39] fix: make days DRY --- .../java/de/cunc/autochef/domain/aggregate/MealPlan.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/main/java/de/cunc/autochef/domain/aggregate/MealPlan.java b/src/main/java/de/cunc/autochef/domain/aggregate/MealPlan.java index 989e857..cd47b1f 100644 --- a/src/main/java/de/cunc/autochef/domain/aggregate/MealPlan.java +++ b/src/main/java/de/cunc/autochef/domain/aggregate/MealPlan.java @@ -18,7 +18,10 @@ public class MealPlan { private LocalDate end; public MealPlan(List meals, LocalDate start, LocalDate end) { - int days = start.until(end).getDays(); + this.start = start; + this.end = end; + + int days = getDays(); if (meals.size() != days) { throw new IllegalArgumentException( "Mahlzeiten-Plan spannt " + days + " Tage, es wurden allerdings nur " + meals.size() @@ -26,8 +29,6 @@ public MealPlan(List meals, LocalDate start, LocalDate end) { } this.meals = meals; - this.start = start; - this.end = end; } public List getMeals() { From 3c4ba0729da0cb088446f5a4b43f677541761a11 Mon Sep 17 00:00:00 2001 From: cuvar Date: Mon, 3 Apr 2023 14:13:47 +0200 Subject: [PATCH 07/39] feat: chapter 7 --- README.md | 52 ++++++++++++++++++++++++++++++++++--------- uml/aggregate.iuml | 36 ++++++++++++++++++++++++++++++ uml/entity.iuml | 16 +++++++++++++ uml/repository.iuml | 23 +++++++++++++++++++ uml/value-object.iuml | 12 ++++++++++ 5 files changed, 128 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index bd8ce4b..99c394d 100644 --- a/README.md +++ b/README.md @@ -146,7 +146,7 @@ _[jeweils eine bis jetzt noch nicht behandelte Klasse als positives und negative ![Kohäsion Beispiel UML](http://www.plantuml.com/plantuml/proxy?cache=no&src=https://raw.githubusercontent.com/ncryptedV1/AutoChef/docs/uml/cohesion.iuml) ### Don’t Repeat Yourself (DRY)\* - +commit sha: d89dcb3 _[ein Commit angeben, bei dem duplizierter Code/duplizierte Logik aufgelöst wurde; Code-Beispiele (vorher/nachher); begründen und Auswirkung beschreiben]_ ## 5. Unit Tests @@ -422,33 +422,63 @@ _[Analyse und Begründung des Einsatzes von 2 Fake/Mock-Objekten; zusätzlich je ### Ubiquitous Language _[4 Beispiele für die Ubiquitous Language; jeweils Bezeichung, Bedeutung und kurze Begründung, warum es zur Ubiquitous Language gehört]_ -| Bezeichnung | Bedeutung | Begründung | -|-------------|-----------|------------| -| | | | -| | | | -| | | | -| | | | +| Bezeichnung | Bedeutung | Begründung | +|--------------|-------------------------------|-------------------------------------------------------------------------------------------------------------------------------------| +| Ingredient | Zutat | Jedes Gericht besteht aus verschiedenen Lebensmitteln sogenannten Zutaten. | +| Grocery item | Element der Lebensmittelliste | Ein Gericht braucht Zutaten in bestimmten Mengen. Lebensmittel auf der Lebensmittelliste sind in Menge und Einheit daran angepasst. | +| Grocery list | Lebensmittelliste | Für Gerichte werden eine Menge von Lebensmittel benötigt. Die Lebensmittelliste fasst all diese zusammen. | +| Meal plan | Essensplan | Ein Essensplan beschreibt eine Sammlung von Gerichten für einen bestimmten Zeitraum. | ### Entities -_[UML, Beschreibung und Begründung des Einsatzes einer Entity; falls keine Entity vorhanden: ausführliche Begründung, warum es keines geben kann/hier nicht sinnvoll ist]_ +zugehörige Klassen(n): `Recipe` ![Entity Beispiel UML](http://www.plantuml.com/plantuml/proxy?cache=no&src=https://raw.githubusercontent.com/ncryptedV1/AutoChef/docs/uml/entity.iuml) +Die `Recipe` Entity beschreibt ein semantisches Rezept. Wie das UML-Diagram zeigt, besteht ein Rezept aus einem Name, einer Liste von Zutaten (GroceryList) und einer Liste von Schritten zur Zubereitung (RecipeStep). Ein Rezept wird eineindeutig über eine ID indetifiziert. In diesem Fall besteht die ID aus dem Namen in Kleinschrift. Haben also zwei Rezepte den selben Namen, werden sie als gleich angesehen. Weiterhin hat die `Recipe` Klasse mehrere Getter-Methoden für die einzelnen Attribute und die ID als auch einen Konstruktor. + +Bei der Erstellung einer `Recipe`-Instanz mittels des Konstruktors wird die Richtigkeit der Attribute überprüft: +- Der Name muss mindestens ein Zeichen abgesehen von White-Space beinhalten. +- Die `RecipeStep`-Instanzen der Liste müssen in der richtigen Reihenfolge und konsektutiv vollständig sein, d.h. es dürfen keine Schritten zwischendrin fehlen. + +Der Einsatz dieser Entity begründet sich dadurch, dass es notwendig war, ein Rezept abbilden zu können. Rezepte werden gespeichert und haben somit einen Lebenszyklus. Das erzwingt laut Domain Driven Design Richtlinien die Erstellung einer Entity. + ### Value Objects -_[UML, Beschreibung und Begründung des Einsatzes eines Value Objects; falls kein Value Object vorhanden: ausführliche Begründung, warum es keines geben kann/hier nicht sinnvoll ist]_ +zugehörige Klassen(n): `Ingredient` ![Entity Beispiel UML](http://www.plantuml.com/plantuml/proxy?cache=no&src=https://raw.githubusercontent.com/ncryptedV1/AutoChef/docs/uml/value-object.iuml) +Das `Ingredient` Value Object beschreibt eine Zutat für ein Gericht. Es besitzt als einziges Attribute einen Namen, der es definiert. Neben dem Namen besitzt es zwei Getter-Methoden für den Namen (`getValue`) und für die ID (`getID`). Letztere, ist dabei jedoch nicht zur eindeutigen Identifizierung im Sinne einer Entity anzusehen, sondern wird lediglich zum einfacheren Vergleich zweier `Ingredient`-Instanzen verwendet. Die ID setzt sich aus dem Namen in Kleinschrift zusammen. Zusätzlich existiert ein Konstruktor zur Erzeugung einer Ingredient-Instanz, bei der die Richtigkeit des Namens überprüft wird. Ähnlich wie bei der `Recipe`-Klassen, muss ein Namen mindestens ein Zeichen enthalten, das keinem White-Space Zeichen entspricht. + +Ein `Ingredient` hat keinen Lebenszyklus und auch keine relevante Logik implementiert. Es dient lediglich zur Repräsentation von Informationen. Aus diesem Grund ist es auch nicht möglich die Informationen einer `Ingredient`-Instanz anzupassen - sie sind konstant. All diese Punkte begründen, warum sich hier für ein Value Object anstelle einer Entity oder Ähnlichem entschieden wurde. + ### Repositories -_[UML, Beschreibung und Begründung des Einsatzes eines Repositories; falls kein Repository vorhanden: ausführliche Begründung, warum es keines geben kann/hier nicht sinnvoll ist]_ +zugehörige Klassen(n): `RecipeFileRepository` ![Repository Beispiel UML](http://www.plantuml.com/plantuml/proxy?cache=no&src=https://raw.githubusercontent.com/ncryptedV1/AutoChef/docs/uml/repository.iuml) +- `saveRecipe`: speichert ein gegebenes Rezept in einer Datei im Dateiort `recipesFolder` ab +Repositories dienen als Vermittler zwischen Datenmodell und Domänenlogik. Sie werden genutzt, um Daten zu speichern oder sie abzurufen aus ebendiesen Speicher. In diesem Projekt wurde ein Repository `RecipeFileRepository` genutzt, um die Persistenz von Rezepten zu verwalten. Das `RecipeFileRepository` besitzt als einziges Attribut eine Instanz der `File`-Klasse namens `recipesFolder`, die angibt, wo im Dateisystem Rezepte gespeichert werden sollten. Zusätzlich existieren Methoden zur Persistenzverwaltung, die von dem Interface `RecipeRepository` implementiert werden: +- `deleteRecipe`: löscht ein gegebenes Rezept im Dateiort `recipesFolder` +- `getRecipe`: liest ein anhand der ID definiertes Rezept im Dateiort `recipesFolder` ein +- `getRecipes`: liest alle vorhandenen Rezepte im Dateiort `recipesFolder` ein + +Damit sind die relevanten Methoden der Persistenzverwaltung abgedeckt. Bei der Erstellung einer `RecipeFileRepository`-Instanz mittels des Konstruktors wird ebenso geprüft, ob ein Ordner im Pfad `recipesFolder` angelegt werden kann. Sollte das nicht der Fall sein, tritt ein Fehler auf und es kann keine Instanz angelegt werden. + +Um die Persistenzverwaltung gründlich und sauber von der Domänenlogik zu trennen, wurde dieses Repository eingesetzt. Mit Nutzung des Interfaces, kann sichergestellt werden, dass beide Elemente getrennt bleiben. Zusätzlich ermöglicht der Einsatz eines Repositories, Veränderungen an der Persistenzverwaltung vorzunehmen, ob auf die Domänenlogik eingreifen zu müsssen. + ### Aggregates -_[UML, Beschreibung und Begründung des Einsatzes eines Aggregates; falls kein Aggregate vorhanden: ausführliche Begründung, warum es keines geben kann/hier nicht sinnvoll ist]_ +zugehörige Klassen(n): `Meal` ![Aggregate Beispiel UML](http://www.plantuml.com/plantuml/proxy?cache=no&src=https://raw.githubusercontent.com/ncryptedV1/AutoChef/docs/uml/aggregate.iuml) +In diesem Projekt wurde die `Meal`-Klasse als Aggregate ausgewählt.Die `Meal`-Klasse fasst logsiches Verhalten verschiedener Elemente zusammen. Es definiert sich durch ein Rezept `recipe` und einem Integer `adjustedNumberOfPeople`, das darstellt, auf wie viele Personen die Zutatenmenge des Rezeptes angepasst werden soll. Der Konstruktor enthält keine weitere Logik zur Überprüfung der Attribute. Des Weiteren exisiteren folge Methoden: +- `getRecipe`: eine Getter-Methode für `recipe` +- `setRecipe`: eine Setter-Methode für `recipe` +- `getAdjustedNumberOfPeople`: eine Getter-Methode für `adjustedNumberOfPeople` +- `getGroceryList`: eine Getter-Methode, die die Zutaten des Rezeptes zurückgibt. Dabei werden die Mengen der Zutaten auf `adjustedNumberOfPeople` angepasst. + +Damit fasst das `Meal`-Aggregate Logik eines Gerichtes zusammen. Es hält jedoch keine eigene Identität, da dies bereits durch `Recipe` erfolgt ist. Es dient lediglich als Wrapper für ein Rezept um jenes zu repräsentieren. In der Theorie wäre es möglich, der `Meal`-Klasse eine eigenständige Identität und Lebenszyklus zu verleihen und es damit zu einer Entity zu transferieren. Das wäre vor allem hilfreich, wenn Logik zur Persistierung von Gerichten implementiert werden sollte. Da das aber nicht im Umfang der Anwendung inbegriffen ist, ist ein Transfer zu einer Entity nicht notwendig. + ## 7. Refactoring ### Code Smells diff --git a/uml/aggregate.iuml b/uml/aggregate.iuml index e69de29..ce77def 100644 --- a/uml/aggregate.iuml +++ b/uml/aggregate.iuml @@ -0,0 +1,36 @@ +@startuml + +left to right direction + +'class MealPlan { +' - meals: List +' - start: LocalDate +' - end: LocalDate +' + getMeals(): List +' + getStart(): LocalDate +' + getEnd(): LocalDate +' + getDays(): int +' + getGroceryList(): GroceryList +'} + +class Meal { + - recipe: Recipe + - adjustedNumberOfPeople: int + + Meal(recipe: Recipe, adjustedNumberOfPeople: int) + + getRecipe(): Recipe + + setRecipe(recipe: Recipe): void + + getAdjustedNumberOfPeople(): int + + getGroceryList(): GroceryList +} + +'class GroceryList { +' - items: List +' + addItem(GroceryItem entry): void +' + getItems(): List +'} +' +'MealPlan <- GroceryList +'MealPlan <- Meal +'Meal <- GroceryList + +@enduml \ No newline at end of file diff --git a/uml/entity.iuml b/uml/entity.iuml index e69de29..8ac3b06 100644 --- a/uml/entity.iuml +++ b/uml/entity.iuml @@ -0,0 +1,16 @@ +@startuml + +left to right direction + +class Recipe { + - name: String + - groceryList: GroceryList + - recipeSteps: List + + Recipe(name: String) + + getName(): String + + getRecipeSteps(): List + + getGroceryList(): GroceryList + + getId(): String +} + +@enduml \ No newline at end of file diff --git a/uml/repository.iuml b/uml/repository.iuml index e69de29..80149cf 100644 --- a/uml/repository.iuml +++ b/uml/repository.iuml @@ -0,0 +1,23 @@ +@startuml + +left to right direction + +interface RecipeRepository { + + saveRecipe(recipe: Recipe): void + + deleteRecipe(recipe: Recipe): boolean + + getRecipes(): List + + getRecipe(id: String): Recipe +} + +class RecipeFileRepository { + - recipesFolder: File + + RecipeFileRepository(recipesFolder: File) + + saveRecipe(recipe: Recipe): void + + deleteRecipe(recipe: Recipe): boolean + + getRecipes(): List + + getRecipe(id: String): Recipe +} + +RecipeRepository <|.. RecipeFileRepository + +@enduml \ No newline at end of file diff --git a/uml/value-object.iuml b/uml/value-object.iuml index e69de29..349eab7 100644 --- a/uml/value-object.iuml +++ b/uml/value-object.iuml @@ -0,0 +1,12 @@ +@startuml + +left to right direction + +class Ingredient { + - name: String + + Ingredient(name: String) + + getValue(): String + + getId(): String +} + +@enduml \ No newline at end of file From 9a8f1ac6f10c193392146ca65664ab390a227853 Mon Sep 17 00:00:00 2001 From: cuvar Date: Mon, 3 Apr 2023 14:22:53 +0200 Subject: [PATCH 08/39] fix: spacing --- README.md | 8 ++++++++ docs/res/cov.png | Bin 0 -> 337606 bytes 2 files changed, 8 insertions(+) create mode 100644 docs/res/cov.png diff --git a/README.md b/README.md index 99c394d..bc99be0 100644 --- a/README.md +++ b/README.md @@ -259,6 +259,7 @@ Test-Klasse: `TestIngredient` assertNotEquals(code1, code2); } ``` + Diese Testklasse mit den dargestellten Methoden ist ein Positivbeispiel für "thorough testing". All diese Testmethoden testen verschiedene Zweige der selbstimplementieren `equals` Methode der Klasse `Ingredient`. Sie testen Vergeliche zwischen - einer `Ingredient`-Instanz mit sich selbst - zwei gleichen `Ingredient`-Instanzen @@ -305,6 +306,7 @@ Da die Tests für diese Methode vollständig fehlen, werden dementsprechend auch #### positives Beispiel Test-Methode: `TestWebsiteFetcher#testGetWebsiteBodyInvalidUrl` + ```java @Test public void testGetWebsiteBodyInvalidUrl() { @@ -373,6 +375,7 @@ Unabhängig dessen begründet sich die Code-Coverage wie folgt: In diesem Projekt wurden vor allem Mock-Objekte eingesetzt. Sie wurden genutzt, um benötigte Nebenklassen zu mocken. Nachfolgend sind zwei demonstrative Beispiel für den Einsatz von Mock-Objekten mit dazugehörigen UML Diagrammen zu sehen. Beispiel aus: `TestGroceryList#testConstructorVarArgs` + ```java @Test public void testConstructorVarArgs() { @@ -393,6 +396,7 @@ _[TODO: Analyse und Begründung für Einsatz] Beispiel aus: `TestMealPlan#setUp` + ```java @BeforeEach public void setUp() { @@ -432,6 +436,7 @@ _[4 Beispiele für die Ubiquitous Language; jeweils Bezeichung, Bedeutung und ku ### Entities zugehörige Klassen(n): `Recipe` + ![Entity Beispiel UML](http://www.plantuml.com/plantuml/proxy?cache=no&src=https://raw.githubusercontent.com/ncryptedV1/AutoChef/docs/uml/entity.iuml) Die `Recipe` Entity beschreibt ein semantisches Rezept. Wie das UML-Diagram zeigt, besteht ein Rezept aus einem Name, einer Liste von Zutaten (GroceryList) und einer Liste von Schritten zur Zubereitung (RecipeStep). Ein Rezept wird eineindeutig über eine ID indetifiziert. In diesem Fall besteht die ID aus dem Namen in Kleinschrift. Haben also zwei Rezepte den selben Namen, werden sie als gleich angesehen. Weiterhin hat die `Recipe` Klasse mehrere Getter-Methoden für die einzelnen Attribute und die ID als auch einen Konstruktor. @@ -445,6 +450,7 @@ Der Einsatz dieser Entity begründet sich dadurch, dass es notwendig war, ein Re ### Value Objects zugehörige Klassen(n): `Ingredient` + ![Entity Beispiel UML](http://www.plantuml.com/plantuml/proxy?cache=no&src=https://raw.githubusercontent.com/ncryptedV1/AutoChef/docs/uml/value-object.iuml) Das `Ingredient` Value Object beschreibt eine Zutat für ein Gericht. Es besitzt als einziges Attribute einen Namen, der es definiert. Neben dem Namen besitzt es zwei Getter-Methoden für den Namen (`getValue`) und für die ID (`getID`). Letztere, ist dabei jedoch nicht zur eindeutigen Identifizierung im Sinne einer Entity anzusehen, sondern wird lediglich zum einfacheren Vergleich zweier `Ingredient`-Instanzen verwendet. Die ID setzt sich aus dem Namen in Kleinschrift zusammen. Zusätzlich existiert ein Konstruktor zur Erzeugung einer Ingredient-Instanz, bei der die Richtigkeit des Namens überprüft wird. Ähnlich wie bei der `Recipe`-Klassen, muss ein Namen mindestens ein Zeichen enthalten, das keinem White-Space Zeichen entspricht. @@ -454,6 +460,7 @@ Ein `Ingredient` hat keinen Lebenszyklus und auch keine relevante Logik implemen ### Repositories zugehörige Klassen(n): `RecipeFileRepository` + ![Repository Beispiel UML](http://www.plantuml.com/plantuml/proxy?cache=no&src=https://raw.githubusercontent.com/ncryptedV1/AutoChef/docs/uml/repository.iuml) - `saveRecipe`: speichert ein gegebenes Rezept in einer Datei im Dateiort `recipesFolder` ab @@ -469,6 +476,7 @@ Um die Persistenzverwaltung gründlich und sauber von der Domänenlogik zu trenn ### Aggregates zugehörige Klassen(n): `Meal` + ![Aggregate Beispiel UML](http://www.plantuml.com/plantuml/proxy?cache=no&src=https://raw.githubusercontent.com/ncryptedV1/AutoChef/docs/uml/aggregate.iuml) In diesem Projekt wurde die `Meal`-Klasse als Aggregate ausgewählt.Die `Meal`-Klasse fasst logsiches Verhalten verschiedener Elemente zusammen. Es definiert sich durch ein Rezept `recipe` und einem Integer `adjustedNumberOfPeople`, das darstellt, auf wie viele Personen die Zutatenmenge des Rezeptes angepasst werden soll. Der Konstruktor enthält keine weitere Logik zur Überprüfung der Attribute. Des Weiteren exisiteren folge Methoden: diff --git a/docs/res/cov.png b/docs/res/cov.png new file mode 100644 index 0000000000000000000000000000000000000000..9f5e72ad645e487719a57e87b8fcd0816a3c0884 GIT binary patch literal 337606 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 z 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 z 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 literal 0 HcmV?d00001 From 1a8d5b756a6a02b03dcd5ef5c8d157023474a86e Mon Sep 17 00:00:00 2001 From: cuvar Date: Tue, 4 Apr 2023 10:36:01 +0200 Subject: [PATCH 09/39] docs: add DRY chapter --- README.md | 70 +++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 68 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index bc99be0..caa4802 100644 --- a/README.md +++ b/README.md @@ -146,8 +146,62 @@ _[jeweils eine bis jetzt noch nicht behandelte Klasse als positives und negative ![Kohäsion Beispiel UML](http://www.plantuml.com/plantuml/proxy?cache=no&src=https://raw.githubusercontent.com/ncryptedV1/AutoChef/docs/uml/cohesion.iuml) ### Don’t Repeat Yourself (DRY)\* -commit sha: d89dcb3 -_[ein Commit angeben, bei dem duplizierter Code/duplizierte Logik aufgelöst wurde; Code-Beispiele (vorher/nachher); begründen und Auswirkung beschreiben]_ +Commit-SHA: d89dcb3 ([Link](https://github.com/ncryptedV1/AutoChef/commit/d89dcb38a0e45759dd3e689593870d1e9ed0da96)) + +vorher: + +```java + public MealPlan(List meals, LocalDate start, LocalDate end) { + int days = start.until(end).getDays(); + if (meals.size() != days) { + throw new IllegalArgumentException( + "Mahlzeiten-Plan spannt " + days + " Tage, es wurden allerdings nur " + meals.size() + + " Mahlzeiten übergeben"); + } + + this.meals = meals; + this.start = start; + this.end = end; + } + + // ... + + public int getDays() { + return start.until(getEnd()).getDays(); + } +``` + +danach: + +```java + public MealPlan(List meals, LocalDate start, LocalDate end) { + this.start = start; + this.end = end; + + int days = getDays(); + if (meals.size() != days) { + throw new IllegalArgumentException( + "Mahlzeiten-Plan spannt " + days + " Tage, es wurden allerdings nur " + meals.size() + + " Mahlzeiten übergeben"); + } + + this.meals = meals; + } + + // ... + + public int getDays() { + return start.until(getEnd()).getDays(); + } +``` + +Die oben gezeigte Änderung ist ein kleines Beispiel zur Reduktion von Code-Duplikationen. Die Methode `getDays` ware bereits vor dem Commit vorhanden. Sie dient dazu, die Anzahl an Tagen zwischen `start` und `end` zu berechnen. Vor dem Commit, wurde jedoch dieselbe Logik im Konstruktor in der 2. Zeile verwendet: + +```java +int days = start.until(end).getDays(); +``` + +Der Commit sorgte dafür, dass dieser Code durch einen Aufruf der `getDays` Methode ersetzt wurde. Das hat zur Folge, dass die Logik der Berechnung der Anzahl der Tage nur an einem Punkt im Code genutzt wird: in der `getDays` Methode. Dadurch können Fehler vermieden werden, die durch unachtsame Änderungen an einer der beiden Code-Stellen aufgetreten wären. ## 5. Unit Tests @@ -390,6 +444,7 @@ Beispiel aus: `TestGroceryList#testConstructorVarArgs` assertNotNull(list); } ``` + ![Fakes und Mocks Beispiel 1 UML](http://www.plantuml.com/plantuml/proxy?cache=no&src=https://raw.githubusercontent.com/ncryptedV1/AutoChef/docs/uml/fake-mock-1.iuml) _[TODO: Analyse und Begründung für Einsatz] @@ -492,6 +547,17 @@ Damit fasst das `Meal`-Aggregate Logik eines Gerichtes zusammen. Es hält jedoch ### Code Smells _[jeweils 1 Code-Beispiel zu 2 Code Smells aus der Vorlesung; jeweils Code-Beispiel und einen möglichen Lösungsweg bzw. den genommen Lösungsweg beschreiben (inkl. (Pseudo-)Code)]_ +#### Code Smell 1 +https://github.com/ncryptedV1/AutoChef/commit/d89dcb38a0e45759dd3e689593870d1e9ed0da96 +Duplicate Code + + +Large Method / Extract Class +https://github.com/ncryptedV1/AutoChef/commit/8a66d9a6405c9ca4cf710490ae06eb810ea87978#diff-a914343e6af07030cd7b3b51d56fc5e0f541d6bd350ee0ef11324a9bc5aae66f + +Dead Code +https://github.com/ncryptedV1/AutoChef/commit/f2b13c5e535bf60d72c17d51970f2993bd3457cf + ### 2 Refactorings From 768974da286ab34f5f06d54af241ae7d2e1e2284 Mon Sep 17 00:00:00 2001 From: cuvar Date: Tue, 4 Apr 2023 13:26:44 +0200 Subject: [PATCH 10/39] docs: solid layer analyzing --- README.md | 33 ++++++++++++++++++++++++++++++--- uml/layer-1.iuml | 16 ++++++++++++++++ uml/layer-2.iuml | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 94 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index caa4802..cb44799 100644 --- a/README.md +++ b/README.md @@ -77,16 +77,43 @@ _[(1 Klasse, die die Dependency Rule einhält und eine Klasse, die die Dependenc ### Analyse der Schichten -_[jeweils 1 Klasse zu 2 unterschiedlichen Schichten der Clean-Architecture: jeweils UML der Klasse (ggf. auch zusammenspielenden Klassen), Beschreibung der Aufgabe, Einordnung mit Begründung in die Clean-Architecture]_ +[//]: # (#### Plugins) +[//]: # (- ConsoleInputReader, ConsoleOutputService) -#### Schicht: [Name] + +#### Schicht: Domain Code + +- gewählte Klasse(n): `Recipe` ![Schicht 1 UML](http://www.plantuml.com/plantuml/proxy?cache=no&src=https://raw.githubusercontent.com/ncryptedV1/AutoChef/docs/uml/layer-1.iuml) -#### Schicht: [Name] +Die `Recipe`-Klasse ist eine Entity im Sinne der Clean-Architecture, da sie die Entität eines Rezeptes abbildet. Ein Rezept besteht aus folgenden Attributen: +- `name: String`: Name des Rezeptes +- `groceryList: GroceryList` : Liste an Zutaten, die für das Rezept benötigt werden +- `recipeSteps: List`: Liste an Zubereitungsschritten, die im Laufe des Rezeptes abgearbeitet werden müssen + +Ein Rezept wird eineindeutig über eine ID indetifiziert. Die ID umfasst den Namen in Kleinschrift. Außerdem existieren für die Attribute und die ID jeweils Getter-Methoden und ein Konstruktor. + +Damit liegt die Aufgabe der `Recipe`-Entität darin, ein Rezept semantisch im Code zu repräsentieren. Da das Konzept eines Rezept essenziell für die Domäne von Essensplänen ist, wurde es als Teil des Kernes der Anwendung aufgenommen. `Recipe` ist deshalb Teil der Schicht "Domain Code", da der Domänencode ebenjene Entities bzw den Kern der Anwendung enthalten sollte. Außerdem ändert sich die Modellierung eines Rezeptes selten, was ebenso dafür spricht, es in die Schicht "Domain Code" einzuordnen. + +#### Schicht: Application Code + +- gewählte Klasse(n): `DialogService` mit `DialogState` + +[//]: # (mit `RecipeRepository`, `RecipeFileRepository` und `DialogState`) ![Schicht 2 UML](http://www.plantuml.com/plantuml/proxy?cache=no&src=https://raw.githubusercontent.com/ncryptedV1/AutoChef/docs/uml/layer-2.iuml) +Die Schicht des Applications Codes umfasst mehrere Klassen. Dabei soll die Klasse `DialogService` im Fokus stehen. + +[//]: # (Da dieser Service aber Abhängigkeiten zu anderen Klassen hat, die relevant in der Betrachtung und Einordnung sind, werden sie hier mit aufgeführt.) + +Im Kern ist der Dialog-Service für die Ablauflogik der Anwendung verantwortlich. Er verwaltet die Datenpersistenz über die Klassen `RecipeRepository` und `RecipeFileRepository`, ist aber gleichzeitig auch für die Nutzung von Benutzereingaben über die Klassen `ConsoleInputReader`, `ConsoleInputParser` und `ConsoleOutputService` verantwortlich. Damit ist er die Schnittstelle zwischen den einzelnen Verantwortungsbereichen der Anwendung. + +Im Allgemeinen startet er den Dialog mit dem Benutzer, organisiert die Generierung von Essensplänen und gibt dem Benutzer die Möglichkeit Rezepte hinzuzufügen. In diesem Sinne nimmt er die Rolle eines "Controllers" ein. Für andere Anwendungen wie etwa eine Web-Anwendung würde eine andere Funktionalität erwartet werden. Der Dialog-Service ist speziell für den Anwendungsfall einer CLI-Anwendung definiert und nutzbar. + +Ebenso bedeutet das, dass Änderungen an dem DialogService keinen Einfluss auf den Domänen-Code haben. All diese Aspekte begründen, warum der Dialog-Service im Application Code angesidelt ist. + ## 3. SOLID ### Analyse Single-Responsibility-Principle (SRP) diff --git a/uml/layer-1.iuml b/uml/layer-1.iuml index e69de29..8ac3b06 100644 --- a/uml/layer-1.iuml +++ b/uml/layer-1.iuml @@ -0,0 +1,16 @@ +@startuml + +left to right direction + +class Recipe { + - name: String + - groceryList: GroceryList + - recipeSteps: List + + Recipe(name: String) + + getName(): String + + getRecipeSteps(): List + + getGroceryList(): GroceryList + + getId(): String +} + +@enduml \ No newline at end of file diff --git a/uml/layer-2.iuml b/uml/layer-2.iuml index e69de29..b131db1 100644 --- a/uml/layer-2.iuml +++ b/uml/layer-2.iuml @@ -0,0 +1,48 @@ +@startuml + +left to right direction + +class DialogService { + - currentState: DialogState + - recipeRepository: RecipeRepository + + DialogService(recipeRepository: RecipeRepository) + + startDialog(): void + + startMain(): void + + showRecipes(): void + + addRecipe(): void + + startMealPlanGeneration(): void + + startPostMealPlanGeneration(MealPlan mealPlan, List allRecipes): void + + getCurrentState(): DialogState + + offerOptions(options: List): int +} + +enum DialogState { + - MAIN + - SHOW_RECIPES + - ADD_RECIPE + - MEAL_PLAN_GENERATION + - POST_MEAL_PLAN_GENERATION +} + +'interface RecipeRepository { +' + saveRecipe(recipe: Recipe): void +' + deleteRecipe(recipe: Recipe): boolean +' + getRecipes(): List +' + getRecipe(id: String): Recipe +'} +' +'class RecipeFileRepository { +' - recipesFolder: File +' + RecipeFileRepository(recipesFolder: File) +' + saveRecipe(recipe: Recipe): void +' + deleteRecipe(recipe: Recipe): boolean +' + getRecipes(): List +' + getRecipe(id: String): Recipe +'} + + +DialogState <- DialogService +'RecipeFileRepository <- DialogService +'RecipeRepository <|.. RecipeFileRepository + +@enduml \ No newline at end of file From 0d1933f8c8a282305573f3a10fbaa50ec01c4e59 Mon Sep 17 00:00:00 2001 From: cuvar Date: Tue, 4 Apr 2023 14:27:04 +0200 Subject: [PATCH 11/39] docs: single responsiblity principle --- README.md | 34 ++++++++++++++++++++++++---- uml/single-responsibility-neg-1.iuml | 19 ++++++++++++++++ uml/single-responsibility-neg-2.iuml | 25 ++++++++++++++++++++ uml/single-responsibility-neg.iuml | 0 uml/single-responsibility-pos.iuml | 12 ++++++++++ 5 files changed, 86 insertions(+), 4 deletions(-) create mode 100644 uml/single-responsibility-neg-1.iuml create mode 100644 uml/single-responsibility-neg-2.iuml delete mode 100644 uml/single-responsibility-neg.iuml diff --git a/README.md b/README.md index cb44799..fd07d76 100644 --- a/README.md +++ b/README.md @@ -118,19 +118,43 @@ Ebenso bedeutet das, dass Änderungen an dem DialogService keinen Einfluss auf d ### Analyse Single-Responsibility-Principle (SRP) -_[jeweils eine Klasse als positives und negatives Beispiel für SRP; jeweils UML der Klasse und Beschreibung der Aufgabe bzw. der Aufgaben und möglicher Lösungsweg des Negativ-Beispiels (inkl. UML)]_ +_[Beschreibung der Aufgabe bzw. der Aufgaben und möglicher Lösungsweg des Negativ-Beispiels (inkl. UML)]_ #### Positiv-Beispiel +gewählte Klasse: `Quantity` + ![Single Responsibility positives Beispiel UML](http://www.plantuml.com/plantuml/proxy?cache=no&src=https://raw.githubusercontent.com/ncryptedV1/AutoChef/docs/uml/single-responsibility-pos.iuml) +Die `Quantity`-Klasse bildet eine "Menge" oder "Anzahl" ab. Es ist relevant für die Menge von Zutaten in einem Rezept. Die Klasse ist maßgeblich definiert durch ihr einziges Attribute `value`, das eine Menge als `double` repräsentiert. Zusätzlich dazu kann über die Methode `multiply` eine `Quantity`-Instanz mit einem Wert multipliziert werden, was wieder eine neue `Quantity`-Instanz zurückgibt. `Quantity` ist vor allem relevant im Kontext von `GroceryItem`, da dort beschrieben wird, wie viel von einem `Ingredient` benötigt wird. + +Die Verantwortung der Klasse liegt demnach darin, semantisch eine "Menge" abzubilden. Daneben besitzt sie keine weiteren Verantwortlichkeiten. + #### Negativ-Beispiel -![Single Responsibility negatives Beispiel UML](http://www.plantuml.com/plantuml/proxy?cache=no&src=https://raw.githubusercontent.com/ncryptedV1/AutoChef/docs/uml/single-responsibility-neg.iuml) +gewählte Klasse: `DialogService` + +![Single Responsibility negatives Beispiel UML](http://www.plantuml.com/plantuml/proxy?cache=no&src=https://raw.githubusercontent.com/ncryptedV1/AutoChef/docs/uml/single-responsibility-neg-1.iuml) + +Ein Negativbeispiel für das SRP ist der Dialog-Service - im Speziellen die `startMealPlanGeneration`-Methode. Die Klasse nimmt die Rolle eines "Controllers" ein, da sie die Persistenz von Daten und die Ausgabe an das Command-Line-Interface verwaltet. Als Teil der Application-Code-Schicht enthält der Dialog-Service jedwede Logik für den Ablauf der Anwendung: +- es startet den Dialog mit dem Benutzer +- organisiert die Generierung von Essensplänen und +- gibt dem Benutzer die Möglichkeit Rezepte hinzuzufügen. +Der Dialog-Service ist speziell für den Anwendungsfall einer CLI-Anwendung definiert. + +Der Dialog-Service hat jedoch mehrere Verantwortlichkeiten und bricht damit das SRP: +- Steuerung der Ablauflogik (dafür werden die Verwaltung von Benutzerein- und -ausgabe als auch der Datenpersistenz genutzt) +- Generierung eines Essensplan über die Methode `startMealPlanGeneration` + +Gerade die zweite Verantwortlichkeit - die Generierung eines Essenplans - kann in einen separaten Service ausgelagert werden: + +![Single Responsibility negatives Beispiel UML](http://www.plantuml.com/plantuml/proxy?cache=no&src=https://raw.githubusercontent.com/ncryptedV1/AutoChef/docs/uml/single-responsibility-negc.iuml) + +Dabei könnte `startMealPlanGeneration` mit einem `GenerationService` interagieren. Der `GenerationService` wäre dann für die eigentliche Generierung des Essensplans verantwortlich, während die `startMealPlanGeneration` lediglich eine verwaltende Rolle einnehmen würde. ### Analyse Open-Closed-Principle (OCP) -_[jeweils eine Klasse als positives und negatives Beispiel für OCP; jeweils UML der Klasse und Analyse mit Begründung, warum das OCP erfüllt/nicht erfüllt wurde – falls erfüllt: warum hier sinnvoll/welches Problem gab es? Falls nicht erfüllt: wie könnte man es lösen (inkl. UML)?]_ +_[Analyse mit Begründung, warum das OCP erfüllt/nicht erfüllt wurde – falls erfüllt: warum hier sinnvoll/welches Problem gab es? Falls nicht erfüllt: wie könnte man es lösen (inkl. UML)?]_ #### Positiv-Beispiel @@ -138,7 +162,9 @@ _[jeweils eine Klasse als positives und negatives Beispiel für OCP; jeweils UML #### Negativ-Beispiel -![Open-Closed negatives Beispiel UML](http://www.plantuml.com/plantuml/proxy?cache=no&src=https://raw.githubusercontent.com/ncryptedV1/AutoChef/docs/uml/open-closed-neg.iuml) +![Open-Closed negatives Beispiel UML](http://www.plantuml.com/plantuml/proxy?cache=no&src=https://raw.githubusercontent.com/ncryptedV1/AutoChef/docs/uml/open-closed-neg-1.iuml) + +![Open-Closed negatives Beispiel UML](http://www.plantuml.com/plantuml/proxy?cache=no&src=https://raw.githubusercontent.com/ncryptedV1/AutoChef/docs/uml/open-closed-neg-2.iuml) ### Analyse Liskov-Substitution- (LSP), Interface-Segreggation- (ISP), Dependency-Inversion-Principle (DIP) diff --git a/uml/single-responsibility-neg-1.iuml b/uml/single-responsibility-neg-1.iuml new file mode 100644 index 0000000..3cfa814 --- /dev/null +++ b/uml/single-responsibility-neg-1.iuml @@ -0,0 +1,19 @@ +@startuml + +left to right direction + +class DialogService { + - currentState: DialogState + - recipeRepository: RecipeRepository + + DialogService(recipeRepository: RecipeRepository) + + startDialog(): void + + startMain(): void + + showRecipes(): void + + addRecipe(): void + + startMealPlanGeneration(): void + + startPostMealPlanGeneration(MealPlan mealPlan, List allRecipes): void + + getCurrentState(): DialogState + + offerOptions(options: List): int +} + +@enduml \ No newline at end of file diff --git a/uml/single-responsibility-neg-2.iuml b/uml/single-responsibility-neg-2.iuml new file mode 100644 index 0000000..ee46a57 --- /dev/null +++ b/uml/single-responsibility-neg-2.iuml @@ -0,0 +1,25 @@ +@startuml + +left to right direction + +class DialogService { + - currentState: DialogState + - recipeRepository: RecipeRepository + + DialogService(recipeRepository: RecipeRepository) + + startDialog(): void + + startMain(): void + + showRecipes(): void + + addRecipe(): void + + startMealPlanGeneration(): void + + startPostMealPlanGeneration(MealPlan mealPlan, List allRecipes): void + + getCurrentState(): DialogState + + offerOptions(options: List): int +} + +class GenerationService { + + generateMealPlan(): void +} + +DialogService -> GenerationService + +@enduml \ No newline at end of file diff --git a/uml/single-responsibility-neg.iuml b/uml/single-responsibility-neg.iuml deleted file mode 100644 index e69de29..0000000 diff --git a/uml/single-responsibility-pos.iuml b/uml/single-responsibility-pos.iuml index e69de29..a3e2c44 100644 --- a/uml/single-responsibility-pos.iuml +++ b/uml/single-responsibility-pos.iuml @@ -0,0 +1,12 @@ +@startuml + +left to right direction + +class Quantity { + - value: double + + Quantity(value: double) + + getValue(): double + + multiple(factor: double): Quantity +} + +@enduml \ No newline at end of file From 9959ff096111f5ef0bf0a2f0fd71d630d724d329 Mon Sep 17 00:00:00 2001 From: cuvar Date: Tue, 4 Apr 2023 16:16:42 +0200 Subject: [PATCH 12/39] docs: open-closed principle --- README.md | 21 +++++++++++++++++++-- uml/open-closed-neg-1.iuml | 13 +++++++++++++ uml/open-closed-neg-2.iuml | 29 +++++++++++++++++++++++++++++ uml/open-closed-neg.iuml | 0 uml/open-closed-pos.iuml | 23 +++++++++++++++++++++++ 5 files changed, 84 insertions(+), 2 deletions(-) create mode 100644 uml/open-closed-neg-1.iuml create mode 100644 uml/open-closed-neg-2.iuml delete mode 100644 uml/open-closed-neg.iuml diff --git a/README.md b/README.md index fd07d76..acd5845 100644 --- a/README.md +++ b/README.md @@ -154,18 +154,35 @@ Dabei könnte `startMealPlanGeneration` mit einem `GenerationService` interagier ### Analyse Open-Closed-Principle (OCP) -_[Analyse mit Begründung, warum das OCP erfüllt/nicht erfüllt wurde – falls erfüllt: warum hier sinnvoll/welches Problem gab es? Falls nicht erfüllt: wie könnte man es lösen (inkl. UML)?]_ #### Positiv-Beispiel +gewählte Klasse(n): `RecipeFileRepository` mit Interface `RecipeRepository` + ![Open-Closed positives Beispiel UML](http://www.plantuml.com/plantuml/proxy?cache=no&src=https://raw.githubusercontent.com/ncryptedV1/AutoChef/docs/uml/open-closed-pos.iuml) +Die `RecipeFileRepository`-Klasse ist ein Repository, dass das Interface `RecipeRepository` implementiert. Das Interface beschreibt, welche Schnittstelle zum Speichern von Daten benötigt wird. Die `RecipeFileRepository`-Klasse implementiert diese Methoden für den Fall von Datenpersistenz in Dateien. + +Da hier ein Interface verwendet wird, ist es leicht möglich neue Funktionalität hinzuzufügen. Dazu muss lediglich eine neuer Methodekopf im Interface definiert werden. Die dazugehörige Methode muss in allen Klassen, die das Interface implementieren, hinzugefügt werden. Damit ist die "Open" Eigenschaft des OCP erfüllt. Auf der anderen Seite sollte Code "closed" gegenüber Modifikationen sein. Das ist ebenso durch die Verwendung eines Interfaces erfüllt. Das Interface bestimmt die Funktionalitäten der Klasse. + +Hier mit dem OCP zu arbeiten ist vor allem aus Sicht der Flexibilität und Erweiterbarkeit sinnhaft. Sollte sich später dazu entschieden werden, eine andere Methode der Datenpersistenz zu wählen, muss lediglich die Implementierung des Interfaces angepasst bzw weitere Klassen, die das Interface implementieren hinzugefügt werden. So kann die Anwendung an verschiedene Umgebungen und Anforderungen leichter angepasst werden, ohne die inneren Schichten der Clean-Architecture verändern zu müssen. Gerade in Hinsicht dessen, dass dieses Projekt eventuell freizeitlich weiterverfolgt wird, ergibt dieser Ansatz Sinn. + #### Negativ-Beispiel +gewählte Klasse(n): `ChefkochRecipeFetcher` + +vorher: ![Open-Closed negatives Beispiel UML](http://www.plantuml.com/plantuml/proxy?cache=no&src=https://raw.githubusercontent.com/ncryptedV1/AutoChef/docs/uml/open-closed-neg-1.iuml) +nachher: ![Open-Closed negatives Beispiel UML](http://www.plantuml.com/plantuml/proxy?cache=no&src=https://raw.githubusercontent.com/ncryptedV1/AutoChef/docs/uml/open-closed-neg-2.iuml) +Die `ChefkochRecipeFetcher`-Klasse ist ein Beispiel davon, wie das OCP verletzt werden kann. Diese Klasse dient dazu, Daten aus einer Chefkoch-Website zu extrahieren. Dazu werden verschiedene RegEx-Ausdrücke genutzt. + +Das OCP ist hier nicht erfüllt, da die Klasse nicht offen für Erweiterungen ist, ohne gleichzeitig geschlossen für Modifikationen zu sein. Sollte die Anforderung aufkommen, weitere Anbieter wie KitchenStories.com zu integrieren oder gar die Chefkoch-Integration zu ersetzen, müssten einige Code-Modifikationen durchgeführt werden. Dadurch ist das OCP verletzt. + +Gelöst werden kann das durch ein Interface wie im zweiten UML ersichtlich. Ein abstrakter `RecipeFetcher` kann als Interface für die Integration genutzt werden. Welche spezielle Webseiten-Integration im Hintergrund benutzt wird, ist dabei irrelevant für die inneren Schichten der Clean-Architecture. So wäre die Implementierung offen für weitere Erweiterungen unter Verwendung des Interfaces. Gleichzeitig müssten keine aufwändigen Code-Modifikationen an den inneren Schichten oder dem Interface vorgenommen werden, damit beide Module miteinander interagieren können. Dadurch wäre das OCP erfüllt. + ### Analyse Liskov-Substitution- (LSP), Interface-Segreggation- (ISP), Dependency-Inversion-Principle (DIP) _[jeweils eine Klasse als positives und negatives Beispiel für entweder LSP oder ISP oder DIP); jeweils UML der Klasse und Begründung, warum man hier das Prinzip erfüllt/nicht erfüllt wird]_ \ @@ -224,7 +241,7 @@ vorher: } ``` -danach: +nachher: ```java public MealPlan(List meals, LocalDate start, LocalDate end) { diff --git a/uml/open-closed-neg-1.iuml b/uml/open-closed-neg-1.iuml new file mode 100644 index 0000000..ac6e778 --- /dev/null +++ b/uml/open-closed-neg-1.iuml @@ -0,0 +1,13 @@ +@startuml + +left to right direction + +class ChefkochRecipeFetcher { + + getRecipe(url: String): Recipe + + extractRecipeName(content: String): String + + extractIngredients(content: String): GroceryList + + extractRecipeSteps(content: String): List +} + + +@enduml \ No newline at end of file diff --git a/uml/open-closed-neg-2.iuml b/uml/open-closed-neg-2.iuml new file mode 100644 index 0000000..613f50b --- /dev/null +++ b/uml/open-closed-neg-2.iuml @@ -0,0 +1,29 @@ +@startuml + +left to right direction + +interface RecipeFetcher { + + getRecipe(url: String): Recipe + + extractRecipeName(content: String): String + + extractIngredients(content: String): GroceryList + + extractRecipeSteps(content: String): List +} + +class ChefkochRecipeFetcher { + + getRecipe(url: String): Recipe + + extractRecipeName(content: String): String + + extractIngredients(content: String): GroceryList + + extractRecipeSteps(content: String): List +} + +class KitchenStoriesRecipeFetcher { + + getRecipe(url: String): Recipe + + extractRecipeName(content: String): String + + extractIngredients(content: String): GroceryList + + extractRecipeSteps(content: String): List +} + +RecipeFetcher <|.. ChefkochRecipeFetcher +RecipeFetcher <|.. KitchenStoriesRecipeFetcher + +@enduml \ No newline at end of file diff --git a/uml/open-closed-neg.iuml b/uml/open-closed-neg.iuml deleted file mode 100644 index e69de29..0000000 diff --git a/uml/open-closed-pos.iuml b/uml/open-closed-pos.iuml index e69de29..80149cf 100644 --- a/uml/open-closed-pos.iuml +++ b/uml/open-closed-pos.iuml @@ -0,0 +1,23 @@ +@startuml + +left to right direction + +interface RecipeRepository { + + saveRecipe(recipe: Recipe): void + + deleteRecipe(recipe: Recipe): boolean + + getRecipes(): List + + getRecipe(id: String): Recipe +} + +class RecipeFileRepository { + - recipesFolder: File + + RecipeFileRepository(recipesFolder: File) + + saveRecipe(recipe: Recipe): void + + deleteRecipe(recipe: Recipe): boolean + + getRecipes(): List + + getRecipe(id: String): Recipe +} + +RecipeRepository <|.. RecipeFileRepository + +@enduml \ No newline at end of file From 8ab40d2c4b4da7e7dc37356cb96676f5263019a6 Mon Sep 17 00:00:00 2001 From: cuvar Date: Tue, 4 Apr 2023 16:18:32 +0200 Subject: [PATCH 13/39] docs: remove unnecessary comments --- README.md | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/README.md b/README.md index acd5845..0b131fc 100644 --- a/README.md +++ b/README.md @@ -77,10 +77,6 @@ _[(1 Klasse, die die Dependency Rule einhält und eine Klasse, die die Dependenc ### Analyse der Schichten -[//]: # (#### Plugins) -[//]: # (- ConsoleInputReader, ConsoleOutputService) - - #### Schicht: Domain Code - gewählte Klasse(n): `Recipe` @@ -100,14 +96,10 @@ Damit liegt die Aufgabe der `Recipe`-Entität darin, ein Rezept semantisch im Co - gewählte Klasse(n): `DialogService` mit `DialogState` -[//]: # (mit `RecipeRepository`, `RecipeFileRepository` und `DialogState`) - ![Schicht 2 UML](http://www.plantuml.com/plantuml/proxy?cache=no&src=https://raw.githubusercontent.com/ncryptedV1/AutoChef/docs/uml/layer-2.iuml) Die Schicht des Applications Codes umfasst mehrere Klassen. Dabei soll die Klasse `DialogService` im Fokus stehen. -[//]: # (Da dieser Service aber Abhängigkeiten zu anderen Klassen hat, die relevant in der Betrachtung und Einordnung sind, werden sie hier mit aufgeführt.) - Im Kern ist der Dialog-Service für die Ablauflogik der Anwendung verantwortlich. Er verwaltet die Datenpersistenz über die Klassen `RecipeRepository` und `RecipeFileRepository`, ist aber gleichzeitig auch für die Nutzung von Benutzereingaben über die Klassen `ConsoleInputReader`, `ConsoleInputParser` und `ConsoleOutputService` verantwortlich. Damit ist er die Schnittstelle zwischen den einzelnen Verantwortungsbereichen der Anwendung. Im Allgemeinen startet er den Dialog mit dem Benutzer, organisiert die Generierung von Essensplänen und gibt dem Benutzer die Möglichkeit Rezepte hinzuzufügen. In diesem Sinne nimmt er die Rolle eines "Controllers" ein. Für andere Anwendungen wie etwa eine Web-Anwendung würde eine andere Funktionalität erwartet werden. Der Dialog-Service ist speziell für den Anwendungsfall einer CLI-Anwendung definiert und nutzbar. @@ -118,8 +110,6 @@ Ebenso bedeutet das, dass Änderungen an dem DialogService keinen Einfluss auf d ### Analyse Single-Responsibility-Principle (SRP) -_[Beschreibung der Aufgabe bzw. der Aufgaben und möglicher Lösungsweg des Negativ-Beispiels (inkl. UML)]_ - #### Positiv-Beispiel gewählte Klasse: `Quantity` @@ -277,7 +267,6 @@ Der Commit sorgte dafür, dass dieser Code durch einen Aufruf der `getDays` Meth ### 10 Unit Tests -_[Nennung von 10 Unit-Tests und Beschreibung, was getestet wird]_ | Unit Test | Beschreibung | |---------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------| | _TestUnit#testHashCodeTrue_ | testet, ob die `hashCode` Funktion der `Unit` Klasse, für zwei Objekte, den selben Hashcode korrekt zurückgibt zurückgibt | @@ -296,8 +285,6 @@ Die Tests wurden mittels Testautomatisierung realisiert. Dabei wurde JUnit 5 ver ### ATRIP: Thorough -_[jeweils 1 positives und negatives Beispiel zu ‘Thorough’; jeweils Code-Beispiel, Analyse und Begründung, was professionell/nicht professionell ist]_ - #### positives Beispiel Test-Klasse: `TestIngredient` @@ -550,7 +537,6 @@ _[Analyse und Begründung des Einsatzes von 2 Fake/Mock-Objekten; zusätzlich je ### Ubiquitous Language -_[4 Beispiele für die Ubiquitous Language; jeweils Bezeichung, Bedeutung und kurze Begründung, warum es zur Ubiquitous Language gehört]_ | Bezeichnung | Bedeutung | Begründung | |--------------|-------------------------------|-------------------------------------------------------------------------------------------------------------------------------------| | Ingredient | Zutat | Jedes Gericht besteht aus verschiedenen Lebensmitteln sogenannten Zutaten. | From 059109c7ac8734ff720ebbeb41e7b056f1877576 Mon Sep 17 00:00:00 2001 From: cuvar Date: Tue, 4 Apr 2023 16:24:00 +0200 Subject: [PATCH 14/39] docs: fix minor typos --- README.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 0b131fc..ea421f8 100644 --- a/README.md +++ b/README.md @@ -162,9 +162,11 @@ Hier mit dem OCP zu arbeiten ist vor allem aus Sicht der Flexibilität und Erwei gewählte Klasse(n): `ChefkochRecipeFetcher` vorher: + ![Open-Closed negatives Beispiel UML](http://www.plantuml.com/plantuml/proxy?cache=no&src=https://raw.githubusercontent.com/ncryptedV1/AutoChef/docs/uml/open-closed-neg-1.iuml) nachher: + ![Open-Closed negatives Beispiel UML](http://www.plantuml.com/plantuml/proxy?cache=no&src=https://raw.githubusercontent.com/ncryptedV1/AutoChef/docs/uml/open-closed-neg-2.iuml) Die `ChefkochRecipeFetcher`-Klasse ist ein Beispiel davon, wie das OCP verletzt werden kann. Diese Klasse dient dazu, Daten aus einer Chefkoch-Website zu extrahieren. Dazu werden verschiedene RegEx-Ausdrücke genutzt. @@ -546,7 +548,7 @@ _[Analyse und Begründung des Einsatzes von 2 Fake/Mock-Objekten; zusätzlich je ### Entities -zugehörige Klassen(n): `Recipe` +zugehörige Klasse(n): `Recipe` ![Entity Beispiel UML](http://www.plantuml.com/plantuml/proxy?cache=no&src=https://raw.githubusercontent.com/ncryptedV1/AutoChef/docs/uml/entity.iuml) @@ -560,7 +562,7 @@ Der Einsatz dieser Entity begründet sich dadurch, dass es notwendig war, ein Re ### Value Objects -zugehörige Klassen(n): `Ingredient` +zugehörige Klasse(n): `Ingredient` ![Entity Beispiel UML](http://www.plantuml.com/plantuml/proxy?cache=no&src=https://raw.githubusercontent.com/ncryptedV1/AutoChef/docs/uml/value-object.iuml) @@ -570,7 +572,7 @@ Ein `Ingredient` hat keinen Lebenszyklus und auch keine relevante Logik implemen ### Repositories -zugehörige Klassen(n): `RecipeFileRepository` +zugehörige Klasse(n): `RecipeFileRepository` ![Repository Beispiel UML](http://www.plantuml.com/plantuml/proxy?cache=no&src=https://raw.githubusercontent.com/ncryptedV1/AutoChef/docs/uml/repository.iuml) @@ -586,7 +588,7 @@ Um die Persistenzverwaltung gründlich und sauber von der Domänenlogik zu trenn ### Aggregates -zugehörige Klassen(n): `Meal` +zugehörige Klasse(n): `Meal` ![Aggregate Beispiel UML](http://www.plantuml.com/plantuml/proxy?cache=no&src=https://raw.githubusercontent.com/ncryptedV1/AutoChef/docs/uml/aggregate.iuml) From eb7e69c5d216d5701d4dd3f0e1cabdba839b6444 Mon Sep 17 00:00:00 2001 From: cuvar Date: Tue, 4 Apr 2023 18:36:46 +0200 Subject: [PATCH 15/39] docs: code smells and refactorings --- README.md | 198 ++++++++++++++++++++++++++++++++++-- uml/refactoring-1-post.iuml | 21 ++++ uml/refactoring-1-pre.iuml | 10 ++ uml/refactoring-2-post.iuml | 14 +++ uml/refactoring-2-pre.iuml | 14 +++ 5 files changed, 249 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index ea421f8..63714f6 100644 --- a/README.md +++ b/README.md @@ -604,33 +604,215 @@ Damit fasst das `Meal`-Aggregate Logik eines Gerichtes zusammen. Es hält jedoch ### Code Smells -_[jeweils 1 Code-Beispiel zu 2 Code Smells aus der Vorlesung; jeweils Code-Beispiel und einen möglichen Lösungsweg bzw. den genommen Lösungsweg beschreiben (inkl. (Pseudo-)Code)]_ #### Code Smell 1 -https://github.com/ncryptedV1/AutoChef/commit/d89dcb38a0e45759dd3e689593870d1e9ed0da96 -Duplicate Code +Code Smell: Duplicate Code +([Commit](https://github.com/ncryptedV1/AutoChef/commit/d89dcb38a0e45759dd3e689593870d1e9ed0da96)) +vorher: + +```java + public MealPlan(List meals, LocalDate start, LocalDate end) { + int days = start.until(end).getDays(); + if (meals.size() != days) { + throw new IllegalArgumentException( + "Mahlzeiten-Plan spannt " + days + " Tage, es wurden allerdings nur " + meals.size() + + " Mahlzeiten übergeben"); + } + + this.meals = meals; + this.start = start; + this.end = end; + } + + // ... + + public int getDays() { + return start.until(getEnd()).getDays(); + } +``` -Large Method / Extract Class -https://github.com/ncryptedV1/AutoChef/commit/8a66d9a6405c9ca4cf710490ae06eb810ea87978#diff-a914343e6af07030cd7b3b51d56fc5e0f541d6bd350ee0ef11324a9bc5aae66f +nachher: + +```java + public MealPlan(List meals, LocalDate start, LocalDate end) { + this.start = start; + this.end = end; + + int days = getDays(); + if (meals.size() != days) { + throw new IllegalArgumentException( + "Mahlzeiten-Plan spannt " + days + " Tage, es wurden allerdings nur " + meals.size() + + " Mahlzeiten übergeben"); + } + + this.meals = meals; + } -Dead Code -https://github.com/ncryptedV1/AutoChef/commit/f2b13c5e535bf60d72c17d51970f2993bd3457cf + // ... + + public int getDays() { + return start.until(getEnd()).getDays(); + } +``` +Dieser Code Smell umfasst eine Code Duplication in der `MealPlan`-Klasse. Relevant ist hierbei vor allem Zeile 2. Hier wird die folgende Berechnung ausgeführt: + +``` +int days = start.until(end).getDays(); +``` + +`days` gibt dabei die Anzahl an Tagen zwischen `start` udn `end` an. Was dabei aber auffällt, ist, dass diese Logik bereits zu anderen Zwecken in der `getDays`-Methode vorhanden ist. Diese Methode wird verwendet, um auch von außen Zugriff auf die Anzahl der Tage zu haben. Damit kann hier eine kleine Dopplung vorgefunden werden. Diese wurde nun, wie im zweiten Code-Beispiel ersichtlich, behoben. Dabei wurde die Zuweisung der Variable `this.start` und `this.end` nach oebn verschoben, sodass dann die Methode `getDays` aufgerufen werden kann. Dieser Methodenaufruf ermöglicht es jetzt, die Logik der Berechnung der Anzahl der Tage zu kapseln und somit die Wartbarkeit zu erhöhen. + +#### Code Smell 2 +Code Smell 2: Large Method +([Commit](https://github.com/ncryptedV1/AutoChef/commit/990e2b31be1fb10314691630584319d48b3d0cd8#diff-fdae48ae8bf101ffc36c43d7bbad7c1da1bef6ba56429655006dac33210fe387)) + +vorher: + +```java + public static void main(String[] args) { + logger.info("Starting..."); + + // setup groceries + GroceryItem item1 = new GroceryItem(new Ingredient("Banane"), new Quantity(1), Unit.GRAM); + GroceryItem item2 = + new GroceryItem(new Ingredient("Pineapple"), new Quantity(0.2), Unit.KILOGRAM); + GroceryItem item3 = + new GroceryItem(new Ingredient("Orange juice"), new Quantity(0.1), Unit.LITER); + GroceryItem item4 = new GroceryItem(new Ingredient("Apple"), new Quantity(1), Unit.PIECE); + GroceryItem item5 = + new GroceryItem(new Ingredient("Nutella"), new Quantity(2), Unit.TABLESPOON); + + // setup recipe steps + RecipeStep recipeStep1 = + new RecipeStep(1, "Cut some banana, apple and pineapple as the basis for this salad.", + item1, item2, item3); + RecipeStep recipeStep2 = new RecipeStep(2, "Add orange juice to make it more juicy.", item4); + RecipeStep recipeStep3 = + new RecipeStep(3, "Add a bit of Nutella for making it look beautiful.", item5); + + // setup recipe for + Recipe recipe1 = new Recipe("Sugar-free fruit salad", recipeStep1, recipeStep2, recipeStep3); + + // setup meal + Meal meal1 = new Meal(recipe1, 2); + // setup meal plan + List mealList = Arrays.asList(meal1); + LocalDate startDate = LocalDate.of(2023, 2, 20); + LocalDate endDate = LocalDate.of(2023, 2, 26); + MealPlan mealPlan = new MealPlan(mealList, startDate, endDate); + + logger.info(mealPlan.toString()); + } +``` + +nachher: + +```java + public static void main(String[] args) { + logger.info("Starting..."); + + // generate mock data + List groceryItems = MockService.generateGroceryItems(); + List recipeSteps = MockService.generateRecipeSteps(groceryItems); + Recipe recipe = MockService.generateRecipe(recipeSteps); + Meal meal = MockService.generateMeal(recipe); + List meals = Arrays.asList(meal); + MealPlan mealPlan = MockService.generateMealPlan(meals); + + logger.info(mealPlan.toString()); + } +``` + +```java +public class MockService { + + private static final Random random = new Random(); + + public static List generateGroceryItems() { + GroceryItem item1 = new GroceryItem(new Ingredient("Banane"), new Quantity(1), Unit.GRAM); + GroceryItem item2 = new GroceryItem(new Ingredient("Pineapple"), new Quantity(0.2), + Unit.KILOGRAM); + GroceryItem item3 = new GroceryItem(new Ingredient("Orange juice"), new Quantity(0.1), + Unit.LITER); + GroceryItem item4 = new GroceryItem(new Ingredient("Apple"), new Quantity(1), Unit.PIECE); + GroceryItem item5 = new GroceryItem(new Ingredient("Nutella"), new Quantity(2), + Unit.TABLESPOON); + return Arrays.asList(item1, item2, item3, item4, item5); + } + + public static List generateRecipeSteps(List groceryItems) { + RecipeStep recipeStep1 = new RecipeStep(1, + "Cut some banana, apple and pineapple as the basis for this salad.", + getSample(groceryItems)); + RecipeStep recipeStep2 = new RecipeStep(2, "Add orange juice to make it more juicy.", + getSample(groceryItems)); + RecipeStep recipeStep3 = new RecipeStep(3, "Add a bit of Nutella for making it look beautiful.", + getSample(groceryItems)); + return Arrays.asList(recipeStep1, recipeStep2, recipeStep3); + } + + public static Recipe generateRecipe(List recipeSteps) { + return new Recipe("Sugar-free fruit salad", recipeSteps); + } + + public static Meal generateMeal(Recipe recipe) { + return new Meal(recipe, 2); + } + + public static MealPlan generateMealPlan(List meals) { + LocalDate startDate = LocalDate.of(2023, 2, 20); + LocalDate endDate = LocalDate.of(2023, 2, 26); + return new MealPlan(meals, startDate, endDate); + } + + private static List getSample(List list) { + int sampleSize = random.nextInt(1, list.size()); + List result = new ArrayList<>(list); + result = result.subList(0, sampleSize); + return result; + } +} +``` + +Der zweite Code Smell befasst sich mit der Auslagerung der `MockService`-Klasse, die im aktuellen Stand nicht mehr vorhanden ist. In diesem Fall ist der Code Smell eine _Large Method_: die `main`-Methode. Sie umfasst die Erstellung einiger Objekte, damit die Anwendung mit Testdaten getestet werden konnte. Da es sich hier um vergleichsweise viele Objekte handelt, ist die Methode groß. + +Um das zu beheben, wurde die vorhandene Logik extrahiert in eine `MockService`-Klasse. Diese Klasse ist nun dafür verantwortlich, Testobjekte und -daten zu generieren und über Methodenaufrufe zurückzugeben. Dabei sind mehrere Methoden im Mock-Service entstanden, was die Komplexität des Code-Smells zeigt. ### 2 Refactorings -_[2 unterschiedliche Refactorings aus der Vorlesung anwenden, begründen, sowie UML vorher/nachher liefern; jeweils auf die Commits verweisen]_ +[//]: # (Refactoring: Extract Method) + +[//]: # ([Commit](https://github.com/ncryptedV1/AutoChef/commit/8a66d9a6405c9ca4cf710490ae06eb810ea87978#diff-a914343e6af07030cd7b3b51d56fc5e0f541d6bd350ee0ef11324a9bc5aae66f)) #### Refactoring 1 +Refactoring 2: Extract Class (ConsoleOutputService) +([Commit](https://github.com/ncryptedV1/AutoChef/commit/d2251bcb4cc4053a1de24e20213a46034d3e0dbf)) + +vorher: ![Refactoring Beispiel 1 Pre UML](http://www.plantuml.com/plantuml/proxy?cache=no&src=https://raw.githubusercontent.com/ncryptedV1/AutoChef/docs/uml/refactoring-1-pre.iuml) + +nachher: + ![Refactoring Beispiel 1 Post UML](http://www.plantuml.com/plantuml/proxy?cache=no&src=https://raw.githubusercontent.com/ncryptedV1/AutoChef/docs/uml/refactoring-1-post.iuml) +Das in den UML-Diagrammen gezeigte Refactoring ist das _Extract Class_-Refactoring, das genutzt wurde um die `ConsoleOutputService`-Klasse zu erschaffen. Der Commit und das erste UML-Diagramm zeigen, dass die `AutoChef`-Klasse mit der `main`-Methode zunächst auch für die Konsolenausgabe verantwortlich war. Um das aber sauber voneinander zu trennen, und die Komplexität der `main`-Methode so einfach wie möglich zu halten, wurde die Logik der Consolenausgabe in eine separate Klasse ausgelagert. Die neu geschaffene Klasse `ConsoleOutputService` umfasst neben der Logik, die vorher in der `main`-Methode existierte, auch noch weitere Funktionalitäten. Das zeigt auch das zweite UML-Diagramm. Zu sehen ist hier die `ConsoleOutputService`-Klasse mit ihren verschiedenen Methoden zur Konsolenausgabe. Mit diesem Refactoring konnte eine saubere Trennung der Verantwortlichkeiten, sowie eine kleinere `main`-Klasse erreicht werden. + #### Refactoring 2 +Refactoring 2: Rename Method +([Commit](https://github.com/ncryptedV1/AutoChef/commit/19fb6fc9c8bdee1a0e1d31f31c431a38eaf68ae2#diff-a621a84bbbaeb91c8fc33118865eb8c87e0bf202d7dfe7b5bf8d20a786a51239)) + +vorher: ![Refactoring Beispiel 2 Pre UML](http://www.plantuml.com/plantuml/proxy?cache=no&src=https://raw.githubusercontent.com/ncryptedV1/AutoChef/docs/uml/refactoring-2-pre.iuml) + +nachher: + ![Refactoring Beispiel 2 Post UML](http://www.plantuml.com/plantuml/proxy?cache=no&src=https://raw.githubusercontent.com/ncryptedV1/AutoChef/docs/uml/refactoring-2-post.iuml) +Dieses Refactoring umfasst das Umbenennen einer Methode (_Rename Method_) um mehr Klarheit im Quellcode zu schaffen. Es handelt sich dabei um die Methode `getIngredients` respektive `getGroceryList` (nach dem Refactoring). Diese kleine Änderung im Code ist auch in den UML-Diagrammen ersichtlich. Der Commit zeigt ebenso, dass die Größe der Änderung vergleichsweise klein ist. Die `getIngredients`-Methode wurde dabei umbenannt in `getGroceryList`. Für den Entwickler macht das vor allem deutlich, dass das Rückgabeergebnis eine `GroceryList` anstelle einer `List` ist. Das ist besonders wichtig, da es sich hier um verschiedene Objekte mit unterschiedlichen Eigenschaften und Funktionalitäten handelt. So können vermeintliche Fehler in der Benutzung der Methode durch mehr Klarheit vermieden werden, die sonst durch Unachtsamkeit aufgetreten wären. + ## 8. Entwurfsmuster _[2 unterschiedliche Entwurfsmuster aus der Vorlesung (oder nach Absprache auch andere) jeweils sinnvoll einsetzen, begründen und UML-Diagramm]_ diff --git a/uml/refactoring-1-post.iuml b/uml/refactoring-1-post.iuml index e69de29..9699643 100644 --- a/uml/refactoring-1-post.iuml +++ b/uml/refactoring-1-post.iuml @@ -0,0 +1,21 @@ +@startuml + +left to right direction + +class AutoChef { + + main(args: String[]) +} + +class ConsoleOutputService { + - logger: Logger + + info(msg: String): void + + warning(msg: String): void + + severe(msg: String): void + + rawOut(msg: Object): void + + rawErr(msg: Object): void +} + + +AutoChef -> ConsoleOutputService + +@enduml \ No newline at end of file diff --git a/uml/refactoring-1-pre.iuml b/uml/refactoring-1-pre.iuml index e69de29..1cd4fb5 100644 --- a/uml/refactoring-1-pre.iuml +++ b/uml/refactoring-1-pre.iuml @@ -0,0 +1,10 @@ +@startuml + +left to right direction + +class AutoChef { + - logger: Logger + + main(args: String[]) +} + +@enduml \ No newline at end of file diff --git a/uml/refactoring-2-post.iuml b/uml/refactoring-2-post.iuml index e69de29..5d3c2f5 100644 --- a/uml/refactoring-2-post.iuml +++ b/uml/refactoring-2-post.iuml @@ -0,0 +1,14 @@ +@startuml + +left to right direction + +class Meal { + - Recipe recipe: Recipe + - adjustedNumberOfPeople: int + + Meal(recipe: Recipe, adjustedNumberOfPeople: int) + + setRecipe(recipe: Recipe): void + + getRecipe(): Recipe + + getAdjustedNumberOfPeople(): int + + getGroceryList(): GroceryList +} +@enduml \ No newline at end of file diff --git a/uml/refactoring-2-pre.iuml b/uml/refactoring-2-pre.iuml index e69de29..b4a0dac 100644 --- a/uml/refactoring-2-pre.iuml +++ b/uml/refactoring-2-pre.iuml @@ -0,0 +1,14 @@ +@startuml + +left to right direction + +class Meal { + - Recipe recipe: Recipe + - adjustedNumberOfPeople: int + + Meal(recipe: Recipe, adjustedNumberOfPeople: int) + + setRecipe(recipe: Recipe): void + + getRecipe(): Recipe + + getAdjustedNumberOfPeople(): int + + getIngredients(): GroceryList +} +@enduml \ No newline at end of file From 0de5c5b57c8298fd99354e7e2a5f6fe5d5e73797 Mon Sep 17 00:00:00 2001 From: ncryptedV1 <15632467+ncryptedV1@users.noreply.github.com> Date: Wed, 5 Apr 2023 14:47:42 +0200 Subject: [PATCH 16/39] add: dependency rule documentation --- README.md | 836 ++++++++++++++++++++++------------- uml/dependency-rule-neg.iuml | 31 ++ uml/dependency-rule-pos.iuml | 48 +- 3 files changed, 596 insertions(+), 319 deletions(-) diff --git a/README.md b/README.md index 63714f6..4e89dd7 100644 --- a/README.md +++ b/README.md @@ -10,18 +10,26 @@ Abgabedatum: 28. Mai 2023 ## Allgemeine Anmerkungen -- es darf nicht auf andere Kapitel als Leistungsnachweis verwiesen werden (z.B. in der Form “XY wurde schon in Kapitel 2 behandelt, daher hier keine Ausführung”) +- es darf nicht auf andere Kapitel als Leistungsnachweis verwiesen werden (z.B. in der Form “XY + wurde schon in Kapitel 2 behandelt, daher hier keine Ausführung”) - alles muss in UTF-8 codiert sein (Text und Code) -- sollten mündliche Aussagen den schriftlichen Aufgaben widersprechen, gelten die schriftlichen Aufgaben (ggf. an Anpassung der schriftlichen Aufgaben erinnern!) +- sollten mündliche Aussagen den schriftlichen Aufgaben widersprechen, gelten die schriftlichen + Aufgaben (ggf. an Anpassung der schriftlichen Aufgaben erinnern!) - alles muss ins Repository (Code, Ausarbeitung und alles was damit zusammenhängt) - die Beispiele sollten wenn möglich vom aktuellen Stand genommen werden - - finden sich dort keine entsprechenden Beispiele, dürfen auch ältere Commits unter Verweis auf den Commit verwendet werden - - Ausnahme: beim Kapitel “Refactoring” darf von vorne herein aus allen Ständen frei gewählt werden (mit Verweis auf den entsprechenden Commit) -- falls verlangte Negativ-Beispiele nicht vorhanden sind, müssen entsprechend mehr Positiv-Beispiele gebracht werden - - Achtung: werden im Code entsprechende Negativ-Beispiele gefunden, gibt es keine Punkte für die zusätzlichen Positiv-Beispiele - - Beispiel: “Nennen Sie jeweils eine Klasse, die das SRP einhält bzw. verletzt.” - - -> Antwort: Es gibt keine Klasse, die SRP verletzt, daher hier 2 Klassen, die SRP einhalten: [Klasse 1], [Klasse 2] - - -> Bewertung: falls im Code tatsächlich keine Klasse das SRP verletzt: volle Punktzahl ODER falls im Code mind. eine Klasse SRP verletzt: halbe Punktzahl + - finden sich dort keine entsprechenden Beispiele, dürfen auch ältere Commits unter Verweis auf + den Commit verwendet werden + - Ausnahme: beim Kapitel “Refactoring” darf von vorne herein aus allen Ständen frei gewählt + werden (mit Verweis auf den entsprechenden Commit) +- falls verlangte Negativ-Beispiele nicht vorhanden sind, müssen entsprechend mehr Positiv-Beispiele + gebracht werden + - Achtung: werden im Code entsprechende Negativ-Beispiele gefunden, gibt es keine Punkte für die + zusätzlichen Positiv-Beispiele + - Beispiel: “Nennen Sie jeweils eine Klasse, die das SRP einhält bzw. verletzt.” + - -> Antwort: Es gibt keine Klasse, die SRP verletzt, daher hier 2 Klassen, die SRP + einhalten: [Klasse 1], [Klasse 2] + - -> Bewertung: falls im Code tatsächlich keine Klasse das SRP verletzt: volle Punktzahl ODER + falls im Code mind. eine Klasse SRP verletzt: halbe Punktzahl - verlangte Positiv-Beispiele müssen gebracht werden - Code-Beispiel = Code in das Dokument kopieren @@ -29,21 +37,45 @@ Abgabedatum: 28. Mai 2023 ### Übersicht über die Applikation -AutoChef ist eine Anwendung zur einfachen und effizienten Verwaltung und Erstellung von Essensplänen, sowie dazugehöriger Einkaufslisten. Zugrunde liegt dabei eine Datenbank an Rezepten. Diese ist beliebig erweiterbar durch eine [Chefkoch](chefkoch.de)-Integration. Diese ermöglicht es, einen Link zu einem Rezept auf Chefkoch an die Anwendung zu übergeben, woraufhin diese das entsprechende Rezept herunterlädt und persistiert. Diese Datenbank an Rezepten, sowie jeweilige Rezept-Details, können jederzeit eingesehen werden. Hauptfunktion ist jedoch die Generierung von Essensplänen. Dafür kann der Nutzer einen Zeitraum, sowie die Anzahl an Personen je Mahlzeit angeben und die Anwendung generiert anhand der Rezept-Datenbank, einen zufälligen Essensplan. Für diesen Essensplan wird ebenfalls eine Einkaufsliste generiert, die an die Anzahl an Personen angepasst ist. Mit diesen Funktionalitäten ist es einfach möglich, seine Woche kulinarisch zu planen und die Einkaufliste für den Wocheneinkauf zu erstellen. +AutoChef ist eine Anwendung zur einfachen und effizienten Verwaltung und Erstellung von +Essensplänen, sowie dazugehöriger Einkaufslisten. Zugrunde liegt dabei eine Datenbank an Rezepten. +Diese ist beliebig erweiterbar durch eine [Chefkoch](chefkoch.de)-Integration. Diese ermöglicht es, +einen Link zu einem Rezept auf Chefkoch an die Anwendung zu übergeben, woraufhin diese das +entsprechende Rezept herunterlädt und persistiert. Diese Datenbank an Rezepten, sowie jeweilige +Rezept-Details, können jederzeit eingesehen werden. Hauptfunktion ist jedoch die Generierung von +Essensplänen. Dafür kann der Nutzer einen Zeitraum, sowie die Anzahl an Personen je Mahlzeit angeben +und die Anwendung generiert anhand der Rezept-Datenbank, einen zufälligen Essensplan. Für diesen +Essensplan wird ebenfalls eine Einkaufsliste generiert, die an die Anzahl an Personen angepasst ist. +Mit diesen Funktionalitäten ist es einfach möglich, seine Woche kulinarisch zu planen und die +Einkaufliste für den Wocheneinkauf zu erstellen. _[Was macht die Applikation, Wie funktioniert sie? Welches Problem löst sie/welchen Zweck hat sie?]_ ### Wie startet man die Applikation? -Bei AutoChef handelt es sich um eine CLI-Anwendung, geschrieben in Java 19. Zum Starten wird daher lediglich ein Desktop-Rechner mit **Java 19 aufwärts** benötigt. Die Anwendung kann dann über ein Konsolenfenster mit dem Befehl `java -jar AutoChef.jar` gestartet werden. +Bei AutoChef handelt es sich um eine CLI-Anwendung, geschrieben in Java 19. Zum Starten wird daher +lediglich ein Desktop-Rechner mit **Java 19 aufwärts** benötigt. Die Anwendung kann dann über ein +Konsolenfenster mit dem Befehl `java -jar AutoChef.jar` gestartet werden. _[Wie startet man die Applikation? Welche Voraussetzungen werden benötigt? Schritt-für-Schritt-Anleitung]_ ### Wie testet man die Applikation? -Nach dem Start der Anwendung wird der Nutzer durch einen intuitiven Dialog-Prozess begrüßt und geleitet. Der Nutzer interagiert dabei mit der Anwendung mittels des Konsolenfensters. Über dieses werden sowohl Informationen ausgegeben, als auch Eingaben vom Nutzer eingeholt. Der Dialog-Prozess ist so gestaltet, dass der Nutzer in der Regel mehrere nummerierte Optionen zur Auswahl hat und nur die Nummer der gewünschten Option eingeben und mit Enter absenden muss. Für den Import von Rezepten muss der Nutzer sich zuvor ein Rezept von Chefkoch aussuchen und im entsprechenden Dialog-Prozess-Schritt den dazugehörigen Link einfügen. Zu Beginn der Nutzung ist die Rezept-Datenbank noch leer, weshalb es sich anbietet anfangs ein paar Rezepte zu importieren. Erst danach können die Funktionen der Rezept-Anzeige und Essensplan-Generierung sinnvoll genutzt werden. - -Hinweis: Zur Persistierung der Rezepte erstellt die Anwendung im aktuellen Arbeitsverzeichnis (des Konsolenfensters) einen `recipes`-Ordner. Falls der Nutzer keine Rechte hat im aktuellen Arbeitsverzeichnis Ordner & Dateien zu erstellen, sowie in diese zu schreiben, kann das Programm nicht ordnungsgemäß arbeiten und terminiert. Versuche in dem Fall die Anwendung mit erhöhten Berechtigungen zu starten oder in einem Arbeitsverzeichnis mit Schreibzugriff auszuführen. +Nach dem Start der Anwendung wird der Nutzer durch einen intuitiven Dialog-Prozess begrüßt und +geleitet. Der Nutzer interagiert dabei mit der Anwendung mittels des Konsolenfensters. Über dieses +werden sowohl Informationen ausgegeben, als auch Eingaben vom Nutzer eingeholt. Der Dialog-Prozess +ist so gestaltet, dass der Nutzer in der Regel mehrere nummerierte Optionen zur Auswahl hat und nur +die Nummer der gewünschten Option eingeben und mit Enter absenden muss. Für den Import von Rezepten +muss der Nutzer sich zuvor ein Rezept von Chefkoch aussuchen und im entsprechenden +Dialog-Prozess-Schritt den dazugehörigen Link einfügen. Zu Beginn der Nutzung ist die +Rezept-Datenbank noch leer, weshalb es sich anbietet anfangs ein paar Rezepte zu importieren. Erst +danach können die Funktionen der Rezept-Anzeige und Essensplan-Generierung sinnvoll genutzt werden. + +Hinweis: Zur Persistierung der Rezepte erstellt die Anwendung im aktuellen Arbeitsverzeichnis (des +Konsolenfensters) einen `recipes`-Ordner. Falls der Nutzer keine Rechte hat im aktuellen +Arbeitsverzeichnis Ordner & Dateien zu erstellen, sowie in diese zu schreiben, kann das Programm +nicht ordnungsgemäß arbeiten und terminiert. Versuche in dem Fall die Anwendung mit erhöhten +Berechtigungen zu starten oder in einem Arbeitsverzeichnis mit Schreibzugriff auszuführen. _[Wie testet man die Applikation? Welche Voraussetzungen werden benötigt? Schritt-für-Schritt-Anleitung]_ @@ -51,30 +83,57 @@ _[Wie testet man die Applikation? Welche Voraussetzungen werden benötigt? Schri ### Was ist Clean Architecture? -Clean Architecture ist eine Architektur- und Designphilosophie, die darauf abzielt, komplexe Softwaresysteme in leicht verständliche, wartbare und erweiterbare Komponenten zu unterteilen. Es wurde von Robert C. Martin entwickelt und basiert auf den SOLID-Prinzipien. +Clean Architecture ist eine Architektur- und Designphilosophie, die darauf abzielt, komplexe +Softwaresysteme in leicht verständliche, wartbare und erweiterbare Komponenten zu unterteilen. Es +wurde von Robert C. Martin entwickelt und basiert auf den SOLID-Prinzipien. -Im Wesentlichen sieht Clean Architecture vor, dass eine Software in mehrere Schichten unterteilt wird, wobei jede Schicht eine klare Abhängigkeitshierarchie aufweist und nur von der nächstgelegenen Schicht abhängt. Die äußerste Schicht ist die Benutzerschnittstelle, die direkt mit dem Benutzer interagiert, gefolgt von einer oder mehreren Schichten mit Geschäftslogik, Datenzugriff und Infrastruktur. +Im Wesentlichen sieht Clean Architecture vor, dass eine Software in mehrere Schichten unterteilt +wird, wobei jede Schicht eine klare Abhängigkeitshierarchie aufweist und nur von der nächstgelegenen +Schicht abhängt. Die äußerste Schicht ist die Benutzerschnittstelle, die direkt mit dem Benutzer +interagiert, gefolgt von einer oder mehreren Schichten mit Geschäftslogik, Datenzugriff und +Infrastruktur. -Durch diese Aufteilung kann jede Schicht unabhängig von den anderen Schichten getestet und gewartet werden, was zu einer höheren Flexibilität und Skalierbarkeit des gesamten Systems führt. Darüber hinaus ist es einfacher, Änderungen an einem Teil des Systems vorzunehmen, ohne Auswirkungen auf den Rest des Systems zu haben. +Durch diese Aufteilung kann jede Schicht unabhängig von den anderen Schichten getestet und gewartet +werden, was zu einer höheren Flexibilität und Skalierbarkeit des gesamten Systems führt. Darüber +hinaus ist es einfacher, Änderungen an einem Teil des Systems vorzunehmen, ohne Auswirkungen auf den +Rest des Systems zu haben. -Clean Architecture ermutigt auch zur Verwendung von Schnittstellen, um die Abhängigkeiten zwischen den Komponenten zu minimieren. Durch die Verwendung von Schnittstellen können verschiedene Implementierungen ausgetauscht werden, ohne dass dies Auswirkungen auf den Rest des Systems hat. +Clean Architecture ermutigt auch zur Verwendung von Schnittstellen, um die Abhängigkeiten zwischen +den Komponenten zu minimieren. Durch die Verwendung von Schnittstellen können verschiedene +Implementierungen ausgetauscht werden, ohne dass dies Auswirkungen auf den Rest des Systems hat. -Zusammenfassend lässt sich sagen, dass Clean Architecture eine Methode ist, um große, komplexe Softwaresysteme in einfachere, leichter zu wartende Komponenten aufzuteilen, indem eine klare Abhängigkeitshierarchie zwischen den Komponenten eingeführt wird. +Zusammenfassend lässt sich sagen, dass Clean Architecture eine Methode ist, um große, komplexe +Softwaresysteme in einfachere, leichter zu wartende Komponenten aufzuteilen, indem eine klare +Abhängigkeitshierarchie zwischen den Komponenten eingeführt wird. _[allgemeine Beschreibung der Clean Architecture in eigenen Worten]_ ### Analyse der Dependency Rule -_[(1 Klasse, die die Dependency Rule einhält und eine Klasse, die die Dependency Rule verletzt); jeweils UML der Klasse und Analyse der Abhängigkeiten in beide Richtungen (d.h., von wem hängt die Klasse ab und wer hängt von der Klasse ab) in Bezug auf die Dependency Rule]_ - #### Positiv-Beispiel: Dependency Rule +- gewählte Klasse(n): `DialogService` + ![Dependency Rule positives Beispiel UML](http://www.plantuml.com/plantuml/proxy?cache=no&src=https://raw.githubusercontent.com/ncryptedV1/AutoChef/docs/uml/dependency-rule-pos.iuml) +`DialogService` ist abhängig von `RecipeRepository` (Interface). +`DialogService` hat keine Abhängigkeiten zu Implementierungen, nur zu Interfaces und anderen Domänen-Entities/Value Objects. +In dieser Anwendung zeigt die `DialogService`-Klasse ein gutes Beispiel für das Einhalten der Dependency Rule, da sie nur vom `RecipeRepository`-Interface abhängt und nicht von einer konkreten +Implementierung. Das bedeutet, dass die äußeren Schichten (in diesem Fall die Infrastrukturschicht mit der `RecipeFileRepository`-Implementierung) von der inneren Schicht (Domänenschicht) abhängen und nicht umgekehrt. Die Abhängigkeiten richten sich somit von außen nach innen. +Es gibt nur eine Klasse, die von `DialogService` abhängt: `AutoChef`. Dies ist die Main-Klasse der Anwendung. Ihre Aufgabe ist die Initialisierung des `DialogService` mitsamt Abhängigkeiten (`RecipeRepository`) und enthält daher keine weitere Logik noch +Konfigurationsdetails. Aus diesem Grund ist sie der Infrastrukturschicht zuzuordnen. Auch hier wird die Dependency Rule eingehalten, da eine Abhängigkeit von außen nach innen besteht. + #### Negativ-Beispiel: Dependency Rule +- gewählte Klasse(n): `ChefkochRecipeFetcher` + ![Dependency Rule negatives Beispiel UML](http://www.plantuml.com/plantuml/proxy?cache=no&src=https://raw.githubusercontent.com/ncryptedV1/AutoChef/docs/uml/dependency-rule-neg.iuml) +`ChefkochRecipeFetcher` ist abhängig von `WebsiteFetcher`. +`WebsiteFetcher` ist eine Util-Klasse, die für das Abrufen von Webseiten-Inhalten zuständig ist und damit der Infrastruktursschicht zugehörig. +Dadurch verletzt die `ChefkochRecipeFetcher`-Klasse die Dependency Rule, da eine Abhängigkeit von der Applikationsschicht in die Infrastrukturschicht besteht. Die `ChefkochRecipeFetcher`-Klasse sollte stattdessen von einem Interface abhängen, das in der Domänenschicht definiert ist und von einer Implementierung in einer äußeren Schicht bereitgestellt wird, um die Abhängigkeiten korrekt von außen nach innen zu richten. +Von der Klasse abhängig ist lediglich der `DialogService`, welcher sich auf selbiger Schicht befindet. + ### Analyse der Schichten #### Schicht: Domain Code @@ -83,28 +142,47 @@ _[(1 Klasse, die die Dependency Rule einhält und eine Klasse, die die Dependenc ![Schicht 1 UML](http://www.plantuml.com/plantuml/proxy?cache=no&src=https://raw.githubusercontent.com/ncryptedV1/AutoChef/docs/uml/layer-1.iuml) -Die `Recipe`-Klasse ist eine Entity im Sinne der Clean-Architecture, da sie die Entität eines Rezeptes abbildet. Ein Rezept besteht aus folgenden Attributen: +Die `Recipe`-Klasse ist eine Entity im Sinne der Clean-Architecture, da sie die Entität eines +Rezeptes abbildet. Ein Rezept besteht aus folgenden Attributen: + - `name: String`: Name des Rezeptes - `groceryList: GroceryList` : Liste an Zutaten, die für das Rezept benötigt werden -- `recipeSteps: List`: Liste an Zubereitungsschritten, die im Laufe des Rezeptes abgearbeitet werden müssen +- `recipeSteps: List`: Liste an Zubereitungsschritten, die im Laufe des Rezeptes + abgearbeitet werden müssen -Ein Rezept wird eineindeutig über eine ID indetifiziert. Die ID umfasst den Namen in Kleinschrift. Außerdem existieren für die Attribute und die ID jeweils Getter-Methoden und ein Konstruktor. +Ein Rezept wird eineindeutig über eine ID indetifiziert. Die ID umfasst den Namen in Kleinschrift. +Außerdem existieren für die Attribute und die ID jeweils Getter-Methoden und ein Konstruktor. -Damit liegt die Aufgabe der `Recipe`-Entität darin, ein Rezept semantisch im Code zu repräsentieren. Da das Konzept eines Rezept essenziell für die Domäne von Essensplänen ist, wurde es als Teil des Kernes der Anwendung aufgenommen. `Recipe` ist deshalb Teil der Schicht "Domain Code", da der Domänencode ebenjene Entities bzw den Kern der Anwendung enthalten sollte. Außerdem ändert sich die Modellierung eines Rezeptes selten, was ebenso dafür spricht, es in die Schicht "Domain Code" einzuordnen. +Damit liegt die Aufgabe der `Recipe`-Entität darin, ein Rezept semantisch im Code zu repräsentieren. +Da das Konzept eines Rezept essenziell für die Domäne von Essensplänen ist, wurde es als Teil des +Kernes der Anwendung aufgenommen. `Recipe` ist deshalb Teil der Schicht "Domain Code", da der +Domänencode ebenjene Entities bzw den Kern der Anwendung enthalten sollte. Außerdem ändert sich die +Modellierung eines Rezeptes selten, was ebenso dafür spricht, es in die Schicht "Domain Code" +einzuordnen. #### Schicht: Application Code -- gewählte Klasse(n): `DialogService` mit `DialogState` +- gewählte Klasse(n): `DialogService` mit `DialogState` ![Schicht 2 UML](http://www.plantuml.com/plantuml/proxy?cache=no&src=https://raw.githubusercontent.com/ncryptedV1/AutoChef/docs/uml/layer-2.iuml) -Die Schicht des Applications Codes umfasst mehrere Klassen. Dabei soll die Klasse `DialogService` im Fokus stehen. +Die Schicht des Applications Codes umfasst mehrere Klassen. Dabei soll die Klasse `DialogService` im +Fokus stehen. -Im Kern ist der Dialog-Service für die Ablauflogik der Anwendung verantwortlich. Er verwaltet die Datenpersistenz über die Klassen `RecipeRepository` und `RecipeFileRepository`, ist aber gleichzeitig auch für die Nutzung von Benutzereingaben über die Klassen `ConsoleInputReader`, `ConsoleInputParser` und `ConsoleOutputService` verantwortlich. Damit ist er die Schnittstelle zwischen den einzelnen Verantwortungsbereichen der Anwendung. +Im Kern ist der Dialog-Service für die Ablauflogik der Anwendung verantwortlich. Er verwaltet die +Datenpersistenz über die Klassen `RecipeRepository` und `RecipeFileRepository`, ist aber +gleichzeitig auch für die Nutzung von Benutzereingaben über die +Klassen `ConsoleInputReader`, `ConsoleInputParser` und `ConsoleOutputService` verantwortlich. Damit +ist er die Schnittstelle zwischen den einzelnen Verantwortungsbereichen der Anwendung. -Im Allgemeinen startet er den Dialog mit dem Benutzer, organisiert die Generierung von Essensplänen und gibt dem Benutzer die Möglichkeit Rezepte hinzuzufügen. In diesem Sinne nimmt er die Rolle eines "Controllers" ein. Für andere Anwendungen wie etwa eine Web-Anwendung würde eine andere Funktionalität erwartet werden. Der Dialog-Service ist speziell für den Anwendungsfall einer CLI-Anwendung definiert und nutzbar. +Im Allgemeinen startet er den Dialog mit dem Benutzer, organisiert die Generierung von Essensplänen +und gibt dem Benutzer die Möglichkeit Rezepte hinzuzufügen. In diesem Sinne nimmt er die Rolle +eines "Controllers" ein. Für andere Anwendungen wie etwa eine Web-Anwendung würde eine andere +Funktionalität erwartet werden. Der Dialog-Service ist speziell für den Anwendungsfall einer +CLI-Anwendung definiert und nutzbar. -Ebenso bedeutet das, dass Änderungen an dem DialogService keinen Einfluss auf den Domänen-Code haben. All diese Aspekte begründen, warum der Dialog-Service im Application Code angesidelt ist. +Ebenso bedeutet das, dass Änderungen an dem DialogService keinen Einfluss auf den Domänen-Code +haben. All diese Aspekte begründen, warum der Dialog-Service im Application Code angesidelt ist. ## 3. SOLID @@ -116,9 +194,15 @@ gewählte Klasse: `Quantity` ![Single Responsibility positives Beispiel UML](http://www.plantuml.com/plantuml/proxy?cache=no&src=https://raw.githubusercontent.com/ncryptedV1/AutoChef/docs/uml/single-responsibility-pos.iuml) -Die `Quantity`-Klasse bildet eine "Menge" oder "Anzahl" ab. Es ist relevant für die Menge von Zutaten in einem Rezept. Die Klasse ist maßgeblich definiert durch ihr einziges Attribute `value`, das eine Menge als `double` repräsentiert. Zusätzlich dazu kann über die Methode `multiply` eine `Quantity`-Instanz mit einem Wert multipliziert werden, was wieder eine neue `Quantity`-Instanz zurückgibt. `Quantity` ist vor allem relevant im Kontext von `GroceryItem`, da dort beschrieben wird, wie viel von einem `Ingredient` benötigt wird. +Die `Quantity`-Klasse bildet eine "Menge" oder "Anzahl" ab. Es ist relevant für die Menge von +Zutaten in einem Rezept. Die Klasse ist maßgeblich definiert durch ihr einziges Attribute `value`, +das eine Menge als `double` repräsentiert. Zusätzlich dazu kann über die Methode `multiply` +eine `Quantity`-Instanz mit einem Wert multipliziert werden, was wieder eine neue `Quantity`-Instanz +zurückgibt. `Quantity` ist vor allem relevant im Kontext von `GroceryItem`, da dort beschrieben +wird, wie viel von einem `Ingredient` benötigt wird. -Die Verantwortung der Klasse liegt demnach darin, semantisch eine "Menge" abzubilden. Daneben besitzt sie keine weiteren Verantwortlichkeiten. +Die Verantwortung der Klasse liegt demnach darin, semantisch eine "Menge" abzubilden. Daneben +besitzt sie keine weiteren Verantwortlichkeiten. #### Negativ-Beispiel @@ -126,36 +210,58 @@ gewählte Klasse: `DialogService` ![Single Responsibility negatives Beispiel UML](http://www.plantuml.com/plantuml/proxy?cache=no&src=https://raw.githubusercontent.com/ncryptedV1/AutoChef/docs/uml/single-responsibility-neg-1.iuml) -Ein Negativbeispiel für das SRP ist der Dialog-Service - im Speziellen die `startMealPlanGeneration`-Methode. Die Klasse nimmt die Rolle eines "Controllers" ein, da sie die Persistenz von Daten und die Ausgabe an das Command-Line-Interface verwaltet. Als Teil der Application-Code-Schicht enthält der Dialog-Service jedwede Logik für den Ablauf der Anwendung: +Ein Negativbeispiel für das SRP ist der Dialog-Service - im Speziellen die `startMealPlanGeneration` +-Methode. Die Klasse nimmt die Rolle eines "Controllers" ein, da sie die Persistenz von Daten und +die Ausgabe an das Command-Line-Interface verwaltet. Als Teil der Application-Code-Schicht enthält +der Dialog-Service jedwede Logik für den Ablauf der Anwendung: + - es startet den Dialog mit dem Benutzer -- organisiert die Generierung von Essensplänen und +- organisiert die Generierung von Essensplänen und - gibt dem Benutzer die Möglichkeit Rezepte hinzuzufügen. -Der Dialog-Service ist speziell für den Anwendungsfall einer CLI-Anwendung definiert. + Der Dialog-Service ist speziell für den Anwendungsfall einer CLI-Anwendung definiert. Der Dialog-Service hat jedoch mehrere Verantwortlichkeiten und bricht damit das SRP: -- Steuerung der Ablauflogik (dafür werden die Verwaltung von Benutzerein- und -ausgabe als auch der Datenpersistenz genutzt) + +- Steuerung der Ablauflogik (dafür werden die Verwaltung von Benutzerein- und -ausgabe als auch der + Datenpersistenz genutzt) - Generierung eines Essensplan über die Methode `startMealPlanGeneration` -Gerade die zweite Verantwortlichkeit - die Generierung eines Essenplans - kann in einen separaten Service ausgelagert werden: +Gerade die zweite Verantwortlichkeit - die Generierung eines Essenplans - kann in einen separaten +Service ausgelagert werden: ![Single Responsibility negatives Beispiel UML](http://www.plantuml.com/plantuml/proxy?cache=no&src=https://raw.githubusercontent.com/ncryptedV1/AutoChef/docs/uml/single-responsibility-negc.iuml) -Dabei könnte `startMealPlanGeneration` mit einem `GenerationService` interagieren. Der `GenerationService` wäre dann für die eigentliche Generierung des Essensplans verantwortlich, während die `startMealPlanGeneration` lediglich eine verwaltende Rolle einnehmen würde. +Dabei könnte `startMealPlanGeneration` mit einem `GenerationService` interagieren. +Der `GenerationService` wäre dann für die eigentliche Generierung des Essensplans verantwortlich, +während die `startMealPlanGeneration` lediglich eine verwaltende Rolle einnehmen würde. ### Analyse Open-Closed-Principle (OCP) - #### Positiv-Beispiel -gewählte Klasse(n): `RecipeFileRepository` mit Interface `RecipeRepository` +gewählte Klasse(n): `RecipeFileRepository` mit Interface `RecipeRepository` ![Open-Closed positives Beispiel UML](http://www.plantuml.com/plantuml/proxy?cache=no&src=https://raw.githubusercontent.com/ncryptedV1/AutoChef/docs/uml/open-closed-pos.iuml) -Die `RecipeFileRepository`-Klasse ist ein Repository, dass das Interface `RecipeRepository` implementiert. Das Interface beschreibt, welche Schnittstelle zum Speichern von Daten benötigt wird. Die `RecipeFileRepository`-Klasse implementiert diese Methoden für den Fall von Datenpersistenz in Dateien. - -Da hier ein Interface verwendet wird, ist es leicht möglich neue Funktionalität hinzuzufügen. Dazu muss lediglich eine neuer Methodekopf im Interface definiert werden. Die dazugehörige Methode muss in allen Klassen, die das Interface implementieren, hinzugefügt werden. Damit ist die "Open" Eigenschaft des OCP erfüllt. Auf der anderen Seite sollte Code "closed" gegenüber Modifikationen sein. Das ist ebenso durch die Verwendung eines Interfaces erfüllt. Das Interface bestimmt die Funktionalitäten der Klasse. - -Hier mit dem OCP zu arbeiten ist vor allem aus Sicht der Flexibilität und Erweiterbarkeit sinnhaft. Sollte sich später dazu entschieden werden, eine andere Methode der Datenpersistenz zu wählen, muss lediglich die Implementierung des Interfaces angepasst bzw weitere Klassen, die das Interface implementieren hinzugefügt werden. So kann die Anwendung an verschiedene Umgebungen und Anforderungen leichter angepasst werden, ohne die inneren Schichten der Clean-Architecture verändern zu müssen. Gerade in Hinsicht dessen, dass dieses Projekt eventuell freizeitlich weiterverfolgt wird, ergibt dieser Ansatz Sinn. +Die `RecipeFileRepository`-Klasse ist ein Repository, dass das Interface `RecipeRepository` +implementiert. Das Interface beschreibt, welche Schnittstelle zum Speichern von Daten benötigt wird. +Die `RecipeFileRepository`-Klasse implementiert diese Methoden für den Fall von Datenpersistenz in +Dateien. + +Da hier ein Interface verwendet wird, ist es leicht möglich neue Funktionalität hinzuzufügen. Dazu +muss lediglich eine neuer Methodekopf im Interface definiert werden. Die dazugehörige Methode muss +in allen Klassen, die das Interface implementieren, hinzugefügt werden. Damit ist die "Open" +Eigenschaft des OCP erfüllt. Auf der anderen Seite sollte Code "closed" gegenüber Modifikationen +sein. Das ist ebenso durch die Verwendung eines Interfaces erfüllt. Das Interface bestimmt die +Funktionalitäten der Klasse. + +Hier mit dem OCP zu arbeiten ist vor allem aus Sicht der Flexibilität und Erweiterbarkeit sinnhaft. +Sollte sich später dazu entschieden werden, eine andere Methode der Datenpersistenz zu wählen, muss +lediglich die Implementierung des Interfaces angepasst bzw weitere Klassen, die das Interface +implementieren hinzugefügt werden. So kann die Anwendung an verschiedene Umgebungen und +Anforderungen leichter angepasst werden, ohne die inneren Schichten der Clean-Architecture verändern +zu müssen. Gerade in Hinsicht dessen, dass dieses Projekt eventuell freizeitlich weiterverfolgt +wird, ergibt dieser Ansatz Sinn. #### Negativ-Beispiel @@ -169,11 +275,22 @@ nachher: ![Open-Closed negatives Beispiel UML](http://www.plantuml.com/plantuml/proxy?cache=no&src=https://raw.githubusercontent.com/ncryptedV1/AutoChef/docs/uml/open-closed-neg-2.iuml) -Die `ChefkochRecipeFetcher`-Klasse ist ein Beispiel davon, wie das OCP verletzt werden kann. Diese Klasse dient dazu, Daten aus einer Chefkoch-Website zu extrahieren. Dazu werden verschiedene RegEx-Ausdrücke genutzt. +Die `ChefkochRecipeFetcher`-Klasse ist ein Beispiel davon, wie das OCP verletzt werden kann. Diese +Klasse dient dazu, Daten aus einer Chefkoch-Website zu extrahieren. Dazu werden verschiedene +RegEx-Ausdrücke genutzt. -Das OCP ist hier nicht erfüllt, da die Klasse nicht offen für Erweiterungen ist, ohne gleichzeitig geschlossen für Modifikationen zu sein. Sollte die Anforderung aufkommen, weitere Anbieter wie KitchenStories.com zu integrieren oder gar die Chefkoch-Integration zu ersetzen, müssten einige Code-Modifikationen durchgeführt werden. Dadurch ist das OCP verletzt. +Das OCP ist hier nicht erfüllt, da die Klasse nicht offen für Erweiterungen ist, ohne gleichzeitig +geschlossen für Modifikationen zu sein. Sollte die Anforderung aufkommen, weitere Anbieter wie +KitchenStories.com zu integrieren oder gar die Chefkoch-Integration zu ersetzen, müssten einige +Code-Modifikationen durchgeführt werden. Dadurch ist das OCP verletzt. -Gelöst werden kann das durch ein Interface wie im zweiten UML ersichtlich. Ein abstrakter `RecipeFetcher` kann als Interface für die Integration genutzt werden. Welche spezielle Webseiten-Integration im Hintergrund benutzt wird, ist dabei irrelevant für die inneren Schichten der Clean-Architecture. So wäre die Implementierung offen für weitere Erweiterungen unter Verwendung des Interfaces. Gleichzeitig müssten keine aufwändigen Code-Modifikationen an den inneren Schichten oder dem Interface vorgenommen werden, damit beide Module miteinander interagieren können. Dadurch wäre das OCP erfüllt. +Gelöst werden kann das durch ein Interface wie im zweiten UML ersichtlich. Ein +abstrakter `RecipeFetcher` kann als Interface für die Integration genutzt werden. Welche spezielle +Webseiten-Integration im Hintergrund benutzt wird, ist dabei irrelevant für die inneren Schichten +der Clean-Architecture. So wäre die Implementierung offen für weitere Erweiterungen unter Verwendung +des Interfaces. Gleichzeitig müssten keine aufwändigen Code-Modifikationen an den inneren Schichten +oder dem Interface vorgenommen werden, damit beide Module miteinander interagieren können. Dadurch +wäre das OCP erfüllt. ### Analyse Liskov-Substitution- (LSP), Interface-Segreggation- (ISP), Dependency-Inversion-Principle (DIP) @@ -208,62 +325,70 @@ _[jeweils eine bis jetzt noch nicht behandelte Klasse als positives und negative ![Kohäsion Beispiel UML](http://www.plantuml.com/plantuml/proxy?cache=no&src=https://raw.githubusercontent.com/ncryptedV1/AutoChef/docs/uml/cohesion.iuml) ### Don’t Repeat Yourself (DRY)\* -Commit-SHA: d89dcb3 ([Link](https://github.com/ncryptedV1/AutoChef/commit/d89dcb38a0e45759dd3e689593870d1e9ed0da96)) + +Commit-SHA: +d89dcb3 ([Link](https://github.com/ncryptedV1/AutoChef/commit/d89dcb38a0e45759dd3e689593870d1e9ed0da96)) vorher: ```java - public MealPlan(List meals, LocalDate start, LocalDate end) { - int days = start.until(end).getDays(); - if (meals.size() != days) { - throw new IllegalArgumentException( - "Mahlzeiten-Plan spannt " + days + " Tage, es wurden allerdings nur " + meals.size() - + " Mahlzeiten übergeben"); + public MealPlan(List meals,LocalDate start,LocalDate end){ + int days=start.until(end).getDays(); + if(meals.size()!=days){ + throw new IllegalArgumentException( + "Mahlzeiten-Plan spannt "+days+" Tage, es wurden allerdings nur "+meals.size() + +" Mahlzeiten übergeben"); } - this.meals = meals; - this.start = start; - this.end = end; - } + this.meals=meals; + this.start=start; + this.end=end; + } - // ... +// ... - public int getDays() { +public int getDays(){ return start.until(getEnd()).getDays(); - } + } ``` nachher: ```java - public MealPlan(List meals, LocalDate start, LocalDate end) { - this.start = start; - this.end = end; - - int days = getDays(); - if (meals.size() != days) { - throw new IllegalArgumentException( - "Mahlzeiten-Plan spannt " + days + " Tage, es wurden allerdings nur " + meals.size() - + " Mahlzeiten übergeben"); + public MealPlan(List meals,LocalDate start,LocalDate end){ + this.start=start; + this.end=end; + + int days=getDays(); + if(meals.size()!=days){ + throw new IllegalArgumentException( + "Mahlzeiten-Plan spannt "+days+" Tage, es wurden allerdings nur "+meals.size() + +" Mahlzeiten übergeben"); } - this.meals = meals; - } + this.meals=meals; + } + +// ... - // ... - - public int getDays() { +public int getDays(){ return start.until(getEnd()).getDays(); - } + } ``` -Die oben gezeigte Änderung ist ein kleines Beispiel zur Reduktion von Code-Duplikationen. Die Methode `getDays` ware bereits vor dem Commit vorhanden. Sie dient dazu, die Anzahl an Tagen zwischen `start` und `end` zu berechnen. Vor dem Commit, wurde jedoch dieselbe Logik im Konstruktor in der 2. Zeile verwendet: +Die oben gezeigte Änderung ist ein kleines Beispiel zur Reduktion von Code-Duplikationen. Die +Methode `getDays` ware bereits vor dem Commit vorhanden. Sie dient dazu, die Anzahl an Tagen +zwischen `start` und `end` zu berechnen. Vor dem Commit, wurde jedoch dieselbe Logik im Konstruktor +in der 2. Zeile verwendet: ```java -int days = start.until(end).getDays(); +int days=start.until(end).getDays(); ``` -Der Commit sorgte dafür, dass dieser Code durch einen Aufruf der `getDays` Methode ersetzt wurde. Das hat zur Folge, dass die Logik der Berechnung der Anzahl der Tage nur an einem Punkt im Code genutzt wird: in der `getDays` Methode. Dadurch können Fehler vermieden werden, die durch unachtsame Änderungen an einer der beiden Code-Stellen aufgetreten wären. +Der Commit sorgte dafür, dass dieser Code durch einen Aufruf der `getDays` Methode ersetzt wurde. +Das hat zur Folge, dass die Logik der Berechnung der Anzahl der Tage nur an einem Punkt im Code +genutzt wird: in der `getDays` Methode. Dadurch können Fehler vermieden werden, die durch unachtsame +Änderungen an einer der beiden Code-Stellen aufgetreten wären. ## 5. Unit Tests @@ -283,7 +408,12 @@ Der Commit sorgte dafür, dass dieser Code durch einen Aufruf der `getDays` Meth | _TestGroceryList#testAddItem_ | testet, ob die `addItem` Methode der `GroceryList` Klasse korrekt eine Itme zur GroceryList hinzufügt | ### ATRIP: Automatic -Die Tests wurden mittels Testautomatisierung realisiert. Dabei wurde JUnit 5 verwendet, um automatisierte Tests zu schreiben. Über die IDE IntelliJ IDEA können die Test simpel über einen Knopfdruck ausgeführt werden. Im Anschluss laufen alle Tests automatisch. Das Ergebnis der Testautomatisierung zeigt, ob ein individueller Test erfolgreich (bestanden) oder nicht erfolgreich (fehlgeschlagen) war. + +Die Tests wurden mittels Testautomatisierung realisiert. Dabei wurde JUnit 5 verwendet, um +automatisierte Tests zu schreiben. Über die IDE IntelliJ IDEA können die Test simpel über einen +Knopfdruck ausgeführt werden. Im Anschluss laufen alle Tests automatisch. Das Ergebnis der +Testautomatisierung zeigt, ob ein individueller Test erfolgreich (bestanden) oder nicht +erfolgreich (fehlgeschlagen) war. ### ATRIP: Thorough @@ -293,126 +423,139 @@ Test-Klasse: `TestIngredient` ```java @Test - void testEqualsResSelf() { - // arrange - Ingredient ingredient1 = mock(Ingredient.class); + void testEqualsResSelf(){ + // arrange + Ingredient ingredient1=mock(Ingredient.class); // act - boolean res = ingredient1.equals(ingredient1); + boolean res=ingredient1.equals(ingredient1); // assert assertTrue(res); - } + } - @Test - void testEqualsResSame() { - // arrange - String value = "banana"; - Ingredient ingredient1 = new Ingredient(value); - Ingredient ingredient2 = new Ingredient(value); +@Test + void testEqualsResSame(){ + // arrange + String value="banana"; + Ingredient ingredient1=new Ingredient(value); + Ingredient ingredient2=new Ingredient(value); - // act - boolean res = ingredient1.equals(ingredient2); + // act + boolean res=ingredient1.equals(ingredient2); - // assert - assertTrue(res); - } + // assert + assertTrue(res); + } - @Test - void testEqualsDifferent() { - // arrange - Ingredient ingredient1 = new Ingredient("banana"); - Ingredient ingredient2 = new Ingredient("nutella"); +@Test + void testEqualsDifferent(){ + // arrange + Ingredient ingredient1=new Ingredient("banana"); + Ingredient ingredient2=new Ingredient("nutella"); - // act - boolean res = ingredient1.equals(ingredient2); + // act + boolean res=ingredient1.equals(ingredient2); - // assert - assertFalse(res); - } + // assert + assertFalse(res); + } - @Test - void testEqualsNull() { - // arrange - Ingredient ingredient1 = mock(Ingredient.class); +@Test + void testEqualsNull(){ + // arrange + Ingredient ingredient1=mock(Ingredient.class); // act - boolean res = ingredient1.equals(null); + boolean res=ingredient1.equals(null); // assert assertFalse(res); - } - - @Test - void testHashCodeTrue() { - // arrange - String value = "banana"; - Ingredient ingredient1 = new Ingredient(value); - Ingredient ingredient2 = new Ingredient(value); - - // act - int code1 = ingredient1.hashCode(); - int code2 = ingredient2.hashCode(); - - // assert - assertEquals(code1, code2); - } + } - @Test - void testHashCodeFalse() { - // arrange - Ingredient ingredient1 = new Ingredient("banana"); - Ingredient ingredient2 = new Ingredient("nutella"); - - // act - int code1 = ingredient1.hashCode(); - int code2 = ingredient2.hashCode(); - - // assert - assertNotEquals(code1, code2); - } +@Test + void testHashCodeTrue(){ + // arrange + String value="banana"; + Ingredient ingredient1=new Ingredient(value); + Ingredient ingredient2=new Ingredient(value); + + // act + int code1=ingredient1.hashCode(); + int code2=ingredient2.hashCode(); + + // assert + assertEquals(code1,code2); + } + +@Test + void testHashCodeFalse(){ + // arrange + Ingredient ingredient1=new Ingredient("banana"); + Ingredient ingredient2=new Ingredient("nutella"); + + // act + int code1=ingredient1.hashCode(); + int code2=ingredient2.hashCode(); + + // assert + assertNotEquals(code1,code2); + } ``` -Diese Testklasse mit den dargestellten Methoden ist ein Positivbeispiel für "thorough testing". All diese Testmethoden testen verschiedene Zweige der selbstimplementieren `equals` Methode der Klasse `Ingredient`. Sie testen Vergeliche zwischen +Diese Testklasse mit den dargestellten Methoden ist ein Positivbeispiel für "thorough testing". All +diese Testmethoden testen verschiedene Zweige der selbstimplementieren `equals` Methode der +Klasse `Ingredient`. Sie testen Vergeliche zwischen + - einer `Ingredient`-Instanz mit sich selbst - zwei gleichen `Ingredient`-Instanzen - zwei verschiedenen `Ingredient`-Instanzen - `null` und einer `Ingredient`-Instanz -Damit sind alle relevanten Pfade der `equals` Methode abgedeckt. Außerdem wird auch die Umgebung getestet. Da die `equals` Methode, auf die `hashcode` Methode zugreift, können Fehler in der `equals` Methode auf Fehler in der dortigen zurückgeführt werden. Demnentsprechend müssen auch alle relevanten Pfade der `hashcode` Methode getestet werden, was hier gemacht wird. +Damit sind alle relevanten Pfade der `equals` Methode abgedeckt. Außerdem wird auch die Umgebung +getestet. Da die `equals` Methode, auf die `hashcode` Methode zugreift, können Fehler in +der `equals` Methode auf Fehler in der dortigen zurückgeführt werden. Demnentsprechend müssen auch +alle relevanten Pfade der `hashcode` Methode getestet werden, was hier gemacht wird. #### negatives Beispiel + zu testende Methode: `DialogService#startMealPlanGeneration` ```java - private void startMealPlanGeneration() { - currentState = DialogState.MEAL_PLAN_GENERATION; + private void startMealPlanGeneration(){ + currentState=DialogState.MEAL_PLAN_GENERATION; ConsoleOutputService.rawOut("Wir generieren jetzt zusammen einen Mahlzeiten-Plan. :D"); - LocalDate startDate = ConsoleInputParser.getDate(null, null, - "Wann soll der Plan beginnen? (DD.MM.YYYY)"); - LocalDate endDate = ConsoleInputParser.getDate(startDate, null, - "Bis wann soll der Plan gehen (exklusiv)? (DD.MM.YYYY)"); - int people = ConsoleInputParser.getInteger(1, 99, - "Für wie viele Leute soll der Plan generiert werden?"); - int days = startDate.until(endDate).getDays(); - ConsoleOutputService.rawOut("Ok, ich generiere einen Plan für " + days + " Tage..."); - - List recipes = recipeRepository.getRecipes(); - List meals = new ArrayList<>(); - for (int i = 0; i < days; i++) { - meals.add(new Meal(recipes.get(random.nextInt(recipes.size())), people)); + LocalDate startDate=ConsoleInputParser.getDate(null,null, + "Wann soll der Plan beginnen? (DD.MM.YYYY)"); + LocalDate endDate=ConsoleInputParser.getDate(startDate,null, + "Bis wann soll der Plan gehen (exklusiv)? (DD.MM.YYYY)"); + int people=ConsoleInputParser.getInteger(1,99, + "Für wie viele Leute soll der Plan generiert werden?"); + int days=startDate.until(endDate).getDays(); + ConsoleOutputService.rawOut("Ok, ich generiere einen Plan für "+days+" Tage..."); + + List recipes=recipeRepository.getRecipes(); + List meals=new ArrayList<>(); + for(int i=0;i WebsiteFetcher.getWebsiteBody(invalidUrl)); - } + assertThrows(IllegalArgumentException.class,()->WebsiteFetcher.getWebsiteBody(invalidUrl)); + } ``` -Diese Testmethode testet die `getWebsiteBody` Methode der `WebsiteFetcher` Klasse. Hierbei wird ein String, der eine invalide URL darstellt, in die zu testende Funktion übergeben. Anschließend wird die `getWebsiteBody` Methode aufgerufen und überprüft, ob die richtige Exception geworfen wird. +Diese Testmethode testet die `getWebsiteBody` Methode der `WebsiteFetcher` Klasse. Hierbei wird ein +String, der eine invalide URL darstellt, in die zu testende Funktion übergeben. Anschließend wird +die `getWebsiteBody` Methode aufgerufen und überprüft, ob die richtige Exception geworfen wird. Diese Testmethode ist ein Positivbeispiel für professionelle Testklassen aus mehreren Gründen: -1. Der Name der Testmethode beschreibt gut, was genau getestet wird. In diesem Fall die `getWebsiteBody` Methode bei Eingabe einer invaliden URL. -2. Die zugehörige Klasse wurde nur zu Testzwecken angelegt. -3. Im Gegensatz zu Getter- oder Setter-Methoden existiert Logik, die getestetet werden sollte. Ein Test ist dementsprechend notwendig +1. Der Name der Testmethode beschreibt gut, was genau getestet wird. In diesem Fall + die `getWebsiteBody` Methode bei Eingabe einer invaliden URL. +2. Die zugehörige Klasse wurde nur zu Testzwecken angelegt. +3. Im Gegensatz zu Getter- oder Setter-Methoden existiert Logik, die getestetet werden sollte. Ein + Test ist dementsprechend notwendig #### negatives Beispiel + Test-Methode: `TestUnit#getValue` ```java @Test - void testGetValue() { - // arrange - String expected = "piece"; - Unit unit = new Unit(expected); + void testGetValue(){ + // arrange + String expected="piece"; + Unit unit=new Unit(expected); - // act - String res = unit.getValue(); + // act + String res=unit.getValue(); - // assert - assertEquals(expected, res); - } + // assert + assertEquals(expected,res); + } ``` -Diese Testmethode testet die `getValue` Getter-Methode der `Unit` Klasse. Dabei wird ein `Unit` ValueObject angelegt mit einem initialen Wert. Das Ergebnis der `getValue` wird verglichen mit dem initialen Wert. Beide Werten sollten gleich sein. +Diese Testmethode testet die `getValue` Getter-Methode der `Unit` Klasse. Dabei wird ein `Unit` +ValueObject angelegt mit einem initialen Wert. Das Ergebnis der `getValue` wird verglichen mit dem +initialen Wert. Beide Werten sollten gleich sein. -Diese Klasse ist ein Negativbeispiel, da sie einen unnötigen Test darstellt. Getter Methoden sollten nicht getestet werden. Des Weiteren enthält diese Methode keine komplexe Logik, die ein Testen erfordern würde. Es handelt sich hier um einen Test, "der nur wegen des Tests geschrieben wurde". Außerdem ist der Dokumentationswert der Methode nicht vorhanden. +Diese Klasse ist ein Negativbeispiel, da sie einen unnötigen Test darstellt. Getter Methoden sollten +nicht getestet werden. Des Weiteren enthält diese Methode keine komplexe Logik, die ein Testen +erfordern würde. Es handelt sich hier um einen Test, "der nur wegen des Tests geschrieben wurde". +Außerdem ist der Dokumentationswert der Methode nicht vorhanden. ### Code Coverage -Die folgende Tabelle zeigt die summierten Werte der verschiedenen Arten von Testabdeckung des Projektes. Eine aufgeschlüsselte Version der Testabdeckung ist im folgenden Bild zu sehen. Die Prozentangaben des Bildes folgen derselben Reihenfolge wie in der Tabelle aufgelistet. +Die folgende Tabelle zeigt die summierten Werte der verschiedenen Arten von Testabdeckung des +Projektes. Eine aufgeschlüsselte Version der Testabdeckung ist im folgenden Bild zu sehen. Die +Prozentangaben des Bildes folgen derselben Reihenfolge wie in der Tabelle aufgelistet. -| Art | % | -|-----------------|----| -| Class-Coverage | 70 | -| Method-Coverage | 66 | -| Line-Coverage | 51 | +| Art | % | +|-----------------|-----| +| Class-Coverage | 70 | +| Method-Coverage | 66 | +| Line-Coverage | 51 | ![test coverage](./docs/res/cov.png) -Im Allgemeinen bestand das Ziel, die Testabdeckung so hoch wie möglich zu halten. Deshalb wurden weitesgehend alle Klassen automatisiert getestet, jedoch nicht alle. Das hat vor allem den Grund, dass die automatisierte Testung für einige Klassen unserer Ansicht nicht sinnhaft war. Beispiele hierfür sind: +Im Allgemeinen bestand das Ziel, die Testabdeckung so hoch wie möglich zu halten. Deshalb wurden +weitesgehend alle Klassen automatisiert getestet, jedoch nicht alle. Das hat vor allem den Grund, +dass die automatisierte Testung für einige Klassen unserer Ansicht nicht sinnhaft war. Beispiele +hierfür sind: -- `domain.util.Formats`: Diese Klasse dient lediglich als Format-Klasse ohne weitere Logik. -- `domain.util.io.ConsoleOutputService`: Da es sich hier vor allem um die Formattierung von Konsolenausgaben handelt, wurden hier die Tests ebenso vernachlässigt. +- `domain.util.Formats`: Diese Klasse dient lediglich als Format-Klasse ohne weitere Logik. +- `domain.util.io.ConsoleOutputService`: Da es sich hier vor allem um die Formattierung von + Konsolenausgaben handelt, wurden hier die Tests ebenso vernachlässigt. Unabhängig dessen begründet sich die Code-Coverage wie folgt: -- Class-Coverage: die Mehrheit der Klassen weist Tests auf, weshalb der Wert hier mit 70% vergleichsweise hoch liegt. -- Method-Coverage: Aus genannten Gründen wurden eine Reihe von Methoden nicht getestet, daher liegt die Method-Coverage unter der Class-Coverage. -- Line-Coverage: Die Line-Coverage ist durch die Method-Coverage bedingt und ist deshalb vor allem aus demselbigen Grund niedriger. + +- Class-Coverage: die Mehrheit der Klassen weist Tests auf, weshalb der Wert hier mit 70% + vergleichsweise hoch liegt. +- Method-Coverage: Aus genannten Gründen wurden eine Reihe von Methoden nicht getestet, daher liegt + die Method-Coverage unter der Class-Coverage. +- Line-Coverage: Die Line-Coverage ist durch die Method-Coverage bedingt und ist deshalb vor allem + aus demselbigen Grund niedriger. ### Fakes und Mocks -In diesem Projekt wurden vor allem Mock-Objekte eingesetzt. Sie wurden genutzt, um benötigte Nebenklassen zu mocken. Nachfolgend sind zwei demonstrative Beispiel für den Einsatz von Mock-Objekten mit dazugehörigen UML Diagrammen zu sehen. +In diesem Projekt wurden vor allem Mock-Objekte eingesetzt. Sie wurden genutzt, um benötigte +Nebenklassen zu mocken. Nachfolgend sind zwei demonstrative Beispiel für den Einsatz von +Mock-Objekten mit dazugehörigen UML Diagrammen zu sehen. Beispiel aus: `TestGroceryList#testConstructorVarArgs` ```java @Test - public void testConstructorVarArgs() { +public void testConstructorVarArgs(){ // arrange - GroceryItem item1 = mock(GroceryItem.class); - GroceryItem item2 = mock(GroceryItem.class); + GroceryItem item1=mock(GroceryItem.class); + GroceryItem item2=mock(GroceryItem.class); // act - GroceryList list = new GroceryList(item1, item2); + GroceryList list=new GroceryList(item1,item2); // assert assertNotNull(list); - } + } ``` ![Fakes und Mocks Beispiel 1 UML](http://www.plantuml.com/plantuml/proxy?cache=no&src=https://raw.githubusercontent.com/ncryptedV1/AutoChef/docs/uml/fake-mock-1.iuml) _[TODO: Analyse und Begründung für Einsatz] - Beispiel aus: `TestMealPlan#setUp` ```java @BeforeEach - public void setUp() { - start = LocalDate.now(); - end = start.plusDays(5); - int totalMeals = start.until(end).getDays(); +public void setUp(){ + start=LocalDate.now(); + end=start.plusDays(5); + int totalMeals=start.until(end).getDays(); - List meals = new ArrayList<>(); - for (int i = 0; i < totalMeals; i++) { + List meals=new ArrayList<>(); + for(int i=0;i meals, LocalDate start, LocalDate end) { - int days = start.until(end).getDays(); - if (meals.size() != days) { - throw new IllegalArgumentException( - "Mahlzeiten-Plan spannt " + days + " Tage, es wurden allerdings nur " + meals.size() - + " Mahlzeiten übergeben"); + public MealPlan(List meals,LocalDate start,LocalDate end){ + int days=start.until(end).getDays(); + if(meals.size()!=days){ + throw new IllegalArgumentException( + "Mahlzeiten-Plan spannt "+days+" Tage, es wurden allerdings nur "+meals.size() + +" Mahlzeiten übergeben"); } - this.meals = meals; - this.start = start; - this.end = end; - } + this.meals=meals; + this.start=start; + this.end=end; + } - // ... +// ... - public int getDays() { +public int getDays(){ return start.until(getEnd()).getDays(); - } + } ``` nachher: ```java - public MealPlan(List meals, LocalDate start, LocalDate end) { - this.start = start; - this.end = end; - - int days = getDays(); - if (meals.size() != days) { - throw new IllegalArgumentException( - "Mahlzeiten-Plan spannt " + days + " Tage, es wurden allerdings nur " + meals.size() - + " Mahlzeiten übergeben"); + public MealPlan(List meals,LocalDate start,LocalDate end){ + this.start=start; + this.end=end; + + int days=getDays(); + if(meals.size()!=days){ + throw new IllegalArgumentException( + "Mahlzeiten-Plan spannt "+days+" Tage, es wurden allerdings nur "+meals.size() + +" Mahlzeiten übergeben"); } - this.meals = meals; - } + this.meals=meals; + } - // ... - - public int getDays() { +// ... + +public int getDays(){ return start.until(getEnd()).getDays(); - } + } ``` -Dieser Code Smell umfasst eine Code Duplication in der `MealPlan`-Klasse. Relevant ist hierbei vor allem Zeile 2. Hier wird die folgende Berechnung ausgeführt: +Dieser Code Smell umfasst eine Code Duplication in der `MealPlan`-Klasse. Relevant ist hierbei vor +allem Zeile 2. Hier wird die folgende Berechnung ausgeführt: ``` int days = start.until(end).getDays(); ``` -`days` gibt dabei die Anzahl an Tagen zwischen `start` udn `end` an. Was dabei aber auffällt, ist, dass diese Logik bereits zu anderen Zwecken in der `getDays`-Methode vorhanden ist. Diese Methode wird verwendet, um auch von außen Zugriff auf die Anzahl der Tage zu haben. Damit kann hier eine kleine Dopplung vorgefunden werden. Diese wurde nun, wie im zweiten Code-Beispiel ersichtlich, behoben. Dabei wurde die Zuweisung der Variable `this.start` und `this.end` nach oebn verschoben, sodass dann die Methode `getDays` aufgerufen werden kann. Dieser Methodenaufruf ermöglicht es jetzt, die Logik der Berechnung der Anzahl der Tage zu kapseln und somit die Wartbarkeit zu erhöhen. +`days` gibt dabei die Anzahl an Tagen zwischen `start` udn `end` an. Was dabei aber auffällt, ist, +dass diese Logik bereits zu anderen Zwecken in der `getDays`-Methode vorhanden ist. Diese Methode +wird verwendet, um auch von außen Zugriff auf die Anzahl der Tage zu haben. Damit kann hier eine +kleine Dopplung vorgefunden werden. Diese wurde nun, wie im zweiten Code-Beispiel ersichtlich, +behoben. Dabei wurde die Zuweisung der Variable `this.start` und `this.end` nach oebn verschoben, +sodass dann die Methode `getDays` aufgerufen werden kann. Dieser Methodenaufruf ermöglicht es jetzt, +die Logik der Berechnung der Anzahl der Tage zu kapseln und somit die Wartbarkeit zu erhöhen. #### Code Smell 2 + Code Smell 2: Large Method ([Commit](https://github.com/ncryptedV1/AutoChef/commit/990e2b31be1fb10314691630584319d48b3d0cd8#diff-fdae48ae8bf101ffc36c43d7bbad7c1da1bef6ba56429655006dac33210fe387)) -vorher: +vorher: ```java - public static void main(String[] args) { + public static void main(String[]args){ logger.info("Starting..."); // setup groceries - GroceryItem item1 = new GroceryItem(new Ingredient("Banane"), new Quantity(1), Unit.GRAM); - GroceryItem item2 = - new GroceryItem(new Ingredient("Pineapple"), new Quantity(0.2), Unit.KILOGRAM); - GroceryItem item3 = - new GroceryItem(new Ingredient("Orange juice"), new Quantity(0.1), Unit.LITER); - GroceryItem item4 = new GroceryItem(new Ingredient("Apple"), new Quantity(1), Unit.PIECE); - GroceryItem item5 = - new GroceryItem(new Ingredient("Nutella"), new Quantity(2), Unit.TABLESPOON); + GroceryItem item1=new GroceryItem(new Ingredient("Banane"),new Quantity(1),Unit.GRAM); + GroceryItem item2= + new GroceryItem(new Ingredient("Pineapple"),new Quantity(0.2),Unit.KILOGRAM); + GroceryItem item3= + new GroceryItem(new Ingredient("Orange juice"),new Quantity(0.1),Unit.LITER); + GroceryItem item4=new GroceryItem(new Ingredient("Apple"),new Quantity(1),Unit.PIECE); + GroceryItem item5= + new GroceryItem(new Ingredient("Nutella"),new Quantity(2),Unit.TABLESPOON); // setup recipe steps - RecipeStep recipeStep1 = - new RecipeStep(1, "Cut some banana, apple and pineapple as the basis for this salad.", - item1, item2, item3); - RecipeStep recipeStep2 = new RecipeStep(2, "Add orange juice to make it more juicy.", item4); - RecipeStep recipeStep3 = - new RecipeStep(3, "Add a bit of Nutella for making it look beautiful.", item5); + RecipeStep recipeStep1= + new RecipeStep(1,"Cut some banana, apple and pineapple as the basis for this salad.", + item1,item2,item3); + RecipeStep recipeStep2=new RecipeStep(2,"Add orange juice to make it more juicy.",item4); + RecipeStep recipeStep3= + new RecipeStep(3,"Add a bit of Nutella for making it look beautiful.",item5); // setup recipe for - Recipe recipe1 = new Recipe("Sugar-free fruit salad", recipeStep1, recipeStep2, recipeStep3); + Recipe recipe1=new Recipe("Sugar-free fruit salad",recipeStep1,recipeStep2,recipeStep3); // setup meal - Meal meal1 = new Meal(recipe1, 2); + Meal meal1=new Meal(recipe1,2); // setup meal plan - List mealList = Arrays.asList(meal1); - LocalDate startDate = LocalDate.of(2023, 2, 20); - LocalDate endDate = LocalDate.of(2023, 2, 26); - MealPlan mealPlan = new MealPlan(mealList, startDate, endDate); + List mealList=Arrays.asList(meal1); + LocalDate startDate=LocalDate.of(2023,2,20); + LocalDate endDate=LocalDate.of(2023,2,26); + MealPlan mealPlan=new MealPlan(mealList,startDate,endDate); logger.info(mealPlan.toString()); - } + } ``` -nachher: +nachher: ```java - public static void main(String[] args) { + public static void main(String[]args){ logger.info("Starting..."); // generate mock data - List groceryItems = MockService.generateGroceryItems(); - List recipeSteps = MockService.generateRecipeSteps(groceryItems); - Recipe recipe = MockService.generateRecipe(recipeSteps); - Meal meal = MockService.generateMeal(recipe); - List meals = Arrays.asList(meal); - MealPlan mealPlan = MockService.generateMealPlan(meals); + List groceryItems=MockService.generateGroceryItems(); + List recipeSteps=MockService.generateRecipeSteps(groceryItems); + Recipe recipe=MockService.generateRecipe(recipeSteps); + Meal meal=MockService.generateMeal(recipe); + List meals=Arrays.asList(meal); + MealPlan mealPlan=MockService.generateMealPlan(meals); logger.info(mealPlan.toString()); - } + } ``` ```java @@ -732,23 +946,23 @@ public class MockService { public static List generateGroceryItems() { GroceryItem item1 = new GroceryItem(new Ingredient("Banane"), new Quantity(1), Unit.GRAM); GroceryItem item2 = new GroceryItem(new Ingredient("Pineapple"), new Quantity(0.2), - Unit.KILOGRAM); + Unit.KILOGRAM); GroceryItem item3 = new GroceryItem(new Ingredient("Orange juice"), new Quantity(0.1), - Unit.LITER); + Unit.LITER); GroceryItem item4 = new GroceryItem(new Ingredient("Apple"), new Quantity(1), Unit.PIECE); GroceryItem item5 = new GroceryItem(new Ingredient("Nutella"), new Quantity(2), - Unit.TABLESPOON); + Unit.TABLESPOON); return Arrays.asList(item1, item2, item3, item4, item5); } public static List generateRecipeSteps(List groceryItems) { RecipeStep recipeStep1 = new RecipeStep(1, - "Cut some banana, apple and pineapple as the basis for this salad.", - getSample(groceryItems)); + "Cut some banana, apple and pineapple as the basis for this salad.", + getSample(groceryItems)); RecipeStep recipeStep2 = new RecipeStep(2, "Add orange juice to make it more juicy.", - getSample(groceryItems)); + getSample(groceryItems)); RecipeStep recipeStep3 = new RecipeStep(3, "Add a bit of Nutella for making it look beautiful.", - getSample(groceryItems)); + getSample(groceryItems)); return Arrays.asList(recipeStep1, recipeStep2, recipeStep3); } @@ -775,9 +989,15 @@ public class MockService { } ``` -Der zweite Code Smell befasst sich mit der Auslagerung der `MockService`-Klasse, die im aktuellen Stand nicht mehr vorhanden ist. In diesem Fall ist der Code Smell eine _Large Method_: die `main`-Methode. Sie umfasst die Erstellung einiger Objekte, damit die Anwendung mit Testdaten getestet werden konnte. Da es sich hier um vergleichsweise viele Objekte handelt, ist die Methode groß. +Der zweite Code Smell befasst sich mit der Auslagerung der `MockService`-Klasse, die im aktuellen +Stand nicht mehr vorhanden ist. In diesem Fall ist der Code Smell eine _Large Method_: die `main` +-Methode. Sie umfasst die Erstellung einiger Objekte, damit die Anwendung mit Testdaten getestet +werden konnte. Da es sich hier um vergleichsweise viele Objekte handelt, ist die Methode groß. -Um das zu beheben, wurde die vorhandene Logik extrahiert in eine `MockService`-Klasse. Diese Klasse ist nun dafür verantwortlich, Testobjekte und -daten zu generieren und über Methodenaufrufe zurückzugeben. Dabei sind mehrere Methoden im Mock-Service entstanden, was die Komplexität des Code-Smells zeigt. +Um das zu beheben, wurde die vorhandene Logik extrahiert in eine `MockService`-Klasse. Diese Klasse +ist nun dafür verantwortlich, Testobjekte und -daten zu generieren und über Methodenaufrufe +zurückzugeben. Dabei sind mehrere Methoden im Mock-Service entstanden, was die Komplexität des +Code-Smells zeigt. ### 2 Refactorings @@ -786,6 +1006,7 @@ Um das zu beheben, wurde die vorhandene Logik extrahiert in eine `MockService`-K [//]: # ([Commit](https://github.com/ncryptedV1/AutoChef/commit/8a66d9a6405c9ca4cf710490ae06eb810ea87978#diff-a914343e6af07030cd7b3b51d56fc5e0f541d6bd350ee0ef11324a9bc5aae66f)) #### Refactoring 1 + Refactoring 2: Extract Class (ConsoleOutputService) ([Commit](https://github.com/ncryptedV1/AutoChef/commit/d2251bcb4cc4053a1de24e20213a46034d3e0dbf)) @@ -797,21 +1018,40 @@ nachher: ![Refactoring Beispiel 1 Post UML](http://www.plantuml.com/plantuml/proxy?cache=no&src=https://raw.githubusercontent.com/ncryptedV1/AutoChef/docs/uml/refactoring-1-post.iuml) -Das in den UML-Diagrammen gezeigte Refactoring ist das _Extract Class_-Refactoring, das genutzt wurde um die `ConsoleOutputService`-Klasse zu erschaffen. Der Commit und das erste UML-Diagramm zeigen, dass die `AutoChef`-Klasse mit der `main`-Methode zunächst auch für die Konsolenausgabe verantwortlich war. Um das aber sauber voneinander zu trennen, und die Komplexität der `main`-Methode so einfach wie möglich zu halten, wurde die Logik der Consolenausgabe in eine separate Klasse ausgelagert. Die neu geschaffene Klasse `ConsoleOutputService` umfasst neben der Logik, die vorher in der `main`-Methode existierte, auch noch weitere Funktionalitäten. Das zeigt auch das zweite UML-Diagramm. Zu sehen ist hier die `ConsoleOutputService`-Klasse mit ihren verschiedenen Methoden zur Konsolenausgabe. Mit diesem Refactoring konnte eine saubere Trennung der Verantwortlichkeiten, sowie eine kleinere `main`-Klasse erreicht werden. +Das in den UML-Diagrammen gezeigte Refactoring ist das _Extract Class_-Refactoring, das genutzt +wurde um die `ConsoleOutputService`-Klasse zu erschaffen. Der Commit und das erste UML-Diagramm +zeigen, dass die `AutoChef`-Klasse mit der `main`-Methode zunächst auch für die Konsolenausgabe +verantwortlich war. Um das aber sauber voneinander zu trennen, und die Komplexität der `main` +-Methode so einfach wie möglich zu halten, wurde die Logik der Consolenausgabe in eine separate +Klasse ausgelagert. Die neu geschaffene Klasse `ConsoleOutputService` umfasst neben der Logik, die +vorher in der `main`-Methode existierte, auch noch weitere Funktionalitäten. Das zeigt auch das +zweite UML-Diagramm. Zu sehen ist hier die `ConsoleOutputService`-Klasse mit ihren verschiedenen +Methoden zur Konsolenausgabe. Mit diesem Refactoring konnte eine saubere Trennung der +Verantwortlichkeiten, sowie eine kleinere `main`-Klasse erreicht werden. #### Refactoring 2 + Refactoring 2: Rename Method ([Commit](https://github.com/ncryptedV1/AutoChef/commit/19fb6fc9c8bdee1a0e1d31f31c431a38eaf68ae2#diff-a621a84bbbaeb91c8fc33118865eb8c87e0bf202d7dfe7b5bf8d20a786a51239)) -vorher: +vorher: ![Refactoring Beispiel 2 Pre UML](http://www.plantuml.com/plantuml/proxy?cache=no&src=https://raw.githubusercontent.com/ncryptedV1/AutoChef/docs/uml/refactoring-2-pre.iuml) -nachher: +nachher: ![Refactoring Beispiel 2 Post UML](http://www.plantuml.com/plantuml/proxy?cache=no&src=https://raw.githubusercontent.com/ncryptedV1/AutoChef/docs/uml/refactoring-2-post.iuml) -Dieses Refactoring umfasst das Umbenennen einer Methode (_Rename Method_) um mehr Klarheit im Quellcode zu schaffen. Es handelt sich dabei um die Methode `getIngredients` respektive `getGroceryList` (nach dem Refactoring). Diese kleine Änderung im Code ist auch in den UML-Diagrammen ersichtlich. Der Commit zeigt ebenso, dass die Größe der Änderung vergleichsweise klein ist. Die `getIngredients`-Methode wurde dabei umbenannt in `getGroceryList`. Für den Entwickler macht das vor allem deutlich, dass das Rückgabeergebnis eine `GroceryList` anstelle einer `List` ist. Das ist besonders wichtig, da es sich hier um verschiedene Objekte mit unterschiedlichen Eigenschaften und Funktionalitäten handelt. So können vermeintliche Fehler in der Benutzung der Methode durch mehr Klarheit vermieden werden, die sonst durch Unachtsamkeit aufgetreten wären. +Dieses Refactoring umfasst das Umbenennen einer Methode (_Rename Method_) um mehr Klarheit im +Quellcode zu schaffen. Es handelt sich dabei um die Methode `getIngredients` +respektive `getGroceryList` (nach dem Refactoring). Diese kleine Änderung im Code ist auch in den +UML-Diagrammen ersichtlich. Der Commit zeigt ebenso, dass die Größe der Änderung vergleichsweise +klein ist. Die `getIngredients`-Methode wurde dabei umbenannt in `getGroceryList`. Für den +Entwickler macht das vor allem deutlich, dass das Rückgabeergebnis eine `GroceryList` anstelle +einer `List` ist. Das ist besonders wichtig, da es sich hier um verschiedene Objekte mit +unterschiedlichen Eigenschaften und Funktionalitäten handelt. So können vermeintliche Fehler in der +Benutzung der Methode durch mehr Klarheit vermieden werden, die sonst durch Unachtsamkeit +aufgetreten wären. ## 8. Entwurfsmuster diff --git a/uml/dependency-rule-neg.iuml b/uml/dependency-rule-neg.iuml index e69de29..f5eab9b 100644 --- a/uml/dependency-rule-neg.iuml +++ b/uml/dependency-rule-neg.iuml @@ -0,0 +1,31 @@ +@startuml + +left to right direction + +package "Infrastructure" { + class WebsiteFetcher { + + getWebsiteBody(urlString: String) + } + + package "Application" { + class ChefkochRecipeFetcher { + + getRecipe(url: String) + - extractRecipeName(content: String) + - extractIngredients(content: String) + - extractRecipeSteps(content: String) + } + + class DialogService { + - recipeRepository: RecipeRepository + + DialogService(recipeRepository: RecipeRepository) + } + + package "Domain" { + } + } +} + +DialogService -> ChefkochRecipeFetcher +ChefkochRecipeFetcher -> WebsiteFetcher + +@enduml \ No newline at end of file diff --git a/uml/dependency-rule-pos.iuml b/uml/dependency-rule-pos.iuml index 495e34a..1c95f75 100644 --- a/uml/dependency-rule-pos.iuml +++ b/uml/dependency-rule-pos.iuml @@ -2,33 +2,39 @@ left to right direction -class AutoChef { - + main(args: String[]) -} +package "Infrastructure" { + class AutoChef { + + main(args: String[]) + } -class DialogService { - - recipeRepository: RecipeRepository - + DialogService(recipeRepository: RecipeRepository) -} + class RecipeFileRepository { + - recipesFolder: File + + RecipeFileRepository(recipesFolder: File) + + saveRecipe(recipe: Recipe) + + deleteRecipe(recipe: Recipe): boolean + + getRecipes(): List + + getRecipe(id: String): Recipe + } -interface RecipeRepository { - + saveRecipe(recipe: Recipe) - + deleteRecipe(recipe: Recipe): boolean - + getRecipes(): List - + getRecipe(id: String): Recipe -} + package "Application" { + class DialogService { + - recipeRepository: RecipeRepository + + DialogService(recipeRepository: RecipeRepository) + } -class RecipeFileRepository { - - recipesFolder: File - + RecipeFileRepository(recipesFolder: File) - + saveRecipe(recipe: Recipe) - + deleteRecipe(recipe: Recipe): boolean - + getRecipes(): List - + getRecipe(id: String): Recipe + package "Domain" { + interface RecipeRepository { + + saveRecipe(recipe: Recipe) + + deleteRecipe(recipe: Recipe): boolean + + getRecipes(): List + + getRecipe(id: String): Recipe + } + } + } } -AutoChef -> RecipeFileRepository AutoChef -> DialogService +AutoChef -> RecipeFileRepository DialogService -> RecipeRepository RecipeRepository <|.. RecipeFileRepository From 9e588b31b3a8084c05422fe116a12f3975c0a226 Mon Sep 17 00:00:00 2001 From: cuvar Date: Wed, 5 Apr 2023 14:54:23 +0200 Subject: [PATCH 17/39] docs: fix english terms --- README.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 63714f6..509451b 100644 --- a/README.md +++ b/README.md @@ -83,28 +83,28 @@ _[(1 Klasse, die die Dependency Rule einhält und eine Klasse, die die Dependenc ![Schicht 1 UML](http://www.plantuml.com/plantuml/proxy?cache=no&src=https://raw.githubusercontent.com/ncryptedV1/AutoChef/docs/uml/layer-1.iuml) -Die `Recipe`-Klasse ist eine Entity im Sinne der Clean-Architecture, da sie die Entität eines Rezeptes abbildet. Ein Rezept besteht aus folgenden Attributen: +Die `Recipe`-Klasse ist eine Entity im Sinne der Clean-Architecture, da sie die Entity eines Rezeptes abbildet. Ein Rezept besteht aus folgenden Attributen: - `name: String`: Name des Rezeptes - `groceryList: GroceryList` : Liste an Zutaten, die für das Rezept benötigt werden - `recipeSteps: List`: Liste an Zubereitungsschritten, die im Laufe des Rezeptes abgearbeitet werden müssen Ein Rezept wird eineindeutig über eine ID indetifiziert. Die ID umfasst den Namen in Kleinschrift. Außerdem existieren für die Attribute und die ID jeweils Getter-Methoden und ein Konstruktor. -Damit liegt die Aufgabe der `Recipe`-Entität darin, ein Rezept semantisch im Code zu repräsentieren. Da das Konzept eines Rezept essenziell für die Domäne von Essensplänen ist, wurde es als Teil des Kernes der Anwendung aufgenommen. `Recipe` ist deshalb Teil der Schicht "Domain Code", da der Domänencode ebenjene Entities bzw den Kern der Anwendung enthalten sollte. Außerdem ändert sich die Modellierung eines Rezeptes selten, was ebenso dafür spricht, es in die Schicht "Domain Code" einzuordnen. +Damit liegt die Aufgabe der `Recipe`-Entity darin, ein Rezept semantisch im Code zu repräsentieren. Da das Konzept eines Rezept essenziell für die Domäne von Essensplänen ist, wurde es als Teil des Kernes der Anwendung aufgenommen. `Recipe` ist deshalb Teil der Schicht "Domain Code", da der Domänencode ebenjene Entities bzw den Kern der Anwendung enthalten sollte. Außerdem ändert sich die Modellierung eines Rezeptes selten, was ebenso dafür spricht, es in die Schicht "Domain Code" einzuordnen. -#### Schicht: Application Code +#### Schicht: Application-Code - gewählte Klasse(n): `DialogService` mit `DialogState` ![Schicht 2 UML](http://www.plantuml.com/plantuml/proxy?cache=no&src=https://raw.githubusercontent.com/ncryptedV1/AutoChef/docs/uml/layer-2.iuml) -Die Schicht des Applications Codes umfasst mehrere Klassen. Dabei soll die Klasse `DialogService` im Fokus stehen. +Die Schicht des Application-Codes umfasst mehrere Klassen. Dabei soll die Klasse `DialogService` im Fokus stehen. Im Kern ist der Dialog-Service für die Ablauflogik der Anwendung verantwortlich. Er verwaltet die Datenpersistenz über die Klassen `RecipeRepository` und `RecipeFileRepository`, ist aber gleichzeitig auch für die Nutzung von Benutzereingaben über die Klassen `ConsoleInputReader`, `ConsoleInputParser` und `ConsoleOutputService` verantwortlich. Damit ist er die Schnittstelle zwischen den einzelnen Verantwortungsbereichen der Anwendung. Im Allgemeinen startet er den Dialog mit dem Benutzer, organisiert die Generierung von Essensplänen und gibt dem Benutzer die Möglichkeit Rezepte hinzuzufügen. In diesem Sinne nimmt er die Rolle eines "Controllers" ein. Für andere Anwendungen wie etwa eine Web-Anwendung würde eine andere Funktionalität erwartet werden. Der Dialog-Service ist speziell für den Anwendungsfall einer CLI-Anwendung definiert und nutzbar. -Ebenso bedeutet das, dass Änderungen an dem DialogService keinen Einfluss auf den Domänen-Code haben. All diese Aspekte begründen, warum der Dialog-Service im Application Code angesidelt ist. +Ebenso bedeutet das, dass Änderungen an dem DialogService keinen Einfluss auf den Domänen-Code haben. All diese Aspekte begründen, warum der Dialog-Service im Application-Code angesidelt ist. ## 3. SOLID @@ -552,7 +552,7 @@ zugehörige Klasse(n): `Recipe` ![Entity Beispiel UML](http://www.plantuml.com/plantuml/proxy?cache=no&src=https://raw.githubusercontent.com/ncryptedV1/AutoChef/docs/uml/entity.iuml) -Die `Recipe` Entity beschreibt ein semantisches Rezept. Wie das UML-Diagram zeigt, besteht ein Rezept aus einem Name, einer Liste von Zutaten (GroceryList) und einer Liste von Schritten zur Zubereitung (RecipeStep). Ein Rezept wird eineindeutig über eine ID indetifiziert. In diesem Fall besteht die ID aus dem Namen in Kleinschrift. Haben also zwei Rezepte den selben Namen, werden sie als gleich angesehen. Weiterhin hat die `Recipe` Klasse mehrere Getter-Methoden für die einzelnen Attribute und die ID als auch einen Konstruktor. +Die `Recipe`-Entity beschreibt ein semantisches Rezept. Wie das UML-Diagram zeigt, besteht ein Rezept aus einem Name, einer Liste von Zutaten (GroceryList) und einer Liste von Schritten zur Zubereitung (RecipeStep). Ein Rezept wird eineindeutig über eine ID indetifiziert. In diesem Fall besteht die ID aus dem Namen in Kleinschrift. Haben also zwei Rezepte den selben Namen, werden sie als gleich angesehen. Weiterhin hat die `Recipe` Klasse mehrere Getter-Methoden für die einzelnen Attribute und die ID als auch einen Konstruktor. Bei der Erstellung einer `Recipe`-Instanz mittels des Konstruktors wird die Richtigkeit der Attribute überprüft: - Der Name muss mindestens ein Zeichen abgesehen von White-Space beinhalten. From 61d98931b02dfec559e6469c7b5cf692563781fc Mon Sep 17 00:00:00 2001 From: ncryptedV1 <15632467+ncryptedV1@users.noreply.github.com> Date: Wed, 5 Apr 2023 15:11:32 +0200 Subject: [PATCH 18/39] add: table of contents and numbering for headings --- README.md | 102 ++++++++++++------ ...neg.iuml => dependency-inversion-neg.iuml} | 0 ...pos.iuml => dependency-inversion-pos.iuml} | 0 3 files changed, 68 insertions(+), 34 deletions(-) rename uml/{liskov-neg.iuml => dependency-inversion-neg.iuml} (100%) rename uml/{liskov-pos.iuml => dependency-inversion-pos.iuml} (100%) diff --git a/README.md b/README.md index 4e89dd7..326cd41 100644 --- a/README.md +++ b/README.md @@ -33,9 +33,46 @@ Abgabedatum: 28. Mai 2023 - verlangte Positiv-Beispiele müssen gebracht werden - Code-Beispiel = Code in das Dokument kopieren +## Inhaltsverzeichnis +- [Einführung](#1-einführung) + - [Übersicht über die Applikation](#11-übersicht-über-die-applikation) + - [Wie startet man die Applikation?](#12-wie-startet-man-die-applikation) + - [Wie testet man die Applikation?](#13-wie-testet-man-die-applikation) +- [Clean Architecture](#2-clean-architecture) + - [Was ist Clean Architecture?](#21-was-ist-clean-architecture) + - [Analyse der Dependency Rule](#22-analyse-der-dependency-rule) + - [Analyse der Schichten](#23-analyse-der-schichten) +- [SOLID](#3-solid) + - [Analyse Single-Responsibility-Principle (SRP)](#31-analyse-single-responsibility-principle--srp-) + - [Analyse Open-Closed-Principle (OCP)](#32-analyse-open-closed-principle--ocp-) + - [Analyse Dependency-Inversion-Principle (DIP)](#33-analyse-dependency-inversion-principle--dip-) +- [Weitere Prinzipien](#4-weitere-prinzipien) + - [Analyse GRASP: Geringe Kopplung](#41-analyse-grasp--geringe-kopplung) + - [Analyse GRASP: Hohe Kohäsion](#42-analyse-grasp--hohe-kohäsion) + - [Don't repeat yourself (DRY)](#43-dont-repeat-yourself--dry-) +- [Unit Tests](#5-unit-tests) + - [10 Unit Tests](#51-10-unit-tests) + - [ATRIP: Automatic](#52-atrip--automatic) + - [ATRIP: Thorough](#53-atrip--thorough) + - [ATRIP: Professional](#54-atrip--professional) + - [Code Coverage](#55-code-coverage) + - [Fakes und Mocks](#56-fakes-und-mocks) +- [Domain Driven Design](#6-domain-driven-design) + - [Ubiquitous Language](#61-ubiquitous-language) + - [Entities](#62-entities) + - [Value Objects](#63-value-objects) + - [Repositories](#64-repositories) + - [Aggregates](#65-aggregates) +- [Refactoring](#7-refactoring) + - [Code Smells](#71-code-smells) + - [2 Refactorings](#72-2-refactorings) +- [Entwurfsmuster](#8-entwurfsmuster) + - [Entwurfsmuster: Name](#81-entwurfsmuster--name) + - [Entwurfsmuster: Name](#82-entwurfsmuster--name) + ## 1. Einführung -### Übersicht über die Applikation +### 1.1. Übersicht über die Applikation AutoChef ist eine Anwendung zur einfachen und effizienten Verwaltung und Erstellung von Essensplänen, sowie dazugehöriger Einkaufslisten. Zugrunde liegt dabei eine Datenbank an Rezepten. @@ -51,7 +88,7 @@ Einkaufliste für den Wocheneinkauf zu erstellen. _[Was macht die Applikation, Wie funktioniert sie? Welches Problem löst sie/welchen Zweck hat sie?]_ -### Wie startet man die Applikation? +### 1.2. Wie startet man die Applikation? Bei AutoChef handelt es sich um eine CLI-Anwendung, geschrieben in Java 19. Zum Starten wird daher lediglich ein Desktop-Rechner mit **Java 19 aufwärts** benötigt. Die Anwendung kann dann über ein @@ -59,7 +96,7 @@ Konsolenfenster mit dem Befehl `java -jar AutoChef.jar` gestartet werden. _[Wie startet man die Applikation? Welche Voraussetzungen werden benötigt? Schritt-für-Schritt-Anleitung]_ -### Wie testet man die Applikation? +### 1.3. Wie testet man die Applikation? Nach dem Start der Anwendung wird der Nutzer durch einen intuitiven Dialog-Prozess begrüßt und geleitet. Der Nutzer interagiert dabei mit der Anwendung mittels des Konsolenfensters. Über dieses @@ -81,7 +118,7 @@ _[Wie testet man die Applikation? Welche Voraussetzungen werden benötigt? Schri ## 2. Clean Architecture -### Was ist Clean Architecture? +### 2.1. Was ist Clean Architecture? Clean Architecture ist eine Architektur- und Designphilosophie, die darauf abzielt, komplexe Softwaresysteme in leicht verständliche, wartbare und erweiterbare Komponenten zu unterteilen. Es @@ -106,9 +143,7 @@ Zusammenfassend lässt sich sagen, dass Clean Architecture eine Methode ist, um Softwaresysteme in einfachere, leichter zu wartende Komponenten aufzuteilen, indem eine klare Abhängigkeitshierarchie zwischen den Komponenten eingeführt wird. -_[allgemeine Beschreibung der Clean Architecture in eigenen Worten]_ - -### Analyse der Dependency Rule +### 2.2. Analyse der Dependency Rule #### Positiv-Beispiel: Dependency Rule @@ -134,7 +169,7 @@ Konfigurationsdetails. Aus diesem Grund ist sie der Infrastrukturschicht zuzuord Dadurch verletzt die `ChefkochRecipeFetcher`-Klasse die Dependency Rule, da eine Abhängigkeit von der Applikationsschicht in die Infrastrukturschicht besteht. Die `ChefkochRecipeFetcher`-Klasse sollte stattdessen von einem Interface abhängen, das in der Domänenschicht definiert ist und von einer Implementierung in einer äußeren Schicht bereitgestellt wird, um die Abhängigkeiten korrekt von außen nach innen zu richten. Von der Klasse abhängig ist lediglich der `DialogService`, welcher sich auf selbiger Schicht befindet. -### Analyse der Schichten +### 2.3. Analyse der Schichten #### Schicht: Domain Code @@ -186,7 +221,7 @@ haben. All diese Aspekte begründen, warum der Dialog-Service im Application Cod ## 3. SOLID -### Analyse Single-Responsibility-Principle (SRP) +### 3.1. Analyse Single-Responsibility-Principle (SRP) #### Positiv-Beispiel @@ -235,7 +270,7 @@ Dabei könnte `startMealPlanGeneration` mit einem `GenerationService` interagier Der `GenerationService` wäre dann für die eigentliche Generierung des Essensplans verantwortlich, während die `startMealPlanGeneration` lediglich eine verwaltende Rolle einnehmen würde. -### Analyse Open-Closed-Principle (OCP) +### 3.2. Analyse Open-Closed-Principle (OCP) #### Positiv-Beispiel @@ -292,22 +327,21 @@ des Interfaces. Gleichzeitig müssten keine aufwändigen Code-Modifikationen an oder dem Interface vorgenommen werden, damit beide Module miteinander interagieren können. Dadurch wäre das OCP erfüllt. -### Analyse Liskov-Substitution- (LSP), Interface-Segreggation- (ISP), Dependency-Inversion-Principle (DIP) +### 3.3. Analyse Dependency-Inversion-Principle (DIP) -_[jeweils eine Klasse als positives und negatives Beispiel für entweder LSP oder ISP oder DIP); jeweils UML der Klasse und Begründung, warum man hier das Prinzip erfüllt/nicht erfüllt wird]_ \ -_[Anm.: es darf nur ein Prinzip ausgewählt werden; es darf NICHT z.B. ein positives Beispiel für LSP und ein negatives Beispiel für ISP genommen werden]_ +Da zur Einhaltung der Dependency Rule der Clean Architecture-Methode oft das DIP genutzt wird, können hier selbige Beispiel wie aus [Kapitel 2.2](#22-analyse-der-dependency-rule) genutzt werden. #### Positiv-Beispiel -![Liskov positives Beispiel UML](http://www.plantuml.com/plantuml/proxy?cache=no&src=https://raw.githubusercontent.com/ncryptedV1/AutoChef/docs/uml/liskov-pos.iuml) +![Dependency-Inversion-Principle positives Beispiel UML](http://www.plantuml.com/plantuml/proxy?cache=no&src=https://raw.githubusercontent.com/ncryptedV1/AutoChef/docs/uml/dependency-inversion-pos.iuml) #### Negativ-Beispiel -![Liskov negatives Beispiel UML](http://www.plantuml.com/plantuml/proxy?cache=no&src=https://raw.githubusercontent.com/ncryptedV1/AutoChef/docs/uml/liskov-neg.iuml) +![Dependency-Inversion-Principle negatives Beispiel UML](http://www.plantuml.com/plantuml/proxy?cache=no&src=https://raw.githubusercontent.com/ncryptedV1/AutoChef/docs/uml/dependency-inversion-neg.iuml) ## 4. Weitere Prinzipien -### Analyse GRASP: Geringe Kopplung +### 4.1. Analyse GRASP: Geringe Kopplung _[jeweils eine bis jetzt noch nicht behandelte Klasse als positives und negatives Beispiel geringer Kopplung; jeweils UML Diagramm mit zusammenspielenden Klassen, Aufgabenbeschreibung und Begründung für die Umsetzung der geringen Kopplung bzw. Beschreibung, wie die Kopplung aufgelöst werden kann]_ @@ -319,12 +353,12 @@ _[jeweils eine bis jetzt noch nicht behandelte Klasse als positives und negative ![Kopplung negatives Beispiel UML](http://www.plantuml.com/plantuml/proxy?cache=no&src=https://raw.githubusercontent.com/ncryptedV1/AutoChef/docs/uml/coupling-neg.iuml) -### Analyse GRASP: Hohe Kohäsion +### 4.2. Analyse GRASP: Hohe Kohäsion -\*[eine Klasse als positives Beispiel hoher Kohäsion; UML Diagramm und Begründung, warum die Kohäsion hoch ist] +_[eine Klasse als positives Beispiel hoher Kohäsion; UML Diagramm und Begründung, warum die Kohäsion hoch ist]_ ![Kohäsion Beispiel UML](http://www.plantuml.com/plantuml/proxy?cache=no&src=https://raw.githubusercontent.com/ncryptedV1/AutoChef/docs/uml/cohesion.iuml) -### Don’t Repeat Yourself (DRY)\* +### 4.3. Don’t Repeat Yourself (DRY) Commit-SHA: d89dcb3 ([Link](https://github.com/ncryptedV1/AutoChef/commit/d89dcb38a0e45759dd3e689593870d1e9ed0da96)) @@ -392,7 +426,7 @@ genutzt wird: in der `getDays` Methode. Dadurch können Fehler vermieden werden, ## 5. Unit Tests -### 10 Unit Tests +### 5.1. 10 Unit Tests | Unit Test | Beschreibung | |---------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------| @@ -407,7 +441,7 @@ genutzt wird: in der `getDays` Methode. Dadurch können Fehler vermieden werden, | _TestMeal#testGetGroceryList_ | testet, ob die `getGroceryList` Methode der `Meal` Klasse eine korrekt aggregierte Zutatenliste zurückgibt | | _TestGroceryList#testAddItem_ | testet, ob die `addItem` Methode der `GroceryList` Klasse korrekt eine Itme zur GroceryList hinzufügt | -### ATRIP: Automatic +### 5.2. ATRIP: Automatic Die Tests wurden mittels Testautomatisierung realisiert. Dabei wurde JUnit 5 verwendet, um automatisierte Tests zu schreiben. Über die IDE IntelliJ IDEA können die Test simpel über einen @@ -415,7 +449,7 @@ Knopfdruck ausgeführt werden. Im Anschluss laufen alle Tests automatisch. Das E Testautomatisierung zeigt, ob ein individueller Test erfolgreich (bestanden) oder nicht erfolgreich (fehlgeschlagen) war. -### ATRIP: Thorough +### 5.3. ATRIP: Thorough #### positives Beispiel @@ -557,7 +591,7 @@ Rezepten werden im Anschluss an eine weitere Methode weitergegeben. Da die Tests für diese Methode vollständig fehlen, werden dementsprechend auch alle Pfade nicht getestet. Dementsprechend kann nicht herausgefunden werden, wo sich logische Fehler befinden. -### ATRIP: Professional +### 5.4. ATRIP: Professional #### positives Beispiel @@ -614,7 +648,7 @@ nicht getestet werden. Des Weiteren enthält diese Methode keine komplexe Logik, erfordern würde. Es handelt sich hier um einen Test, "der nur wegen des Tests geschrieben wurde". Außerdem ist der Dokumentationswert der Methode nicht vorhanden. -### Code Coverage +### 5.5. Code Coverage Die folgende Tabelle zeigt die summierten Werte der verschiedenen Arten von Testabdeckung des Projektes. Eine aufgeschlüsselte Version der Testabdeckung ist im folgenden Bild zu sehen. Die @@ -646,7 +680,7 @@ Unabhängig dessen begründet sich die Code-Coverage wie folgt: - Line-Coverage: Die Line-Coverage ist durch die Method-Coverage bedingt und ist deshalb vor allem aus demselbigen Grund niedriger. -### Fakes und Mocks +### 5.6. Fakes und Mocks In diesem Projekt wurden vor allem Mock-Objekte eingesetzt. Sie wurden genutzt, um benötigte Nebenklassen zu mocken. Nachfolgend sind zwei demonstrative Beispiel für den Einsatz von @@ -699,7 +733,7 @@ _[Analyse und Begründung des Einsatzes von 2 Fake/Mock-Objekten; zusätzlich je ## 6. Domain Driven Design -### Ubiquitous Language +### 6.1. Ubiquitous Language | Bezeichnung | Bedeutung | Begründung | |--------------|-------------------------------|-------------------------------------------------------------------------------------------------------------------------------------| @@ -708,7 +742,7 @@ _[Analyse und Begründung des Einsatzes von 2 Fake/Mock-Objekten; zusätzlich je | Grocery list | Lebensmittelliste | Für Gerichte werden eine Menge von Lebensmittel benötigt. Die Lebensmittelliste fasst all diese zusammen. | | Meal plan | Essensplan | Ein Essensplan beschreibt eine Sammlung von Gerichten für einen bestimmten Zeitraum. | -### Entities +### 6.2. Entities zugehörige Klasse(n): `Recipe` @@ -732,7 +766,7 @@ Der Einsatz dieser Entity begründet sich dadurch, dass es notwendig war, ein Re können. Rezepte werden gespeichert und haben somit einen Lebenszyklus. Das erzwingt laut Domain Driven Design Richtlinien die Erstellung einer Entity. -### Value Objects +### 6.3. Value Objects zugehörige Klasse(n): `Ingredient` @@ -752,7 +786,7 @@ lediglich zur Repräsentation von Informationen. Aus diesem Grund ist es auch ni Informationen einer `Ingredient`-Instanz anzupassen - sie sind konstant. All diese Punkte begründen, warum sich hier für ein Value Object anstelle einer Entity oder Ähnlichem entschieden wurde. -### Repositories +### 6.4. Repositories zugehörige Klasse(n): `RecipeFileRepository` @@ -780,7 +814,7 @@ Repository eingesetzt. Mit Nutzung des Interfaces, kann sichergestellt werden, d getrennt bleiben. Zusätzlich ermöglicht der Einsatz eines Repositories, Veränderungen an der Persistenzverwaltung vorzunehmen, ob auf die Domänenlogik eingreifen zu müsssen. -### Aggregates +### 6.5. Aggregates zugehörige Klasse(n): `Meal` @@ -807,7 +841,7 @@ nicht im Umfang der Anwendung inbegriffen ist, ist ein Transfer zu einer Entity ## 7. Refactoring -### Code Smells +### 7.1. Code Smells #### Code Smell 1 @@ -999,7 +1033,7 @@ ist nun dafür verantwortlich, Testobjekte und -daten zu generieren und über Me zurückzugeben. Dabei sind mehrere Methoden im Mock-Service entstanden, was die Komplexität des Code-Smells zeigt. -### 2 Refactorings +### 7.2. 2 Refactorings [//]: # (Refactoring: Extract Method) @@ -1057,10 +1091,10 @@ aufgetreten wären. _[2 unterschiedliche Entwurfsmuster aus der Vorlesung (oder nach Absprache auch andere) jeweils sinnvoll einsetzen, begründen und UML-Diagramm]_ -#### Entwurfsmuster: [Name] +#### 8.1. Entwurfsmuster: [Name] ![Entwurfstmuster 1 Beispiel UML](http://www.plantuml.com/plantuml/proxy?cache=no&src=https://raw.githubusercontent.com/ncryptedV1/AutoChef/docs/uml/design-pattern-1.iuml) -#### Entwurfsmuster: [Name] +#### 8.2. Entwurfsmuster: [Name] ![Entwurfstmuster 2 Beispiel UML](http://www.plantuml.com/plantuml/proxy?cache=no&src=https://raw.githubusercontent.com/ncryptedV1/AutoChef/docs/uml/design-pattern-1.iuml) diff --git a/uml/liskov-neg.iuml b/uml/dependency-inversion-neg.iuml similarity index 100% rename from uml/liskov-neg.iuml rename to uml/dependency-inversion-neg.iuml diff --git a/uml/liskov-pos.iuml b/uml/dependency-inversion-pos.iuml similarity index 100% rename from uml/liskov-pos.iuml rename to uml/dependency-inversion-pos.iuml From c6acda13b9e51410262e6ef2af67512f0208bc38 Mon Sep 17 00:00:00 2001 From: ncryptedV1 <15632467+ncryptedV1@users.noreply.github.com> Date: Wed, 5 Apr 2023 15:16:29 +0200 Subject: [PATCH 19/39] add: dependency inversion documentation --- README.md | 8 +++++++ uml/dependency-inversion-neg.iuml | 25 ++++++++++++++++++++++ uml/dependency-inversion-pos.iuml | 35 +++++++++++++++++++++++++++++++ 3 files changed, 68 insertions(+) diff --git a/README.md b/README.md index 326cd41..1a89ea9 100644 --- a/README.md +++ b/README.md @@ -333,12 +333,20 @@ Da zur Einhaltung der Dependency Rule der Clean Architecture-Methode oft das DIP #### Positiv-Beispiel +- gewählte Klasse(n): `DialogService` + ![Dependency-Inversion-Principle positives Beispiel UML](http://www.plantuml.com/plantuml/proxy?cache=no&src=https://raw.githubusercontent.com/ncryptedV1/AutoChef/docs/uml/dependency-inversion-pos.iuml) +Wie im obigen UML ersichtlich, wurde das DIP im `DialogService` eingehalten. Die Klasse `DialogService` hängt von einer Abstraktion (dem Interface `RecipeRepository`) ab und nicht von einer konkreten Implementierung (der Klasse `RecipeFileRepository`). + #### Negativ-Beispiel +- gewählte Klasse(n): `ChefkochRecipeFetcher` + ![Dependency-Inversion-Principle negatives Beispiel UML](http://www.plantuml.com/plantuml/proxy?cache=no&src=https://raw.githubusercontent.com/ncryptedV1/AutoChef/docs/uml/dependency-inversion-neg.iuml) +Das DIP wird in der Klasse `ChefkochRecipeFetcher` verletzt. Die Klasse `ChefkochRecipeFetcher` verwendet direkt die Klasse `WebsiteFetcher`, um den Inhalt einer Webseite abzurufen. Hier wäre es besser, ein Interface für das Abrufen von Webinhalten zu erstellen und dieses Interface als Abstraktion zu verwenden. + ## 4. Weitere Prinzipien ### 4.1. Analyse GRASP: Geringe Kopplung diff --git a/uml/dependency-inversion-neg.iuml b/uml/dependency-inversion-neg.iuml index e69de29..fa2f08a 100644 --- a/uml/dependency-inversion-neg.iuml +++ b/uml/dependency-inversion-neg.iuml @@ -0,0 +1,25 @@ +@startuml + +left to right direction + +package "Infrastructure" { + class WebsiteFetcher { + + getWebsiteBody(urlString: String) + } + + package "Application" { + class ChefkochRecipeFetcher { + + getRecipe(url: String) + - extractRecipeName(content: String) + - extractIngredients(content: String) + - extractRecipeSteps(content: String) + } + + package "Domain" { + } + } +} + +ChefkochRecipeFetcher -> WebsiteFetcher + +@enduml \ No newline at end of file diff --git a/uml/dependency-inversion-pos.iuml b/uml/dependency-inversion-pos.iuml index e69de29..409def7 100644 --- a/uml/dependency-inversion-pos.iuml +++ b/uml/dependency-inversion-pos.iuml @@ -0,0 +1,35 @@ +@startuml + +left to right direction + +package "Infrastructure" { + class RecipeFileRepository { + - recipesFolder: File + + RecipeFileRepository(recipesFolder: File) + + saveRecipe(recipe: Recipe) + + deleteRecipe(recipe: Recipe): boolean + + getRecipes(): List + + getRecipe(id: String): Recipe + } + + package "Application" { + class DialogService { + - recipeRepository: RecipeRepository + + DialogService(recipeRepository: RecipeRepository) + } + + package "Domain" { + interface RecipeRepository { + + saveRecipe(recipe: Recipe) + + deleteRecipe(recipe: Recipe): boolean + + getRecipes(): List + + getRecipe(id: String): Recipe + } + } + } +} + +DialogService -> RecipeRepository +RecipeRepository <|.. RecipeFileRepository + +@enduml \ No newline at end of file From 7b89cb07797ef8ecc9a003513315d5894927a986 Mon Sep 17 00:00:00 2001 From: ncryptedV1 <15632467+ncryptedV1@users.noreply.github.com> Date: Wed, 5 Apr 2023 15:53:20 +0200 Subject: [PATCH 20/39] add: GRASP - cohesion --- README.md | 6 +++++- uml/cohesion.iuml | 27 +++++++++++++++++++++++++++ 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 1a89ea9..5c8f6d4 100644 --- a/README.md +++ b/README.md @@ -363,9 +363,13 @@ _[jeweils eine bis jetzt noch nicht behandelte Klasse als positives und negative ### 4.2. Analyse GRASP: Hohe Kohäsion -_[eine Klasse als positives Beispiel hoher Kohäsion; UML Diagramm und Begründung, warum die Kohäsion hoch ist]_ +- gewählte Klasse: `Recipe` + ![Kohäsion Beispiel UML](http://www.plantuml.com/plantuml/proxy?cache=no&src=https://raw.githubusercontent.com/ncryptedV1/AutoChef/docs/uml/cohesion.iuml) +Die Klasse `Recipe` repräsentiert ein Rezept und besteht aus einem Namen, einer Einkaufsliste (`GroceryList`) und einer Liste von Rezeptschritten (`RecipeStep`). +Sie weist hohe Kohäsion auf, da sie eng verwandte Attribute und Methoden enthält, die speziell für die Repräsentation eines Rezepts erforderlich sind. Alle Attribute sind eng miteinander verbunden und arbeiten zusammen, um ein konsistentes Rezeptmodell bereitzustellen. Die Klasse hat keine zusätzlichen Verantwortlichkeiten, die nicht direkt mit der Darstellung eines Rezepts zusammenhängen. + ### 4.3. Don’t Repeat Yourself (DRY) Commit-SHA: diff --git a/uml/cohesion.iuml b/uml/cohesion.iuml index e69de29..5d93ad1 100644 --- a/uml/cohesion.iuml +++ b/uml/cohesion.iuml @@ -0,0 +1,27 @@ +@startuml +class Recipe { + - name: String + - groceryList: GroceryList + - recipeSteps: List + + Recipe(name: String, groceryList: GroceryList, recipeSteps: List) + + Recipe(name: String, groceryList: GroceryList, recipeSteps: RecipeStep...) + + getName(): String + + getRecipeSteps(): List + + getGroceryList(): GroceryList + + getId(): String + + toString(): String + + equals(Object o): boolean + + hashCode(): int +} + +class GroceryList { + // fields and methods +} + +class RecipeStep { + // fields and methods +} + +Recipe "1" --> "1" GroceryList +Recipe "0..*" --> "1" RecipeStep +@enduml From 80d92c42805260e9b2c6c7424c0d762520c5f67b Mon Sep 17 00:00:00 2001 From: ncryptedV1 <15632467+ncryptedV1@users.noreply.github.com> Date: Wed, 5 Apr 2023 18:15:43 +0200 Subject: [PATCH 21/39] add: GRASP - coupling documentation --- .gitignore | 5 +- README.md | 130 ++++++++++++++++++++++++----------- uml/coupling-neg-better.iuml | 38 ++++++++++ uml/coupling-neg.iuml | 33 +++++++++ uml/coupling-pos-1.iuml | 32 +++++++++ uml/coupling-pos-2.iuml | 26 +++++++ uml/coupling-pos.iuml | 0 7 files changed, 221 insertions(+), 43 deletions(-) create mode 100644 uml/coupling-neg-better.iuml create mode 100644 uml/coupling-pos-1.iuml create mode 100644 uml/coupling-pos-2.iuml delete mode 100644 uml/coupling-pos.iuml diff --git a/.gitignore b/.gitignore index 5ff6309..279ff77 100644 --- a/.gitignore +++ b/.gitignore @@ -35,4 +35,7 @@ build/ .vscode/ ### Mac OS ### -.DS_Store \ No newline at end of file +.DS_Store + +### Persistence Folder ### +recipes \ No newline at end of file diff --git a/README.md b/README.md index 5c8f6d4..435d387 100644 --- a/README.md +++ b/README.md @@ -34,41 +34,42 @@ Abgabedatum: 28. Mai 2023 - Code-Beispiel = Code in das Dokument kopieren ## Inhaltsverzeichnis + - [Einführung](#1-einführung) - - [Übersicht über die Applikation](#11-übersicht-über-die-applikation) - - [Wie startet man die Applikation?](#12-wie-startet-man-die-applikation) - - [Wie testet man die Applikation?](#13-wie-testet-man-die-applikation) + - [Übersicht über die Applikation](#11-übersicht-über-die-applikation) + - [Wie startet man die Applikation?](#12-wie-startet-man-die-applikation) + - [Wie testet man die Applikation?](#13-wie-testet-man-die-applikation) - [Clean Architecture](#2-clean-architecture) - - [Was ist Clean Architecture?](#21-was-ist-clean-architecture) - - [Analyse der Dependency Rule](#22-analyse-der-dependency-rule) - - [Analyse der Schichten](#23-analyse-der-schichten) + - [Was ist Clean Architecture?](#21-was-ist-clean-architecture) + - [Analyse der Dependency Rule](#22-analyse-der-dependency-rule) + - [Analyse der Schichten](#23-analyse-der-schichten) - [SOLID](#3-solid) - - [Analyse Single-Responsibility-Principle (SRP)](#31-analyse-single-responsibility-principle--srp-) - - [Analyse Open-Closed-Principle (OCP)](#32-analyse-open-closed-principle--ocp-) - - [Analyse Dependency-Inversion-Principle (DIP)](#33-analyse-dependency-inversion-principle--dip-) + - [Analyse Single-Responsibility-Principle (SRP)](#31-analyse-single-responsibility-principle--srp-) + - [Analyse Open-Closed-Principle (OCP)](#32-analyse-open-closed-principle--ocp-) + - [Analyse Dependency-Inversion-Principle (DIP)](#33-analyse-dependency-inversion-principle--dip-) - [Weitere Prinzipien](#4-weitere-prinzipien) - - [Analyse GRASP: Geringe Kopplung](#41-analyse-grasp--geringe-kopplung) - - [Analyse GRASP: Hohe Kohäsion](#42-analyse-grasp--hohe-kohäsion) - - [Don't repeat yourself (DRY)](#43-dont-repeat-yourself--dry-) + - [Analyse GRASP: Geringe Kopplung](#41-analyse-grasp--geringe-kopplung) + - [Analyse GRASP: Hohe Kohäsion](#42-analyse-grasp--hohe-kohäsion) + - [Don't repeat yourself (DRY)](#43-dont-repeat-yourself--dry-) - [Unit Tests](#5-unit-tests) - - [10 Unit Tests](#51-10-unit-tests) - - [ATRIP: Automatic](#52-atrip--automatic) - - [ATRIP: Thorough](#53-atrip--thorough) - - [ATRIP: Professional](#54-atrip--professional) - - [Code Coverage](#55-code-coverage) - - [Fakes und Mocks](#56-fakes-und-mocks) + - [10 Unit Tests](#51-10-unit-tests) + - [ATRIP: Automatic](#52-atrip--automatic) + - [ATRIP: Thorough](#53-atrip--thorough) + - [ATRIP: Professional](#54-atrip--professional) + - [Code Coverage](#55-code-coverage) + - [Fakes und Mocks](#56-fakes-und-mocks) - [Domain Driven Design](#6-domain-driven-design) - - [Ubiquitous Language](#61-ubiquitous-language) - - [Entities](#62-entities) - - [Value Objects](#63-value-objects) - - [Repositories](#64-repositories) - - [Aggregates](#65-aggregates) + - [Ubiquitous Language](#61-ubiquitous-language) + - [Entities](#62-entities) + - [Value Objects](#63-value-objects) + - [Repositories](#64-repositories) + - [Aggregates](#65-aggregates) - [Refactoring](#7-refactoring) - - [Code Smells](#71-code-smells) - - [2 Refactorings](#72-2-refactorings) + - [Code Smells](#71-code-smells) + - [2 Refactorings](#72-2-refactorings) - [Entwurfsmuster](#8-entwurfsmuster) - - [Entwurfsmuster: Name](#81-entwurfsmuster--name) - - [Entwurfsmuster: Name](#82-entwurfsmuster--name) + - [Entwurfsmuster: Name](#81-entwurfsmuster--name) + - [Entwurfsmuster: Name](#82-entwurfsmuster--name) ## 1. Einführung @@ -152,11 +153,18 @@ Abhängigkeitshierarchie zwischen den Komponenten eingeführt wird. ![Dependency Rule positives Beispiel UML](http://www.plantuml.com/plantuml/proxy?cache=no&src=https://raw.githubusercontent.com/ncryptedV1/AutoChef/docs/uml/dependency-rule-pos.iuml) `DialogService` ist abhängig von `RecipeRepository` (Interface). -`DialogService` hat keine Abhängigkeiten zu Implementierungen, nur zu Interfaces und anderen Domänen-Entities/Value Objects. -In dieser Anwendung zeigt die `DialogService`-Klasse ein gutes Beispiel für das Einhalten der Dependency Rule, da sie nur vom `RecipeRepository`-Interface abhängt und nicht von einer konkreten -Implementierung. Das bedeutet, dass die äußeren Schichten (in diesem Fall die Infrastrukturschicht mit der `RecipeFileRepository`-Implementierung) von der inneren Schicht (Domänenschicht) abhängen und nicht umgekehrt. Die Abhängigkeiten richten sich somit von außen nach innen. -Es gibt nur eine Klasse, die von `DialogService` abhängt: `AutoChef`. Dies ist die Main-Klasse der Anwendung. Ihre Aufgabe ist die Initialisierung des `DialogService` mitsamt Abhängigkeiten (`RecipeRepository`) und enthält daher keine weitere Logik noch -Konfigurationsdetails. Aus diesem Grund ist sie der Infrastrukturschicht zuzuordnen. Auch hier wird die Dependency Rule eingehalten, da eine Abhängigkeit von außen nach innen besteht. +`DialogService` hat keine Abhängigkeiten zu Implementierungen, nur zu Interfaces und anderen +Domänen-Entities/Value Objects. +In dieser Anwendung zeigt die `DialogService`-Klasse ein gutes Beispiel für das Einhalten der +Dependency Rule, da sie nur vom `RecipeRepository`-Interface abhängt und nicht von einer konkreten +Implementierung. Das bedeutet, dass die äußeren Schichten (in diesem Fall die Infrastrukturschicht +mit der `RecipeFileRepository`-Implementierung) von der inneren Schicht (Domänenschicht) abhängen +und nicht umgekehrt. Die Abhängigkeiten richten sich somit von außen nach innen. +Es gibt nur eine Klasse, die von `DialogService` abhängt: `AutoChef`. Dies ist die Main-Klasse der +Anwendung. Ihre Aufgabe ist die Initialisierung des `DialogService` mitsamt +Abhängigkeiten (`RecipeRepository`) und enthält daher keine weitere Logik noch +Konfigurationsdetails. Aus diesem Grund ist sie der Infrastrukturschicht zuzuordnen. Auch hier wird +die Dependency Rule eingehalten, da eine Abhängigkeit von außen nach innen besteht. #### Negativ-Beispiel: Dependency Rule @@ -165,9 +173,15 @@ Konfigurationsdetails. Aus diesem Grund ist sie der Infrastrukturschicht zuzuord ![Dependency Rule negatives Beispiel UML](http://www.plantuml.com/plantuml/proxy?cache=no&src=https://raw.githubusercontent.com/ncryptedV1/AutoChef/docs/uml/dependency-rule-neg.iuml) `ChefkochRecipeFetcher` ist abhängig von `WebsiteFetcher`. -`WebsiteFetcher` ist eine Util-Klasse, die für das Abrufen von Webseiten-Inhalten zuständig ist und damit der Infrastruktursschicht zugehörig. -Dadurch verletzt die `ChefkochRecipeFetcher`-Klasse die Dependency Rule, da eine Abhängigkeit von der Applikationsschicht in die Infrastrukturschicht besteht. Die `ChefkochRecipeFetcher`-Klasse sollte stattdessen von einem Interface abhängen, das in der Domänenschicht definiert ist und von einer Implementierung in einer äußeren Schicht bereitgestellt wird, um die Abhängigkeiten korrekt von außen nach innen zu richten. -Von der Klasse abhängig ist lediglich der `DialogService`, welcher sich auf selbiger Schicht befindet. +`WebsiteFetcher` ist eine Util-Klasse, die für das Abrufen von Webseiten-Inhalten zuständig ist und +damit der Infrastruktursschicht zugehörig. +Dadurch verletzt die `ChefkochRecipeFetcher`-Klasse die Dependency Rule, da eine Abhängigkeit von +der Applikationsschicht in die Infrastrukturschicht besteht. Die `ChefkochRecipeFetcher`-Klasse +sollte stattdessen von einem Interface abhängen, das in der Domänenschicht definiert ist und von +einer Implementierung in einer äußeren Schicht bereitgestellt wird, um die Abhängigkeiten korrekt +von außen nach innen zu richten. +Von der Klasse abhängig ist lediglich der `DialogService`, welcher sich auf selbiger Schicht +befindet. ### 2.3. Analyse der Schichten @@ -329,7 +343,8 @@ wäre das OCP erfüllt. ### 3.3. Analyse Dependency-Inversion-Principle (DIP) -Da zur Einhaltung der Dependency Rule der Clean Architecture-Methode oft das DIP genutzt wird, können hier selbige Beispiel wie aus [Kapitel 2.2](#22-analyse-der-dependency-rule) genutzt werden. +Da zur Einhaltung der Dependency Rule der Clean Architecture-Methode oft das DIP genutzt wird, +können hier selbige Beispiel wie aus [Kapitel 2.2](#22-analyse-der-dependency-rule) genutzt werden. #### Positiv-Beispiel @@ -337,7 +352,9 @@ Da zur Einhaltung der Dependency Rule der Clean Architecture-Methode oft das DIP ![Dependency-Inversion-Principle positives Beispiel UML](http://www.plantuml.com/plantuml/proxy?cache=no&src=https://raw.githubusercontent.com/ncryptedV1/AutoChef/docs/uml/dependency-inversion-pos.iuml) -Wie im obigen UML ersichtlich, wurde das DIP im `DialogService` eingehalten. Die Klasse `DialogService` hängt von einer Abstraktion (dem Interface `RecipeRepository`) ab und nicht von einer konkreten Implementierung (der Klasse `RecipeFileRepository`). +Wie im obigen UML ersichtlich, wurde das DIP im `DialogService` eingehalten. Die +Klasse `DialogService` hängt von einer Abstraktion (dem Interface `RecipeRepository`) ab und nicht +von einer konkreten Implementierung (der Klasse `RecipeFileRepository`). #### Negativ-Beispiel @@ -345,30 +362,59 @@ Wie im obigen UML ersichtlich, wurde das DIP im `DialogService` eingehalten. Die ![Dependency-Inversion-Principle negatives Beispiel UML](http://www.plantuml.com/plantuml/proxy?cache=no&src=https://raw.githubusercontent.com/ncryptedV1/AutoChef/docs/uml/dependency-inversion-neg.iuml) -Das DIP wird in der Klasse `ChefkochRecipeFetcher` verletzt. Die Klasse `ChefkochRecipeFetcher` verwendet direkt die Klasse `WebsiteFetcher`, um den Inhalt einer Webseite abzurufen. Hier wäre es besser, ein Interface für das Abrufen von Webinhalten zu erstellen und dieses Interface als Abstraktion zu verwenden. +Das DIP wird in der Klasse `ChefkochRecipeFetcher` verletzt. Die Klasse `ChefkochRecipeFetcher` +verwendet direkt die Klasse `WebsiteFetcher`, um den Inhalt einer Webseite abzurufen. Hier wäre es +besser, ein Interface für das Abrufen von Webinhalten zu erstellen und dieses Interface als +Abstraktion zu verwenden. ## 4. Weitere Prinzipien ### 4.1. Analyse GRASP: Geringe Kopplung -_[jeweils eine bis jetzt noch nicht behandelte Klasse als positives und negatives Beispiel geringer Kopplung; jeweils UML Diagramm mit zusammenspielenden Klassen, Aufgabenbeschreibung und Begründung für die Umsetzung der geringen Kopplung bzw. Beschreibung, wie die Kopplung aufgelöst werden kann]_ - #### Positiv-Beispiel +- gewählte Klasse: `DialogInputParser` + ![Kopplung positives Beispiel UML](http://www.plantuml.com/plantuml/proxy?cache=no&src=https://raw.githubusercontent.com/ncryptedV1/AutoChef/docs/uml/coupling-pos.iuml) +Diese Klasse ist für das Parsen der Benutzereingaben zuständig. Sie ist abhängig von `InputReader` +und `OutputService`, wobei es sich in beiden Fällen um Interfaces handelt. Damit hält die +Klasse `DialogInputParser` die Kopplung gering, da sie von Interfaces, statt konkreten +Implementierungen abhängt. Dadurch wird die Austauschbarkeit ermöglicht und die Testbarkeit der +Klasse verbessert. + #### Negativ-Beispiel +- gewählte Klasse: `ChefkochRecipeFetcher` + ![Kopplung negatives Beispiel UML](http://www.plantuml.com/plantuml/proxy?cache=no&src=https://raw.githubusercontent.com/ncryptedV1/AutoChef/docs/uml/coupling-neg.iuml) +Diese Klasse ist dafür zuständig, Rezeptinformationen von der Chefkoch-Website abzurufen und sie in eine Rezept-Instanz umzuwandeln. Sie ist von `WebsiteFetcher`, `Recipe`, `GroceryList` und `RecipeStep` abhängig. Durch die Abhängigkeit von `WebsiteFetcher` weist die Klasse `ChefkochRecipeFetcher` eine hohe Kopplung auf, da sie direkt die statische Methode getWebsiteBody() aufruft. Dies könnte gelöst werden, indem man eine Schnittstelle für das Abrufen von Webseiten erstellt und diese Schnittstelle von der `ChefkochRecipeFetcher`-Klasse verwendet. Auf diese Weise könnten verschiedene Implementierungen zum Abrufen von Webseiten ausgetauscht werden, was die Kopplung verringert. + +![Kopplung negatives Beispiel verbessert UML](http://www.plantuml.com/plantuml/proxy?cache=no&src=https://raw.githubusercontent.com/ncryptedV1/AutoChef/docs/uml/coupling-neg-better.iuml) + +#### Positiv-Beispiel 2 +Da eine Klasse gefordert wurde, die nicht bereits in einem vorigen Kapitel behandelt wurde und dies beim vorherigen Negativ-Beispiel mit `ChefkochRecipeFetcher` nicht der Fall ist, wird ein weiteres Positiv-Beispiel aufgeführt. + +- gewählte Klasse: `RecipeRepository` + +![Kopplung positives Beispiel 2 UML](http://www.plantuml.com/plantuml/proxy?cache=no&src=https://raw.githubusercontent.com/ncryptedV1/AutoChef/docs/uml/coupling-pos-2.iuml) + +Die `RecipeRepository` Klasse ist ein Interface, das die Methoden für den Zugriff auf Rezept-Entitäten definiert. Sie ist lediglich von `Recipe` abhängig. Daher ist es ein weiteres Beispiel für geringe Kopplung. Es definiert lediglich die benötigten Methoden für den Zugriff auf Rezept-Entitäten, ohne sich auf eine spezifische Implementierung festzulegen. Die Implementierung von `RecipeRepository` (z.B. `RecipeFileRepository`) kann dann von der Anwendungsentwicklung abhängig gemacht werden, ohne die gesamte Anwendung zu beeinflussen. + ### 4.2. Analyse GRASP: Hohe Kohäsion - gewählte Klasse: `Recipe` ![Kohäsion Beispiel UML](http://www.plantuml.com/plantuml/proxy?cache=no&src=https://raw.githubusercontent.com/ncryptedV1/AutoChef/docs/uml/cohesion.iuml) -Die Klasse `Recipe` repräsentiert ein Rezept und besteht aus einem Namen, einer Einkaufsliste (`GroceryList`) und einer Liste von Rezeptschritten (`RecipeStep`). -Sie weist hohe Kohäsion auf, da sie eng verwandte Attribute und Methoden enthält, die speziell für die Repräsentation eines Rezepts erforderlich sind. Alle Attribute sind eng miteinander verbunden und arbeiten zusammen, um ein konsistentes Rezeptmodell bereitzustellen. Die Klasse hat keine zusätzlichen Verantwortlichkeiten, die nicht direkt mit der Darstellung eines Rezepts zusammenhängen. +Die Klasse `Recipe` repräsentiert ein Rezept und besteht aus einem Namen, einer +Einkaufsliste (`GroceryList`) und einer Liste von Rezeptschritten (`RecipeStep`). +Sie weist hohe Kohäsion auf, da sie eng verwandte Attribute und Methoden enthält, die speziell für +die Repräsentation eines Rezepts erforderlich sind. Alle Attribute sind eng miteinander verbunden +und arbeiten zusammen, um ein konsistentes Rezeptmodell bereitzustellen. Die Klasse hat keine +zusätzlichen Verantwortlichkeiten, die nicht direkt mit der Darstellung eines Rezepts +zusammenhängen. ### 4.3. Don’t Repeat Yourself (DRY) diff --git a/uml/coupling-neg-better.iuml b/uml/coupling-neg-better.iuml new file mode 100644 index 0000000..dcae9e2 --- /dev/null +++ b/uml/coupling-neg-better.iuml @@ -0,0 +1,38 @@ +@startuml + +left to right direction + +class ChefkochRecipeFetcher { + +getRecipe(url: String): Recipe + -extractRecipeName(content: String): String + -extractIngredients(content: String): GroceryList + -extractRecipeSteps(content: String): List +} + +interface WebsiteFetcher { + +getWebsiteBody(urlString: String): String +} + +class BufferedWebsiteFetcher { + +getWebsiteBody(urlString: String): String +} + +class Recipe { + // fields and methods +} + +class GroceryList { + // fields and methods +} + +class RecipeStep { + // fields and methods +} + +WebsiteFetcher <-- ChefkochRecipeFetcher +WebsiteFetcher <|.. BufferedWebsiteFetcher +Recipe <-- ChefkochRecipeFetcher +GroceryList <-- ChefkochRecipeFetcher +RecipeStep <-- ChefkochRecipeFetcher + +@enduml diff --git a/uml/coupling-neg.iuml b/uml/coupling-neg.iuml index e69de29..8c76ba5 100644 --- a/uml/coupling-neg.iuml +++ b/uml/coupling-neg.iuml @@ -0,0 +1,33 @@ +@startuml + +left to right direction + +class ChefkochRecipeFetcher { + +getRecipe(url: String): Recipe + -extractRecipeName(content: String): String + -extractIngredients(content: String): GroceryList + -extractRecipeSteps(content: String): List +} + +class WebsiteFetcher { + +getWebsiteBody(urlString: String): String +} + +class Recipe { + // fields and methods +} + +class GroceryList { + // fields and methods +} + +class RecipeStep { + // fields and methods +} + +WebsiteFetcher <-- ChefkochRecipeFetcher +Recipe <-- ChefkochRecipeFetcher +GroceryList <-- ChefkochRecipeFetcher +RecipeStep <-- ChefkochRecipeFetcher + +@enduml diff --git a/uml/coupling-pos-1.iuml b/uml/coupling-pos-1.iuml new file mode 100644 index 0000000..200d77c --- /dev/null +++ b/uml/coupling-pos-1.iuml @@ -0,0 +1,32 @@ +@startuml + +interface InputReader { + +readLine(): String +} + +interface OutputService { + +info(msg: String): void + +warning(msg: String): void + +severe(msg: String): void + +rawOut(msg: Object): void + +rawErr(msg: Object): void +} + +interface InputParser { + +getInteger(lowerBound: Integer, upperBound: Integer, question: String): Integer + +getDate(after: LocalDate, before: LocalDate, question: String): LocalDate + +getString(validator: Function, question: String): String +} + +class DialogInputParser { + -inputReader: InputReader + -outputService: OutputService + +DialogInputParser(inputReader: InputReader, outputService: OutputService) + +getInputWithType(transformFunction: Function, question: String): T +} + +InputParser <|.. DialogInputParser +DialogInputParser --> InputReader +DialogInputParser --> OutputService + +@enduml \ No newline at end of file diff --git a/uml/coupling-pos-2.iuml b/uml/coupling-pos-2.iuml new file mode 100644 index 0000000..03c4979 --- /dev/null +++ b/uml/coupling-pos-2.iuml @@ -0,0 +1,26 @@ +@startuml + +class Recipe { + // fields and methods +} + +class RecipeFileRepository { + -recipesFolder: File + +RecipeFileRepository(recipesFolder: File) + +saveRecipe(recipe: Recipe): void + +deleteRecipe(recipe: Recipe): boolean + +getRecipes(): List + +getRecipe(id: String): Recipe +} + +interface RecipeRepository { + +saveRecipe(recipe: Recipe): void + +deleteRecipe(recipe: Recipe): boolean + +getRecipes(): List + +getRecipe(id: String): Recipe +} + +RecipeFileRepository ..|> RecipeRepository +RecipeRepository -> Recipe + +@enduml diff --git a/uml/coupling-pos.iuml b/uml/coupling-pos.iuml deleted file mode 100644 index e69de29..0000000 From b94f746a7b32d09b0186597fc9f3017d7b876dc0 Mon Sep 17 00:00:00 2001 From: ncrypted | Oliver <15632467+ncryptedV1@users.noreply.github.com> Date: Wed, 5 Apr 2023 18:41:52 +0200 Subject: [PATCH 22/39] fix: ToC links --- README.md | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 435d387..058cb9a 100644 --- a/README.md +++ b/README.md @@ -44,18 +44,18 @@ Abgabedatum: 28. Mai 2023 - [Analyse der Dependency Rule](#22-analyse-der-dependency-rule) - [Analyse der Schichten](#23-analyse-der-schichten) - [SOLID](#3-solid) - - [Analyse Single-Responsibility-Principle (SRP)](#31-analyse-single-responsibility-principle--srp-) - - [Analyse Open-Closed-Principle (OCP)](#32-analyse-open-closed-principle--ocp-) - - [Analyse Dependency-Inversion-Principle (DIP)](#33-analyse-dependency-inversion-principle--dip-) + - [Analyse Single-Responsibility-Principle (SRP)](#31-analyse-single-responsibility-principle-srp) + - [Analyse Open-Closed-Principle (OCP)](#32-analyse-open-closed-principle-ocp) + - [Analyse Dependency-Inversion-Principle (DIP)](#33-analyse-dependency-inversion-principle-dip) - [Weitere Prinzipien](#4-weitere-prinzipien) - - [Analyse GRASP: Geringe Kopplung](#41-analyse-grasp--geringe-kopplung) - - [Analyse GRASP: Hohe Kohäsion](#42-analyse-grasp--hohe-kohäsion) - - [Don't repeat yourself (DRY)](#43-dont-repeat-yourself--dry-) + - [Analyse GRASP: Geringe Kopplung](#41-analyse-grasp-geringe-kopplung) + - [Analyse GRASP: Hohe Kohäsion](#42-analyse-grasp-hohe-kohäsion) + - [Don't repeat yourself (DRY)](#43-dont-repeat-yourself-dry) - [Unit Tests](#5-unit-tests) - [10 Unit Tests](#51-10-unit-tests) - - [ATRIP: Automatic](#52-atrip--automatic) - - [ATRIP: Thorough](#53-atrip--thorough) - - [ATRIP: Professional](#54-atrip--professional) + - [ATRIP: Automatic](#52-atrip-automatic) + - [ATRIP: Thorough](#53-atrip-thorough) + - [ATRIP: Professional](#54-atrip-professional) - [Code Coverage](#55-code-coverage) - [Fakes und Mocks](#56-fakes-und-mocks) - [Domain Driven Design](#6-domain-driven-design) @@ -68,8 +68,8 @@ Abgabedatum: 28. Mai 2023 - [Code Smells](#71-code-smells) - [2 Refactorings](#72-2-refactorings) - [Entwurfsmuster](#8-entwurfsmuster) - - [Entwurfsmuster: Name](#81-entwurfsmuster--name) - - [Entwurfsmuster: Name](#82-entwurfsmuster--name) + - [Entwurfsmuster: Name](#81-entwurfsmuster-name) + - [Entwurfsmuster: Name](#82-entwurfsmuster-name) ## 1. Einführung From 4b96a396f4d3ca183831ce529ed14e47f842a33e Mon Sep 17 00:00:00 2001 From: ncryptedV1 <15632467+ncryptedV1@users.noreply.github.com> Date: Wed, 5 Apr 2023 18:43:47 +0200 Subject: [PATCH 23/39] fix: README coupling positive example 1 uml link --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 058cb9a..45b06a0 100644 --- a/README.md +++ b/README.md @@ -375,7 +375,7 @@ Abstraktion zu verwenden. - gewählte Klasse: `DialogInputParser` -![Kopplung positives Beispiel UML](http://www.plantuml.com/plantuml/proxy?cache=no&src=https://raw.githubusercontent.com/ncryptedV1/AutoChef/docs/uml/coupling-pos.iuml) +![Kopplung positives Beispiel UML](http://www.plantuml.com/plantuml/proxy?cache=no&src=https://raw.githubusercontent.com/ncryptedV1/AutoChef/docs/uml/coupling-pos-1.iuml) Diese Klasse ist für das Parsen der Benutzereingaben zuständig. Sie ist abhängig von `InputReader` und `OutputService`, wobei es sich in beiden Fällen um Interfaces handelt. Damit hält die From 3dd9cdd8616670b132253298dfc597ba30297f90 Mon Sep 17 00:00:00 2001 From: ncryptedV1 <15632467+ncryptedV1@users.noreply.github.com> Date: Wed, 5 Apr 2023 20:07:59 +0200 Subject: [PATCH 24/39] add: design patterns --- README.md | 30 +++++++++++++++++++++++----- uml/design-pattern-1.iuml | 0 uml/design-pattern-2.iuml | 0 uml/design-pattern-builder.iuml | 27 +++++++++++++++++++++++++ uml/design-pattern-facade.iuml | 35 +++++++++++++++++++++++++++++++++ 5 files changed, 87 insertions(+), 5 deletions(-) delete mode 100644 uml/design-pattern-1.iuml delete mode 100644 uml/design-pattern-2.iuml create mode 100644 uml/design-pattern-builder.iuml create mode 100644 uml/design-pattern-facade.iuml diff --git a/README.md b/README.md index 45b06a0..ba155c4 100644 --- a/README.md +++ b/README.md @@ -1147,12 +1147,32 @@ aufgetreten wären. ## 8. Entwurfsmuster -_[2 unterschiedliche Entwurfsmuster aus der Vorlesung (oder nach Absprache auch andere) jeweils sinnvoll einsetzen, begründen und UML-Diagramm]_ +#### 8.1. Entwurfsmuster: Facade -#### 8.1. Entwurfsmuster: [Name] +# gewählte Klasse: `ConsoleOutputService` -![Entwurfstmuster 1 Beispiel UML](http://www.plantuml.com/plantuml/proxy?cache=no&src=https://raw.githubusercontent.com/ncryptedV1/AutoChef/docs/uml/design-pattern-1.iuml) +![Entwurfstmuster Facade Beispiel UML](http://www.plantuml.com/plantuml/proxy?cache=no&src=https://raw.githubusercontent.com/ncryptedV1/AutoChef/docs/uml/design-pattern-facade.iuml) -#### 8.2. Entwurfsmuster: [Name] +Die obige Klasse `ConsoleOutputService` stellt eine Facade für die `Logger`-Klasse dar, weil sie eine vereinfachte und einheitliche Schnittstelle für das Logging-System bereitstellt. Die Hauptziele einer Facade sind die Vereinfachung der Schnittstelle und die Entkopplung von Subsystemen. -![Entwurfstmuster 2 Beispiel UML](http://www.plantuml.com/plantuml/proxy?cache=no&src=https://raw.githubusercontent.com/ncryptedV1/AutoChef/docs/uml/design-pattern-1.iuml) +In diesem Fall verbirgt die `ConsoleOutputService`-Klasse die Komplexität der `Logger`-Klasse und des LogManager-Systems, indem sie nur eine Reihe von statischen Methoden zur Verfügung stellt: + +- `info(String msg)`: zum Loggen von Info-Nachrichten. +- `warning(String msg)`: zum Loggen von Warn-Nachrichten. +- `severe(String msg)`: zum Loggen von schwerwiegenden Fehlermeldungen. +- `rawOut(Object msg)`: zum direkten Ausgeben von Nachrichten auf der Standardausgabe (stdout). +- `rawErr(Object msg)`: zum direkten Ausgeben von Fehlermeldungen auf der Standardfehlerausgabe (stderr). + +Die `ConsoleOutputService`-Klasse kapselt die Details der `Logger`-Konfiguration im statischen Block und -Initialisierung. Dies ermöglicht den Nutzern, die Logging-Funktionalität einfach zu verwenden, ohne sich um die zugrunde liegenden Details kümmern zu müssen. + +#### 8.2. Entwurfsmuster: Builder + +- gewählte Klasse: `MealPlanBuilder` + +![Entwurfstmuster Builder Beispiel UML](http://www.plantuml.com/plantuml/proxy?cache=no&src=https://raw.githubusercontent.com/ncryptedV1/AutoChef/docs/uml/design-pattern-builder.iuml) + +Der `MealPlanBuilder` ist ein Beispiel für das Entwurfsmuster Builder, da er eine einfache Schnittstelle bietet, um komplexe Objekte schrittweise aufzubauen. Mithilfe von Methoden wie `setStartDate()`, `setEndDate()` und `addMeal()` kann der Verwender des `MealPlanBuilder` einen Mahlzeiten-Plan definieren, ohne dabei die genaue Implementierung des `MealPlan` kennen zu müssen. + +Die `build()` Methode ist das Herzstück des Builders und erzeugt das fertige Objekt. Dabei wird die Konsistenz des Objekts sichergestellt und gegebenenfalls eine `IllegalStateException` geworfen, wenn zum Beispiel ein Start- oder Enddatum fehlt oder nicht alle Mahlzeiten definiert sind. + +Der Builder ist ein nützliches Muster, wenn die Erstellung von Objekten viele Parameter erfordert oder komplex ist. Indem er den Verwendet von der Komplexität des Aufbaus des Objekts abschirmt, ermöglicht er eine klare und einfache API und stellt sicher, dass die erstellten Objekte korrekt initialisiert sind. diff --git a/uml/design-pattern-1.iuml b/uml/design-pattern-1.iuml deleted file mode 100644 index e69de29..0000000 diff --git a/uml/design-pattern-2.iuml b/uml/design-pattern-2.iuml deleted file mode 100644 index e69de29..0000000 diff --git a/uml/design-pattern-builder.iuml b/uml/design-pattern-builder.iuml new file mode 100644 index 0000000..d6504c2 --- /dev/null +++ b/uml/design-pattern-builder.iuml @@ -0,0 +1,27 @@ +@startuml + +class MealPlanBuilder { + - mealMap: Map + - startDate: LocalDate + - endDate: LocalDate + + setStartDate(startDate: LocalDate): MealPlanBuilder + + setEndDate(endDate: LocalDate): MealPlanBuilder + + addMeal(date: LocalDate, meal: Meal): MealPlanBuilder + + build(): MealPlan +} + +class MealPlan { + - meals: List + - start: LocalDate + - end: LocalDate + + getMeals(): List + + getStart(): LocalDate + + getEnd(): LocalDate + + getDays(): int + + getGroceryList(): GroceryList + + toString(): String +} + +MealPlanBuilder --> MealPlan + +@enduml diff --git a/uml/design-pattern-facade.iuml b/uml/design-pattern-facade.iuml new file mode 100644 index 0000000..5d80c9f --- /dev/null +++ b/uml/design-pattern-facade.iuml @@ -0,0 +1,35 @@ +@startuml + +left to right direction + +class ConsoleOutputService { + - logger: Logger + + info(String msg) + + warning(String msg) + + severe(String msg) + + rawOut(Object msg) + + rawErr(Object msg) +} + +class Logger { + - name: String + - resourceBundleName: String + - handlers: Handler[] + - parent: Logger + // further attributes + - Logger(name: String) + - Logger(name: String, resourceBundleName: String) + - getHandlers(): Handler[] + - setHandlers(handlers: Handler[]) + - addHandler(handler: Handler) + - removeHandler(handler: Handler) + - log(record: LogRecord) + - log(level: Level, msg: String) + - log(level: Level, msg: String, params: Object[]) + - log(level: Level, msg: String, thrown: Throwable) + // further methods() +} + +ConsoleOutputService -> Logger + +@enduml From 5b1d04ff2584f880f56319cd77d795b1445cc447 Mon Sep 17 00:00:00 2001 From: ncryptedV1 <15632467+ncryptedV1@users.noreply.github.com> Date: Wed, 5 Apr 2023 20:13:21 +0200 Subject: [PATCH 25/39] fix: typos --- README.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index ba155c4..3abd688 100644 --- a/README.md +++ b/README.md @@ -148,7 +148,7 @@ Abhängigkeitshierarchie zwischen den Komponenten eingeführt wird. #### Positiv-Beispiel: Dependency Rule -- gewählte Klasse(n): `DialogService` +- gewählte Klasse: `DialogService` ![Dependency Rule positives Beispiel UML](http://www.plantuml.com/plantuml/proxy?cache=no&src=https://raw.githubusercontent.com/ncryptedV1/AutoChef/docs/uml/dependency-rule-pos.iuml) @@ -168,7 +168,7 @@ die Dependency Rule eingehalten, da eine Abhängigkeit von außen nach innen bes #### Negativ-Beispiel: Dependency Rule -- gewählte Klasse(n): `ChefkochRecipeFetcher` +- gewählte Klasse: `ChefkochRecipeFetcher` ![Dependency Rule negatives Beispiel UML](http://www.plantuml.com/plantuml/proxy?cache=no&src=https://raw.githubusercontent.com/ncryptedV1/AutoChef/docs/uml/dependency-rule-neg.iuml) @@ -187,7 +187,7 @@ befindet. #### Schicht: Domain Code -- gewählte Klasse(n): `Recipe` +- gewählte Klasse: `Recipe` ![Schicht 1 UML](http://www.plantuml.com/plantuml/proxy?cache=no&src=https://raw.githubusercontent.com/ncryptedV1/AutoChef/docs/uml/layer-1.iuml) @@ -211,7 +211,7 @@ einzuordnen. #### Schicht: Application Code -- gewählte Klasse(n): `DialogService` mit `DialogState` +- gewählte Klassen: `DialogService` mit `DialogState` ![Schicht 2 UML](http://www.plantuml.com/plantuml/proxy?cache=no&src=https://raw.githubusercontent.com/ncryptedV1/AutoChef/docs/uml/layer-2.iuml) @@ -288,7 +288,7 @@ während die `startMealPlanGeneration` lediglich eine verwaltende Rolle einnehme #### Positiv-Beispiel -gewählte Klasse(n): `RecipeFileRepository` mit Interface `RecipeRepository` +gewählte Klassen: `RecipeFileRepository` mit Interface `RecipeRepository` ![Open-Closed positives Beispiel UML](http://www.plantuml.com/plantuml/proxy?cache=no&src=https://raw.githubusercontent.com/ncryptedV1/AutoChef/docs/uml/open-closed-pos.iuml) @@ -314,7 +314,7 @@ wird, ergibt dieser Ansatz Sinn. #### Negativ-Beispiel -gewählte Klasse(n): `ChefkochRecipeFetcher` +gewählte Klasse: `ChefkochRecipeFetcher` vorher: @@ -344,11 +344,11 @@ wäre das OCP erfüllt. ### 3.3. Analyse Dependency-Inversion-Principle (DIP) Da zur Einhaltung der Dependency Rule der Clean Architecture-Methode oft das DIP genutzt wird, -können hier selbige Beispiel wie aus [Kapitel 2.2](#22-analyse-der-dependency-rule) genutzt werden. +können hier selbige Beispiele wie aus [Kapitel 2.2](#22-analyse-der-dependency-rule) genutzt werden. #### Positiv-Beispiel -- gewählte Klasse(n): `DialogService` +- gewählte Klasse: `DialogService` ![Dependency-Inversion-Principle positives Beispiel UML](http://www.plantuml.com/plantuml/proxy?cache=no&src=https://raw.githubusercontent.com/ncryptedV1/AutoChef/docs/uml/dependency-inversion-pos.iuml) @@ -358,7 +358,7 @@ von einer konkreten Implementierung (der Klasse `RecipeFileRepository`). #### Negativ-Beispiel -- gewählte Klasse(n): `ChefkochRecipeFetcher` +- gewählte Klasse: `ChefkochRecipeFetcher` ![Dependency-Inversion-Principle negatives Beispiel UML](http://www.plantuml.com/plantuml/proxy?cache=no&src=https://raw.githubusercontent.com/ncryptedV1/AutoChef/docs/uml/dependency-inversion-neg.iuml) From 96242ff0ac6fbc1905af949c4727ccad6497bca6 Mon Sep 17 00:00:00 2001 From: ncryptedV1 <15632467+ncryptedV1@users.noreply.github.com> Date: Wed, 5 Apr 2023 20:15:55 +0200 Subject: [PATCH 26/39] edit: clarify --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 3abd688..daa592f 100644 --- a/README.md +++ b/README.md @@ -394,7 +394,7 @@ Diese Klasse ist dafür zuständig, Rezeptinformationen von der Chefkoch-Website ![Kopplung negatives Beispiel verbessert UML](http://www.plantuml.com/plantuml/proxy?cache=no&src=https://raw.githubusercontent.com/ncryptedV1/AutoChef/docs/uml/coupling-neg-better.iuml) #### Positiv-Beispiel 2 -Da eine Klasse gefordert wurde, die nicht bereits in einem vorigen Kapitel behandelt wurde und dies beim vorherigen Negativ-Beispiel mit `ChefkochRecipeFetcher` nicht der Fall ist, wird ein weiteres Positiv-Beispiel aufgeführt. +Da eine Klasse gefordert wurde, die nicht bereits in einem vorigen Kapitel behandelt wurde und dies beim vorherigen Negativ-Beispiel mit `ChefkochRecipeFetcher` nicht der Fall ist und kein weiteres Negativ-Beispiel existiert, wird ein weiteres Positiv-Beispiel aufgeführt. - gewählte Klasse: `RecipeRepository` @@ -1149,7 +1149,7 @@ aufgetreten wären. #### 8.1. Entwurfsmuster: Facade -# gewählte Klasse: `ConsoleOutputService` +- gewählte Klasse: `ConsoleOutputService` ![Entwurfstmuster Facade Beispiel UML](http://www.plantuml.com/plantuml/proxy?cache=no&src=https://raw.githubusercontent.com/ncryptedV1/AutoChef/docs/uml/design-pattern-facade.iuml) From c72e8d04f688cc0bf0c328b4648a1b3360451003 Mon Sep 17 00:00:00 2001 From: cuvar Date: Thu, 6 Apr 2023 11:09:54 +0200 Subject: [PATCH 27/39] docs: add fake 1 --- README.md | 45 +++++++------------------------------------- uml/fake-mock-1.iuml | 22 ++++++++++++++++++++++ uml/fake-mock-2.iuml | 22 ++++++++++++++++++++++ 3 files changed, 51 insertions(+), 38 deletions(-) diff --git a/README.md b/README.md index d3fcdc9..8b7a899 100644 --- a/README.md +++ b/README.md @@ -740,54 +740,23 @@ Unabhängig dessen begründet sich die Code-Coverage wie folgt: ### 5.6. Fakes und Mocks -In diesem Projekt wurden vor allem Mock-Objekte eingesetzt. Sie wurden genutzt, um benötigte -Nebenklassen zu mocken. Nachfolgend sind zwei demonstrative Beispiel für den Einsatz von -Mock-Objekten mit dazugehörigen UML Diagrammen zu sehen. +In diesem Projekt wurden vor allem Mock-Objekte eingesetzt. Sie wurden genutzt, um benötigte Nebenklassen zu mocken. Jedoch wurden auch einige Fake Objekte an Stellen genutzt, an denen die Funktionalitäten von Mock-Objekten nicht ausreichten. -Beispiel aus: `TestGroceryList#testConstructorVarArgs` - -```java - @Test -public void testConstructorVarArgs(){ - // arrange - GroceryItem item1=mock(GroceryItem.class); - GroceryItem item2=mock(GroceryItem.class); - - // act - GroceryList list=new GroceryList(item1,item2); - - // assert - assertNotNull(list); - } -``` +#### Fake-Objekt 1: `OutputServiceFake` ![Fakes und Mocks Beispiel 1 UML](http://www.plantuml.com/plantuml/proxy?cache=no&src=https://raw.githubusercontent.com/ncryptedV1/AutoChef/docs/uml/fake-mock-1.iuml) -_[TODO: Analyse und Begründung für Einsatz] +Die `OutputServiceFake`-Klasse ist ein Fake-Objekt, das die Ausgabe an die Konsole imitiert. Dieses Projekt nutzt Ein- und Ausgabefunktionen für die Interaktion mit dem Benutzer. Da beide Funktionalitäten jedoch automatisierten Unit-Tests im Wege stehen, musste ein Weg gefunden werden sie zu umgehen. Dazu wurden Fake-Objekte eingeführt. -Beispiel aus: `TestMealPlan#setUp` +Möglich ist das durch die Nutzung eines `OutputService`-Interfaces, das die Konsolenausgabe abstrahiert. So ist es möglich, dass Code der inneren Schichten abstrakt mit Code der äußeren Schichten im Sinne der Clean-Architecture interagieren kann. Das `OutputService` bietet einige Funktionalitäten, um Konsolenausgaben auf verschiedenen Art und Weisen auszugeben, wie im UML-Diagram ersichtlich ist. Da für die automatisierten Tests zunächst keine Konsolenausgaben benötigt werden, wurden die eigentlichen Implementierungen in der `OutputServiceFake`-Klasse leer gelassen. -```java - @BeforeEach -public void setUp(){ - start=LocalDate.now(); - end=start.plusDays(5); - int totalMeals=start.until(end).getDays(); - - List meals=new ArrayList<>(); - for(int i=0;i + + getRecipe(id: String): Recipe +} + +class RecipeFileRepositoryFake { + + RecipeFileRepository(recipesFolder: File) + + saveRecipe(recipe: Recipe) + + deleteRecipe(recipe: Recipe): boolean + + getRecipes(): List + + getRecipe(id: String): Recipe +} + +RecipeRepository <|.. RecipeFileRepositoryFake + +@enduml \ No newline at end of file From 017dd0c1414acdef329bb4b4db1334af98e14a0e Mon Sep 17 00:00:00 2001 From: cuvar Date: Thu, 6 Apr 2023 11:42:07 +0200 Subject: [PATCH 28/39] docs: fakes 2 --- README.md | 8 +++++--- uml/fake-mock-2.iuml | 19 +++++++------------ 2 files changed, 12 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 8b7a899..7e9b32d 100644 --- a/README.md +++ b/README.md @@ -750,13 +750,15 @@ Die `OutputServiceFake`-Klasse ist ein Fake-Objekt, das die Ausgabe an die Konso Möglich ist das durch die Nutzung eines `OutputService`-Interfaces, das die Konsolenausgabe abstrahiert. So ist es möglich, dass Code der inneren Schichten abstrakt mit Code der äußeren Schichten im Sinne der Clean-Architecture interagieren kann. Das `OutputService` bietet einige Funktionalitäten, um Konsolenausgaben auf verschiedenen Art und Weisen auszugeben, wie im UML-Diagram ersichtlich ist. Da für die automatisierten Tests zunächst keine Konsolenausgaben benötigt werden, wurden die eigentlichen Implementierungen in der `OutputServiceFake`-Klasse leer gelassen. -#### Fake-Objekt 1: `RecipeFileRepositoryFake` +#### Fake-Objekt 1: `ConsoleInputReaderFake` ![Fakes und Mocks Beispiel 2 UML](http://www.plantuml.com/plantuml/proxy?cache=no&src=https://raw.githubusercontent.com/ncryptedV1/AutoChef/docs/uml/fake-mock-2.iuml) -[//]: # (Ein zweiter Aspekt der ein Fake-Objekt nutzt, ist die Persistenzverwaltung der Anwendung. Im Laufe der Interaktion mit dem Nutzer, werden Rezepte aus einem Dateispeicher gelesen. Den eigentlichen Zugriff auf die Datei übernimmt die `RecipeFileRepositoryFake`-Klasse. FÜr automatisierte Unit-Tests Ein Dateizugriff ) +Ein zweiter Aspekt der ein Fake-Objekt nutzt, ist die Eingaben von Daten durch den Nutzer. Speziell ist damit die Funktionalität der `ConsoleInputReader`-Klasse gemeint. Diese Klasse wird lediglich als Teil der `DialogInputParser`-Klasse genutzt. Dort werden Daten des Benutzers über die Konsole durch die `ConsoleInputReader`-Klasse eingelesen. Im zweiten Schritt analysiert und validiert die `DialogInputParser`-Klasse die Eingabe um sie weiter verwenden zu können. -_[TODO: Analyse und Begründung für Einsatz] +Die Benutzung der Konsole ist jedoch hinderlich, wenn automatisierte Tests genutzt werden. Um die Funktionalitäten der `DialogInputParser`-Klasse testen zu können, muss also ein Fake für die `ConsoleInputReader`-Klasse genutzt werden. Die `ConsoleInputReaderFake`-Klasse hat genau diese Verantwortung. Genauso wie die `ConsoleInputReader`-Klasse, greift der Fake auf ein Interface namens `InputReader` zu. Dieses Interface bietet als einzige Funktionlität eine `readLine`-Methode, die Inhalte des Benutzers einliest. + +Der Fake soll auf eine Art und Weise funktionieren, dass er Daten, die hineingegeben werden genauso wieder zurückgegeben werden. Da die `readLine`-Methode laut Interface aber keine Parameter erwartet, wurde ein zusätzlicher Konstruktor hinzugefügt. Wie im UML ersichtlich, nimmt der Konstruktor einen String auf, der in der `content`-Variable. Intern wird der Inhalt dieser Variable lediglich zurückgegeben. So ist es möglich einen Fake zu verwenden und gleichzeitig effektiv zu testen. ## 6. Domain Driven Design diff --git a/uml/fake-mock-2.iuml b/uml/fake-mock-2.iuml index 58bfe2e..59abadd 100644 --- a/uml/fake-mock-2.iuml +++ b/uml/fake-mock-2.iuml @@ -2,21 +2,16 @@ left to right direction -interface RecipeRepository { - + saveRecipe(recipe: Recipe) - + deleteRecipe(recipe: Recipe): boolean - + getRecipes(): List - + getRecipe(id: String): Recipe +interface InputReader { + + readLine(): String } -class RecipeFileRepositoryFake { - + RecipeFileRepository(recipesFolder: File) - + saveRecipe(recipe: Recipe) - + deleteRecipe(recipe: Recipe): boolean - + getRecipes(): List - + getRecipe(id: String): Recipe +class ConsoleInputReaderFake { + - content: String + + ConsoleInputReaderFake(content: String) + + readLine(): String } -RecipeRepository <|.. RecipeFileRepositoryFake +InputReader <|.. ConsoleInputReaderFake @enduml \ No newline at end of file From 20b4218683bd6d785fd7bdea55900bdf6ace41c7 Mon Sep 17 00:00:00 2001 From: cuvar Date: Sun, 9 Apr 2023 10:38:31 +0200 Subject: [PATCH 29/39] docs: remove instructions --- README.md | 29 ++--------------------------- 1 file changed, 2 insertions(+), 27 deletions(-) diff --git a/README.md b/README.md index 7e9b32d..ad35574 100644 --- a/README.md +++ b/README.md @@ -8,31 +8,6 @@ Martrikelnummer: XXX Abgabedatum: 28. Mai 2023 -## Allgemeine Anmerkungen - -- es darf nicht auf andere Kapitel als Leistungsnachweis verwiesen werden (z.B. in der Form “XY - wurde schon in Kapitel 2 behandelt, daher hier keine Ausführung”) -- alles muss in UTF-8 codiert sein (Text und Code) -- sollten mündliche Aussagen den schriftlichen Aufgaben widersprechen, gelten die schriftlichen - Aufgaben (ggf. an Anpassung der schriftlichen Aufgaben erinnern!) -- alles muss ins Repository (Code, Ausarbeitung und alles was damit zusammenhängt) -- die Beispiele sollten wenn möglich vom aktuellen Stand genommen werden - - finden sich dort keine entsprechenden Beispiele, dürfen auch ältere Commits unter Verweis auf - den Commit verwendet werden - - Ausnahme: beim Kapitel “Refactoring” darf von vorne herein aus allen Ständen frei gewählt - werden (mit Verweis auf den entsprechenden Commit) -- falls verlangte Negativ-Beispiele nicht vorhanden sind, müssen entsprechend mehr Positiv-Beispiele - gebracht werden - - Achtung: werden im Code entsprechende Negativ-Beispiele gefunden, gibt es keine Punkte für die - zusätzlichen Positiv-Beispiele - - Beispiel: “Nennen Sie jeweils eine Klasse, die das SRP einhält bzw. verletzt.” - - -> Antwort: Es gibt keine Klasse, die SRP verletzt, daher hier 2 Klassen, die SRP - einhalten: [Klasse 1], [Klasse 2] - - -> Bewertung: falls im Code tatsächlich keine Klasse das SRP verletzt: volle Punktzahl ODER - falls im Code mind. eine Klasse SRP verletzt: halbe Punktzahl -- verlangte Positiv-Beispiele müssen gebracht werden -- Code-Beispiel = Code in das Dokument kopieren - ## Inhaltsverzeichnis - [Einführung](#1-einführung) @@ -68,8 +43,8 @@ Abgabedatum: 28. Mai 2023 - [Code Smells](#71-code-smells) - [2 Refactorings](#72-2-refactorings) - [Entwurfsmuster](#8-entwurfsmuster) - - [Entwurfsmuster: Name](#81-entwurfsmuster-name) - - [Entwurfsmuster: Name](#82-entwurfsmuster-name) + - [Entwurfsmuster: Facade](#81-entwurfsmuster-facade) + - [Entwurfsmuster: Builder](#82-entwurfsmuster-builder) ## 1. Einführung From 48f71e99b57b2f0736047381a67adf1ad122d112 Mon Sep 17 00:00:00 2001 From: cuvar Date: Sun, 9 Apr 2023 10:47:20 +0200 Subject: [PATCH 30/39] docs: review chapter 1 --- README.md | 34 +++++++++++++++++++--------------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index ad35574..3c9e244 100644 --- a/README.md +++ b/README.md @@ -53,24 +53,30 @@ Abgabedatum: 28. Mai 2023 AutoChef ist eine Anwendung zur einfachen und effizienten Verwaltung und Erstellung von Essensplänen, sowie dazugehöriger Einkaufslisten. Zugrunde liegt dabei eine Datenbank an Rezepten. Diese ist beliebig erweiterbar durch eine [Chefkoch](chefkoch.de)-Integration. Diese ermöglicht es, -einen Link zu einem Rezept auf Chefkoch an die Anwendung zu übergeben, woraufhin diese das -entsprechende Rezept herunterlädt und persistiert. Diese Datenbank an Rezepten, sowie jeweilige +einen Link zu einem Rezept auf Chefkoch an die Anwendung zu übergeben, woraufhin das +entsprechende Rezept heruntergeladen und persistiert wird. Die Datenbank an Rezepten, sowie jeweilige Rezept-Details, können jederzeit eingesehen werden. Hauptfunktion ist jedoch die Generierung von Essensplänen. Dafür kann der Nutzer einen Zeitraum, sowie die Anzahl an Personen je Mahlzeit angeben -und die Anwendung generiert anhand der Rezept-Datenbank, einen zufälligen Essensplan. Für diesen +und die Anwendung generiert anhand der Rezept-Datenbank einen zufälligen Essensplan. Für diesen Essensplan wird ebenfalls eine Einkaufsliste generiert, die an die Anzahl an Personen angepasst ist. -Mit diesen Funktionalitäten ist es einfach möglich, seine Woche kulinarisch zu planen und die +Mit diesen Funktionalitäten ist es einfach möglich, eine Woche kulinarisch zu planen und die Einkaufliste für den Wocheneinkauf zu erstellen. -_[Was macht die Applikation, Wie funktioniert sie? Welches Problem löst sie/welchen Zweck hat sie?]_ - ### 1.2. Wie startet man die Applikation? Bei AutoChef handelt es sich um eine CLI-Anwendung, geschrieben in Java 19. Zum Starten wird daher -lediglich ein Desktop-Rechner mit **Java 19 aufwärts** benötigt. Die Anwendung kann dann über ein -Konsolenfenster mit dem Befehl `java -jar AutoChef.jar` gestartet werden. +lediglich ein Desktop-Rechner mit **Java 19 aufwärts** benötigt. Um die Anwendung starten zu können, +muss zunächst das GitHub-Repository geclont werden: -_[Wie startet man die Applikation? Welche Voraussetzungen werden benötigt? Schritt-für-Schritt-Anleitung]_ +```bash +git clone https://github.com/ncryptedV1/AutoChef +``` +Die Anwendung kann dann im nächsten Schritt über ein Konsolenfenster mit dem folgenden Befehl gestartet werden: + +```bash +cd AutoChef/ +java -jar AutoChef.jar +``` ### 1.3. Wie testet man die Applikation? @@ -78,10 +84,10 @@ Nach dem Start der Anwendung wird der Nutzer durch einen intuitiven Dialog-Proze geleitet. Der Nutzer interagiert dabei mit der Anwendung mittels des Konsolenfensters. Über dieses werden sowohl Informationen ausgegeben, als auch Eingaben vom Nutzer eingeholt. Der Dialog-Prozess ist so gestaltet, dass der Nutzer in der Regel mehrere nummerierte Optionen zur Auswahl hat und nur -die Nummer der gewünschten Option eingeben und mit Enter absenden muss. Für den Import von Rezepten -muss der Nutzer sich zuvor ein Rezept von Chefkoch aussuchen und im entsprechenden -Dialog-Prozess-Schritt den dazugehörigen Link einfügen. Zu Beginn der Nutzung ist die -Rezept-Datenbank noch leer, weshalb es sich anbietet anfangs ein paar Rezepte zu importieren. Erst +die Nummer der gewünschten Option eingeben und mit `Enter` absenden muss. Für den Import von Rezepten +muss sich der Nutzer zuvor ein Rezept von Chefkoch.de aussuchen und den dazugehörigen Link im +entsprechenden Dialog-Prozess-Schritt einfügen. Zu Beginn der Nutzung ist die +Rezept-Datenbank noch leer, weshalb es sich anbietet anfangs einige Rezepte zu importieren. Erst danach können die Funktionen der Rezept-Anzeige und Essensplan-Generierung sinnvoll genutzt werden. Hinweis: Zur Persistierung der Rezepte erstellt die Anwendung im aktuellen Arbeitsverzeichnis (des @@ -90,8 +96,6 @@ Arbeitsverzeichnis Ordner & Dateien zu erstellen, sowie in diese zu schreiben, k nicht ordnungsgemäß arbeiten und terminiert. Versuche in dem Fall die Anwendung mit erhöhten Berechtigungen zu starten oder in einem Arbeitsverzeichnis mit Schreibzugriff auszuführen. -_[Wie testet man die Applikation? Welche Voraussetzungen werden benötigt? Schritt-für-Schritt-Anleitung]_ - ## 2. Clean Architecture ### 2.1. Was ist Clean Architecture? From e720912714aae47012f6c2756a5bdfad60598e8d Mon Sep 17 00:00:00 2001 From: cuvar Date: Sun, 9 Apr 2023 11:01:04 +0200 Subject: [PATCH 31/39] docs: review chapter 2 --- README.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 3c9e244..3564c01 100644 --- a/README.md +++ b/README.md @@ -170,7 +170,7 @@ befindet. ![Schicht 1 UML](http://www.plantuml.com/plantuml/proxy?cache=no&src=https://raw.githubusercontent.com/ncryptedV1/AutoChef/docs/uml/layer-1.iuml) -Die `Recipe`-Klasse ist eine Entity im Sinne der Clean-Architecture, da sie die Entität eines +Die `Recipe`-Klasse ist eine Entity im Sinne der Clean-Architecture, da sie die Idee eines Rezeptes abbildet. Ein Rezept besteht aus folgenden Attributen: - `name: String`: Name des Rezeptes @@ -178,13 +178,13 @@ Rezeptes abbildet. Ein Rezept besteht aus folgenden Attributen: - `recipeSteps: List`: Liste an Zubereitungsschritten, die im Laufe des Rezeptes abgearbeitet werden müssen -Ein Rezept wird eineindeutig über eine ID indetifiziert. Die ID umfasst den Namen in Kleinschrift. +Ein Rezept wird eineindeutig über eine ID identifiziert. Die ID umfasst den Namen in Kleinschrift. Außerdem existieren für die Attribute und die ID jeweils Getter-Methoden und ein Konstruktor. -Damit liegt die Aufgabe der `Recipe`-Entität darin, ein Rezept semantisch im Code zu repräsentieren. +Damit liegt die Aufgabe der `Recipe`-Entity darin, ein Rezept semantisch im Code zu repräsentieren. Da das Konzept eines Rezept essenziell für die Domäne von Essensplänen ist, wurde es als Teil des Kernes der Anwendung aufgenommen. `Recipe` ist deshalb Teil der Schicht "Domain Code", da der -Domänencode ebenjene Entities bzw den Kern der Anwendung enthalten sollte. Außerdem ändert sich die +Domänencode ebenjene Entities bzw, den Kern der Anwendung enthalten sollte. Außerdem ändert sich die Modellierung eines Rezeptes selten, was ebenso dafür spricht, es in die Schicht "Domain Code" einzuordnen. @@ -194,16 +194,16 @@ einzuordnen. ![Schicht 2 UML](http://www.plantuml.com/plantuml/proxy?cache=no&src=https://raw.githubusercontent.com/ncryptedV1/AutoChef/docs/uml/layer-2.iuml) -Die Schicht des Applications Codes umfasst mehrere Klassen. Dabei soll die Klasse `DialogService` im +Die Schicht des Application-Codes umfasst mehrere Klassen. Dabei soll die Klasse `DialogService` im Fokus stehen. Im Kern ist der Dialog-Service für die Ablauflogik der Anwendung verantwortlich. Er verwaltet die Datenpersistenz über die Klassen `RecipeRepository` und `RecipeFileRepository`, ist aber gleichzeitig auch für die Nutzung von Benutzereingaben über die -Klassen `ConsoleInputReader`, `ConsoleInputParser` und `ConsoleOutputService` verantwortlich. Damit +Klassen `InputParser` und `OutputService` verantwortlich. Damit ist er die Schnittstelle zwischen den einzelnen Verantwortungsbereichen der Anwendung. -Im Allgemeinen startet er den Dialog mit dem Benutzer, organisiert die Generierung von Essensplänen +Im Allgemeinen startet der Service den Dialog mit dem Benutzer, organisiert die Generierung von Essensplänen und gibt dem Benutzer die Möglichkeit Rezepte hinzuzufügen. In diesem Sinne nimmt er die Rolle eines "Controllers" ein. Für andere Anwendungen wie etwa eine Web-Anwendung würde eine andere Funktionalität erwartet werden. Der Dialog-Service ist speziell für den Anwendungsfall einer From 46bca282cec7a92b53a7c73da3c7982e6ca38d56 Mon Sep 17 00:00:00 2001 From: cuvar Date: Sun, 9 Apr 2023 11:15:02 +0200 Subject: [PATCH 32/39] docs: review chapter 3+4 --- README.md | 50 +++++++++++++++++++++++++------------------------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/README.md b/README.md index 3564c01..e95bbfd 100644 --- a/README.md +++ b/README.md @@ -222,12 +222,12 @@ gewählte Klasse: `Quantity` ![Single Responsibility positives Beispiel UML](http://www.plantuml.com/plantuml/proxy?cache=no&src=https://raw.githubusercontent.com/ncryptedV1/AutoChef/docs/uml/single-responsibility-pos.iuml) -Die `Quantity`-Klasse bildet eine "Menge" oder "Anzahl" ab. Es ist relevant für die Menge von +Die `Quantity`-Klasse bildet eine "Menge" oder "Anzahl" ab. Das ist relevant für die Menge von Zutaten in einem Rezept. Die Klasse ist maßgeblich definiert durch ihr einziges Attribute `value`, das eine Menge als `double` repräsentiert. Zusätzlich dazu kann über die Methode `multiply` eine `Quantity`-Instanz mit einem Wert multipliziert werden, was wieder eine neue `Quantity`-Instanz zurückgibt. `Quantity` ist vor allem relevant im Kontext von `GroceryItem`, da dort beschrieben -wird, wie viel von einem `Ingredient` benötigt wird. +wird, wie viel von einer `Ingredient`-Instanz benötigt wird. Die Verantwortung der Klasse liegt demnach darin, semantisch eine "Menge" abzubilden. Daneben besitzt sie keine weiteren Verantwortlichkeiten. @@ -246,7 +246,7 @@ der Dialog-Service jedwede Logik für den Ablauf der Anwendung: - es startet den Dialog mit dem Benutzer - organisiert die Generierung von Essensplänen und - gibt dem Benutzer die Möglichkeit Rezepte hinzuzufügen. - Der Dialog-Service ist speziell für den Anwendungsfall einer CLI-Anwendung definiert. +Der Dialog-Service ist speziell für den Anwendungsfall einer CLI-Anwendung definiert. Der Dialog-Service hat jedoch mehrere Verantwortlichkeiten und bricht damit das SRP: @@ -267,7 +267,7 @@ während die `startMealPlanGeneration` lediglich eine verwaltende Rolle einnehme #### Positiv-Beispiel -gewählte Klassen: `RecipeFileRepository` mit Interface `RecipeRepository` +gewählte Klasse: `RecipeFileRepository` mit Interface `RecipeRepository` ![Open-Closed positives Beispiel UML](http://www.plantuml.com/plantuml/proxy?cache=no&src=https://raw.githubusercontent.com/ncryptedV1/AutoChef/docs/uml/open-closed-pos.iuml) @@ -277,9 +277,9 @@ Die `RecipeFileRepository`-Klasse implementiert diese Methoden für den Fall von Dateien. Da hier ein Interface verwendet wird, ist es leicht möglich neue Funktionalität hinzuzufügen. Dazu -muss lediglich eine neuer Methodekopf im Interface definiert werden. Die dazugehörige Methode muss +muss lediglich ein neuer Methodekopf im Interface definiert werden. Die dazugehörige Methode muss in allen Klassen, die das Interface implementieren, hinzugefügt werden. Damit ist die "Open" -Eigenschaft des OCP erfüllt. Auf der anderen Seite sollte Code "closed" gegenüber Modifikationen +Eigenschaft des OCP erfüllt. Auf der anderen Seite sollte Code "closed" (geschlossen) gegenüber Modifikationen sein. Das ist ebenso durch die Verwendung eines Interfaces erfüllt. Das Interface bestimmt die Funktionalitäten der Klasse. @@ -368,7 +368,7 @@ Klasse verbessert. ![Kopplung negatives Beispiel UML](http://www.plantuml.com/plantuml/proxy?cache=no&src=https://raw.githubusercontent.com/ncryptedV1/AutoChef/docs/uml/coupling-neg.iuml) -Diese Klasse ist dafür zuständig, Rezeptinformationen von der Chefkoch-Website abzurufen und sie in eine Rezept-Instanz umzuwandeln. Sie ist von `WebsiteFetcher`, `Recipe`, `GroceryList` und `RecipeStep` abhängig. Durch die Abhängigkeit von `WebsiteFetcher` weist die Klasse `ChefkochRecipeFetcher` eine hohe Kopplung auf, da sie direkt die statische Methode getWebsiteBody() aufruft. Dies könnte gelöst werden, indem man eine Schnittstelle für das Abrufen von Webseiten erstellt und diese Schnittstelle von der `ChefkochRecipeFetcher`-Klasse verwendet. Auf diese Weise könnten verschiedene Implementierungen zum Abrufen von Webseiten ausgetauscht werden, was die Kopplung verringert. +Diese Klasse ist dafür zuständig, Rezeptinformationen von der Chefkoch-Website abzurufen und sie in eine Rezept-Instanz umzuwandeln. Sie ist von `WebsiteFetcher`, `Recipe`, `GroceryList` und `RecipeStep` abhängig. Durch die Abhängigkeit von `WebsiteFetcher` weist die Klasse `ChefkochRecipeFetcher` eine hohe Kopplung auf, da sie direkt die statische Methode `getWebsiteBody` aufruft. Dies könnte gelöst werden, indem man eine Schnittstelle für das Abrufen von Webseiten erstellt und diese Schnittstelle von der `ChefkochRecipeFetcher`-Klasse verwendet. Auf diese Weise könnten verschiedene Implementierungen zum Abrufen von Webseiten ausgetauscht werden, was die Kopplung verringert. ![Kopplung negatives Beispiel verbessert UML](http://www.plantuml.com/plantuml/proxy?cache=no&src=https://raw.githubusercontent.com/ncryptedV1/AutoChef/docs/uml/coupling-neg-better.iuml) @@ -379,7 +379,7 @@ Da eine Klasse gefordert wurde, die nicht bereits in einem vorigen Kapitel behan ![Kopplung positives Beispiel 2 UML](http://www.plantuml.com/plantuml/proxy?cache=no&src=https://raw.githubusercontent.com/ncryptedV1/AutoChef/docs/uml/coupling-pos-2.iuml) -Die `RecipeRepository` Klasse ist ein Interface, das die Methoden für den Zugriff auf Rezept-Entitäten definiert. Sie ist lediglich von `Recipe` abhängig. Daher ist es ein weiteres Beispiel für geringe Kopplung. Es definiert lediglich die benötigten Methoden für den Zugriff auf Rezept-Entitäten, ohne sich auf eine spezifische Implementierung festzulegen. Die Implementierung von `RecipeRepository` (z.B. `RecipeFileRepository`) kann dann von der Anwendungsentwicklung abhängig gemacht werden, ohne die gesamte Anwendung zu beeinflussen. +Die `RecipeRepository` Klasse ist ein Interface, das die Methoden für den Zugriff auf Rezept-Entities definiert. Sie ist lediglich von `Recipe` abhängig. Daher ist es ein weiteres Beispiel für geringe Kopplung. Es definiert lediglich die benötigten Methoden für den Zugriff auf Rezept-Entities, ohne sich auf eine spezifische Implementierung festzulegen. Die Implementierung von `RecipeRepository` (z.B. `RecipeFileRepository`) kann dann von der Anwendungsentwicklung abhängig gemacht werden, ohne die gesamte Anwendung zu beeinflussen. ### 4.2. Analyse GRASP: Hohe Kohäsion @@ -404,56 +404,56 @@ vorher: ```java public MealPlan(List meals,LocalDate start,LocalDate end){ - int days=start.until(end).getDays(); + int days = start.until(end).getDays(); if(meals.size()!=days){ - throw new IllegalArgumentException( - "Mahlzeiten-Plan spannt "+days+" Tage, es wurden allerdings nur "+meals.size() - +" Mahlzeiten übergeben"); + throw new IllegalArgumentException( + "Mahlzeiten-Plan spannt "+days+" Tage, es wurden allerdings nur "+meals.size() + +" Mahlzeiten übergeben"); } this.meals=meals; this.start=start; this.end=end; - } + } // ... -public int getDays(){ + public int getDays(){ return start.until(getEnd()).getDays(); - } + } ``` nachher: ```java public MealPlan(List meals,LocalDate start,LocalDate end){ - this.start=start; - this.end=end; + this.start = start; + this.end = end; int days=getDays(); if(meals.size()!=days){ - throw new IllegalArgumentException( - "Mahlzeiten-Plan spannt "+days+" Tage, es wurden allerdings nur "+meals.size() - +" Mahlzeiten übergeben"); + throw new IllegalArgumentException( + "Mahlzeiten-Plan spannt "+days+" Tage, es wurden allerdings nur "+meals.size() + +" Mahlzeiten übergeben"); } this.meals=meals; - } + } // ... -public int getDays(){ + public int getDays(){ return start.until(getEnd()).getDays(); - } + } ``` Die oben gezeigte Änderung ist ein kleines Beispiel zur Reduktion von Code-Duplikationen. Die -Methode `getDays` ware bereits vor dem Commit vorhanden. Sie dient dazu, die Anzahl an Tagen +Methode `getDays` war bereits vor dem Commit vorhanden. Sie dient dazu, die Anzahl an Tagen zwischen `start` und `end` zu berechnen. Vor dem Commit, wurde jedoch dieselbe Logik im Konstruktor in der 2. Zeile verwendet: ```java -int days=start.until(end).getDays(); +int days = start.until(end).getDays(); ``` Der Commit sorgte dafür, dass dieser Code durch einen Aufruf der `getDays` Methode ersetzt wurde. From 3e01784171c167723d7ed4bd99eb4aaa49b37b7a Mon Sep 17 00:00:00 2001 From: cuvar Date: Sun, 9 Apr 2023 11:30:43 +0200 Subject: [PATCH 33/39] docs: review chapter 5 --- README.md | 123 ++++++++++++++++++++++++++---------------------------- 1 file changed, 60 insertions(+), 63 deletions(-) diff --git a/README.md b/README.md index e95bbfd..173cc2d 100644 --- a/README.md +++ b/README.md @@ -465,18 +465,18 @@ genutzt wird: in der `getDays` Methode. Dadurch können Fehler vermieden werden, ### 5.1. 10 Unit Tests -| Unit Test | Beschreibung | -|---------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------| -| _TestUnit#testHashCodeTrue_ | testet, ob die `hashCode` Funktion der `Unit` Klasse, für zwei Objekte, den selben Hashcode korrekt zurückgibt zurückgibt | -| _TestUnit#testEqualsResSame_ | testet, ob die `equals` Methode der `Unit` Klasse zwei Unit Instanzen mit dem selben `value` als korrekt gleich vergleicht | -| _TestQuantity#testConstructorException_ | testet, ob der Konstruktur der `Quantity` Klasse fehlschlägt, sobald negative (invalide) Werte übergeben werden | -| _TestQuantity#testMultiply_ | testet, ob die `multiply` Methode der `Quantity` Klasse den Wert korrekt multipliziert | -| _TestGroceryItem#testEqualsResSelf_ | testet, ob die `equals` Methode der `GroceryItem` Klasse zwei Objekte mit dem selben `value` als gleich ansieht | -| _TestWebsiteFetcher#testGetWebsiteBodyInvalidUrl_ | testet, ob die `testGetWebsiteBodyInvalidUrl` Methode der `WebsiteFetcher` Klasse eine Exception wirft bei einer invaliden URL | -| _TestChefkochRecipeFetcher#testGetRecipe_ | testet, ob die `testGetRecipe` Methode der `ChefkochRecipeFetcher` Klasse die richtigen Inhalte für ein Chefkoch-Rezept aus dem Internet liefert | -| _TestRecipe#testConstructorHappyPath_ | testet, ob der Konstrutor der `Recipe` Klasse nicht `null` zurückgibt | -| _TestMeal#testGetGroceryList_ | testet, ob die `getGroceryList` Methode der `Meal` Klasse eine korrekt aggregierte Zutatenliste zurückgibt | -| _TestGroceryList#testAddItem_ | testet, ob die `addItem` Methode der `GroceryList` Klasse korrekt eine Itme zur GroceryList hinzufügt | +| Unit Test | Beschreibung | +|---------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------| +| _TestUnit#testHashCodeTrue_ | testet, ob die `hashCode` Funktion der `Unit` Klasse, für zwei Objekte, den selben Hashcode korrekt zurückgibt | +| _TestUnit#testEqualsResSame_ | testet, ob die `equals` Methode der `Unit` Klasse zwei Instanzen mit dem selben `value` als korrekt gleich vergleicht | +| _TestQuantity#testConstructorException_ | testet, ob der Konstruktur der `Quantity` Klasse fehlschlägt, sobald negative (invalide) Werte übergeben werden | +| _TestQuantity#testMultiply_ | testet, ob die `multiply` Methode der `Quantity` Klasse den Wert korrekt multipliziert | +| _TestGroceryItem#testEqualsResSelf_ | testet, ob die `equals` Methode der `GroceryItem` Klasse zwei Objekte mit dem selben `value` als gleich ansieht | +| _TestWebsiteFetcher#testGetWebsiteBodyInvalidUrl_ | testet, ob die `getWebsiteBodyInvalidUrl` Methode der `WebsiteFetcher` Klasse eine Exception wirft bei einer invaliden URL | +| _TestChefkochRecipeFetcher#testGetRecipe_ | testet, ob die `getRecipe` Methode der `ChefkochRecipeFetcher` Klasse die richtigen Inhalte für ein Chefkoch-Rezept aus dem Internet liefert | +| _TestRecipe#testConstructorHappyPath_ | testet, ob der Konstrutor der `Recipe` Klasse nicht `null` zurückgibt | +| _TestMeal#testGetGroceryList_ | testet, ob die `getGroceryList` Methode der `Meal` Klasse eine korrekt aggregierte Zutatenliste zurückgibt | +| _TestGroceryList#testAddItem_ | testet, ob die `addItem` Methode der `GroceryList` Klasse korrekt ein `GroceryItem` zur GroceryList hinzufügt | ### 5.2. ATRIP: Automatic @@ -503,9 +503,9 @@ Test-Klasse: `TestIngredient` // assert assertTrue(res); - } + } -@Test + @Test void testEqualsResSame(){ // arrange String value="banana"; @@ -517,9 +517,9 @@ Test-Klasse: `TestIngredient` // assert assertTrue(res); - } + } -@Test + @Test void testEqualsDifferent(){ // arrange Ingredient ingredient1=new Ingredient("banana"); @@ -530,9 +530,9 @@ Test-Klasse: `TestIngredient` // assert assertFalse(res); - } + } -@Test + @Test void testEqualsNull(){ // arrange Ingredient ingredient1=mock(Ingredient.class); @@ -542,9 +542,9 @@ Test-Klasse: `TestIngredient` // assert assertFalse(res); - } + } -@Test + @Test void testHashCodeTrue(){ // arrange String value="banana"; @@ -557,9 +557,9 @@ Test-Klasse: `TestIngredient` // assert assertEquals(code1,code2); - } + } -@Test + @Test void testHashCodeFalse(){ // arrange Ingredient ingredient1=new Ingredient("banana"); @@ -571,7 +571,7 @@ Test-Klasse: `TestIngredient` // assert assertNotEquals(code1,code2); - } + } ``` Diese Testklasse mit den dargestellten Methoden ist ein Positivbeispiel für "thorough testing". All @@ -594,39 +594,36 @@ zu testende Methode: `DialogService#startMealPlanGeneration` ```java private void startMealPlanGeneration(){ - currentState=DialogState.MEAL_PLAN_GENERATION; - - ConsoleOutputService.rawOut("Wir generieren jetzt zusammen einen Mahlzeiten-Plan. :D"); - LocalDate startDate=ConsoleInputParser.getDate(null,null, - "Wann soll der Plan beginnen? (DD.MM.YYYY)"); - LocalDate endDate=ConsoleInputParser.getDate(startDate,null, - "Bis wann soll der Plan gehen (exklusiv)? (DD.MM.YYYY)"); - int people=ConsoleInputParser.getInteger(1,99, - "Für wie viele Leute soll der Plan generiert werden?"); - int days=startDate.until(endDate).getDays(); - ConsoleOutputService.rawOut("Ok, ich generiere einen Plan für "+days+" Tage..."); - - List recipes=recipeRepository.getRecipes(); - List meals=new ArrayList<>(); - for(int i=0;i recipes = recipeRepository.getRecipes(); + for (int i = 0; i < days; i++) { + mealPlanBuilder.addMeal(startDate.plusDays(i), + new Meal(recipes.get(random.nextInt(recipes.size())), people)); } - MealPlan mealPlan=new MealPlan(meals,startDate,endDate); - - startPostMealPlanGeneration(mealPlan,recipes); - } + startPostMealPlanGeneration(mealPlanBuilder.build(), recipes); + } ``` Die hier gezeigt Methode `DialogService#startMealPlanGeneration` wurde nicht getestet, obwohl es -möglich wäre dies zu tun. Damit stellt sie ein Negativbeispiel für die "thorough testing" dar. Der -gezeigt Code startet die Generierung einer Essensplans. Dazu werden verschiedene Nutzereingaben -abgefordert, z.B. Start und Enddatum, Anzahl der Personen. Im Anschluss werden die Rezepte geladen -und mit einer zufälligen Teilmenge wird der Essensplan erstellt. Der Essensplan und die liste an -Rezepten werden im Anschluss an eine weitere Methode weitergegeben. +möglich wäre dies zu tun. Damit stellt sie ein Negativbeispiel für die "thorough testing" Eigenschaft dar. Der gezeigt Code startet die Generierung eines Essensplans. Dazu werden verschiedene Nutzereingaben abgefordert, z.B. Start- und Enddatum, Anzahl der Personen. Im Anschluss werden die Rezepte geladen und mit einer zufälligen Teilmenge wird der Essensplan erstellt. Der Essensplan und die Liste an Rezepten werden im Anschluss an eine weitere Methode übergegeben. Da die Tests für diese Methode vollständig fehlen, werden dementsprechend auch alle Pfade nicht -getestet. Dementsprechend kann nicht herausgefunden werden, wo sich logische Fehler befinden. +getestet. Deshalb kann nicht herausgefunden werden, wo sich logische Fehler befinden. ### 5.4. ATRIP: Professional @@ -649,13 +646,13 @@ Diese Testmethode testet die `getWebsiteBody` Methode der `WebsiteFetcher` Klass String, der eine invalide URL darstellt, in die zu testende Funktion übergeben. Anschließend wird die `getWebsiteBody` Methode aufgerufen und überprüft, ob die richtige Exception geworfen wird. -Diese Testmethode ist ein Positivbeispiel für professionelle Testklassen aus mehreren Gründen: +Diese Testmethode ist ein Positivbeispiel für professionelle Testmethoden aus mehreren Gründen: 1. Der Name der Testmethode beschreibt gut, was genau getestet wird. In diesem Fall die `getWebsiteBody` Methode bei Eingabe einer invaliden URL. 2. Die zugehörige Klasse wurde nur zu Testzwecken angelegt. -3. Im Gegensatz zu Getter- oder Setter-Methoden existiert Logik, die getestetet werden sollte. Ein - Test ist dementsprechend notwendig +3. Im Gegensatz zu Getter- oder Setter-Methoden existiert Logik, die getestet werden sollte. Ein + Test ist dementsprechend notwendig. #### negatives Beispiel @@ -664,23 +661,23 @@ Test-Methode: `TestUnit#getValue` ```java @Test void testGetValue(){ - // arrange - String expected="piece"; - Unit unit=new Unit(expected); + // arrange + String expected="piece"; + Unit unit=new Unit(expected); - // act - String res=unit.getValue(); + // act + String res=unit.getValue(); - // assert - assertEquals(expected,res); - } + // assert + assertEquals(expected,res); + } ``` Diese Testmethode testet die `getValue` Getter-Methode der `Unit` Klasse. Dabei wird ein `Unit` ValueObject angelegt mit einem initialen Wert. Das Ergebnis der `getValue` wird verglichen mit dem initialen Wert. Beide Werten sollten gleich sein. -Diese Klasse ist ein Negativbeispiel, da sie einen unnötigen Test darstellt. Getter Methoden sollten +Diese Klasse ist ein Negativbeispiel, da sie einen unnötigen Test darstellt. Getter-Methoden sollten nicht getestet werden. Des Weiteren enthält diese Methode keine komplexe Logik, die ein Testen erfordern würde. Es handelt sich hier um einen Test, "der nur wegen des Tests geschrieben wurde". Außerdem ist der Dokumentationswert der Methode nicht vorhanden. @@ -733,11 +730,11 @@ Möglich ist das durch die Nutzung eines `OutputService`-Interfaces, das die Kon ![Fakes und Mocks Beispiel 2 UML](http://www.plantuml.com/plantuml/proxy?cache=no&src=https://raw.githubusercontent.com/ncryptedV1/AutoChef/docs/uml/fake-mock-2.iuml) -Ein zweiter Aspekt der ein Fake-Objekt nutzt, ist die Eingaben von Daten durch den Nutzer. Speziell ist damit die Funktionalität der `ConsoleInputReader`-Klasse gemeint. Diese Klasse wird lediglich als Teil der `DialogInputParser`-Klasse genutzt. Dort werden Daten des Benutzers über die Konsole durch die `ConsoleInputReader`-Klasse eingelesen. Im zweiten Schritt analysiert und validiert die `DialogInputParser`-Klasse die Eingabe um sie weiter verwenden zu können. +Ein zweiter Aspekt, der ein Fake-Objekt nutzt, ist die Eingaben von Daten durch den Nutzer. Speziell ist damit die Funktionalität der `ConsoleInputReader`-Klasse gemeint. Diese Klasse wird lediglich als Teil der `DialogInputParser`-Klasse genutzt. Dort werden Daten des Benutzers über die Konsole durch die `ConsoleInputReader`-Klasse eingelesen. Im zweiten Schritt analysiert und validiert die `DialogInputParser`-Klasse die Eingabe um sie weiter verwenden zu können. Die Benutzung der Konsole ist jedoch hinderlich, wenn automatisierte Tests genutzt werden. Um die Funktionalitäten der `DialogInputParser`-Klasse testen zu können, muss also ein Fake für die `ConsoleInputReader`-Klasse genutzt werden. Die `ConsoleInputReaderFake`-Klasse hat genau diese Verantwortung. Genauso wie die `ConsoleInputReader`-Klasse, greift der Fake auf ein Interface namens `InputReader` zu. Dieses Interface bietet als einzige Funktionlität eine `readLine`-Methode, die Inhalte des Benutzers einliest. -Der Fake soll auf eine Art und Weise funktionieren, dass er Daten, die hineingegeben werden genauso wieder zurückgegeben werden. Da die `readLine`-Methode laut Interface aber keine Parameter erwartet, wurde ein zusätzlicher Konstruktor hinzugefügt. Wie im UML ersichtlich, nimmt der Konstruktor einen String auf, der in der `content`-Variable. Intern wird der Inhalt dieser Variable lediglich zurückgegeben. So ist es möglich einen Fake zu verwenden und gleichzeitig effektiv zu testen. +Der Fake soll auf eine Art und Weise funktionieren, dass er Daten, die hineingegeben werden genauso wieder zurückgegeben werden. Da die `readLine`-Methode laut Interface aber keine Parameter erwartet, wurde ein zusätzlicher Konstruktor hinzugefügt. Wie im UML ersichtlich, nimmt der Konstruktor einen String auf, der in der `content`-Variable gespeichert wird. Intern wird der Inhalt dieser Variable lediglich zurückgegeben. So ist es möglich einen Fake zu verwenden und gleichzeitig effektiv zu testen. ## 6. Domain Driven Design From 2eb9c48eb0413ad4818568c03d03451d0a19d280 Mon Sep 17 00:00:00 2001 From: cuvar Date: Sun, 9 Apr 2023 12:09:17 +0200 Subject: [PATCH 34/39] docs: review chapter 6 --- README.md | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 173cc2d..06d1b2b 100644 --- a/README.md +++ b/README.md @@ -755,7 +755,7 @@ zugehörige Klasse(n): `Recipe` Die `Recipe` Entity beschreibt ein semantisches Rezept. Wie das UML-Diagram zeigt, besteht ein Rezept aus einem Name, einer Liste von Zutaten (GroceryList) und einer Liste von Schritten zur -Zubereitung (RecipeStep). Ein Rezept wird eineindeutig über eine ID indetifiziert. In diesem Fall +Zubereitung (RecipeStep). Ein Rezept wird eineindeutig über eine ID identifiziert. In diesem Fall besteht die ID aus dem Namen in Kleinschrift. Haben also zwei Rezepte den selben Namen, werden sie als gleich angesehen. Weiterhin hat die `Recipe` Klasse mehrere Getter-Methoden für die einzelnen Attribute und die ID als auch einen Konstruktor. @@ -783,7 +783,7 @@ Namen (`getValue`) und für die ID (`getID`). Letztere, ist dabei jedoch nicht z Identifizierung im Sinne einer Entity anzusehen, sondern wird lediglich zum einfacheren Vergleich zweier `Ingredient`-Instanzen verwendet. Die ID setzt sich aus dem Namen in Kleinschrift zusammen. Zusätzlich existiert ein Konstruktor zur Erzeugung einer Ingredient-Instanz, bei der die Richtigkeit -des Namens überprüft wird. Ähnlich wie bei der `Recipe`-Klassen, muss ein Namen mindestens ein +des Namens überprüft wird. Ähnlich wie bei der `Recipe`-Klasse, muss ein Namen mindestens ein Zeichen enthalten, das keinem White-Space Zeichen entspricht. Ein `Ingredient` hat keinen Lebenszyklus und auch keine relevante Logik implementiert. Es dient @@ -797,14 +797,14 @@ zugehörige Klasse(n): `RecipeFileRepository` ![Repository Beispiel UML](http://www.plantuml.com/plantuml/proxy?cache=no&src=https://raw.githubusercontent.com/ncryptedV1/AutoChef/docs/uml/repository.iuml) +Repositories dienen als Vermittler zwischen Datenmodell und Domänenlogik. Sie werden genutzt, um +Daten zu speichern oder sie abzurufen aus ebendiesem Speicher. In diesem Projekt wurde ein +Repository `RecipeFileRepository` genutzt, um die Persistenz von Rezepten zu verwalten. +Das `RecipeFileRepository` besitzt als einziges Attribut eine Instanz der `File`-Klasse +namens `recipesFolder`, die angibt, wo im Dateisystem Rezepte gespeichert werden sollten. +Zusätzlich existieren Methoden zur Persistenzverwaltung, die von dem Interface `RecipeRepository` +implementiert werden: - `saveRecipe`: speichert ein gegebenes Rezept in einer Datei im Dateiort `recipesFolder` ab - Repositories dienen als Vermittler zwischen Datenmodell und Domänenlogik. Sie werden genutzt, um - Daten zu speichern oder sie abzurufen aus ebendiesen Speicher. In diesem Projekt wurde ein - Repository `RecipeFileRepository` genutzt, um die Persistenz von Rezepten zu verwalten. - Das `RecipeFileRepository` besitzt als einziges Attribut eine Instanz der `File`-Klasse - namens `recipesFolder`, die angibt, wo im Dateisystem Rezepte gespeichert werden sollten. - Zusätzlich existieren Methoden zur Persistenzverwaltung, die von dem Interface `RecipeRepository` - implementiert werden: - `deleteRecipe`: löscht ein gegebenes Rezept im Dateiort `recipesFolder` - `getRecipe`: liest ein anhand der ID definiertes Rezept im Dateiort `recipesFolder` ein - `getRecipes`: liest alle vorhandenen Rezepte im Dateiort `recipesFolder` ein @@ -817,7 +817,7 @@ es kann keine Instanz angelegt werden. Um die Persistenzverwaltung gründlich und sauber von der Domänenlogik zu trennen, wurde dieses Repository eingesetzt. Mit Nutzung des Interfaces, kann sichergestellt werden, dass beide Elemente getrennt bleiben. Zusätzlich ermöglicht der Einsatz eines Repositories, Veränderungen an der -Persistenzverwaltung vorzunehmen, ob auf die Domänenlogik eingreifen zu müsssen. +Persistenzverwaltung vorzunehmen, ohne auf die Domänenlogik eingreifen zu müsssen. ### 6.5. Aggregates @@ -825,11 +825,11 @@ zugehörige Klasse(n): `Meal` ![Aggregate Beispiel UML](http://www.plantuml.com/plantuml/proxy?cache=no&src=https://raw.githubusercontent.com/ncryptedV1/AutoChef/docs/uml/aggregate.iuml) -In diesem Projekt wurde die `Meal`-Klasse als Aggregate ausgewählt.Die `Meal`-Klasse fasst logsiches +In diesem Projekt wurde die `Meal`-Klasse als Aggregate ausgewählt. Die `Meal`-Klasse fasst logisches Verhalten verschiedener Elemente zusammen. Es definiert sich durch ein Rezept `recipe` und einem Integer `adjustedNumberOfPeople`, das darstellt, auf wie viele Personen die Zutatenmenge des Rezeptes angepasst werden soll. Der Konstruktor enthält keine weitere Logik zur Überprüfung der -Attribute. Des Weiteren exisiteren folge Methoden: +Attribute. Des Weiteren existieren folgende Methoden: - `getRecipe`: eine Getter-Methode für `recipe` - `setRecipe`: eine Setter-Methode für `recipe` From 3c4f15208661b50d0a64bc4e73f87a029d74905d Mon Sep 17 00:00:00 2001 From: cuvar Date: Sun, 9 Apr 2023 12:14:57 +0200 Subject: [PATCH 35/39] docs: review chapter 7 --- README.md | 142 ++++++++++++++++++++++++++---------------------------- 1 file changed, 68 insertions(+), 74 deletions(-) diff --git a/README.md b/README.md index 06d1b2b..fc638c2 100644 --- a/README.md +++ b/README.md @@ -857,47 +857,47 @@ vorher: ```java public MealPlan(List meals,LocalDate start,LocalDate end){ - int days=start.until(end).getDays(); + int days = start.until(end).getDays(); if(meals.size()!=days){ - throw new IllegalArgumentException( - "Mahlzeiten-Plan spannt "+days+" Tage, es wurden allerdings nur "+meals.size() - +" Mahlzeiten übergeben"); + throw new IllegalArgumentException( + "Mahlzeiten-Plan spannt "+days+" Tage, es wurden allerdings nur "+meals.size() + +" Mahlzeiten übergeben"); } this.meals=meals; this.start=start; this.end=end; - } + } -// ... + // ... -public int getDays(){ + public int getDays(){ return start.until(getEnd()).getDays(); - } + } ``` nachher: ```java public MealPlan(List meals,LocalDate start,LocalDate end){ - this.start=start; - this.end=end; + this.start = start; + this.end = end; - int days=getDays(); + int days = getDays(); if(meals.size()!=days){ - throw new IllegalArgumentException( - "Mahlzeiten-Plan spannt "+days+" Tage, es wurden allerdings nur "+meals.size() - +" Mahlzeiten übergeben"); + throw new IllegalArgumentException( + "Mahlzeiten-Plan spannt "+days+" Tage, es wurden allerdings nur "+meals.size() + +" Mahlzeiten übergeben"); } this.meals=meals; - } + } -// ... + // ... -public int getDays(){ + public int getDays(){ return start.until(getEnd()).getDays(); - } + } ``` Dieser Code Smell umfasst eine Code Duplication in der `MealPlan`-Klasse. Relevant ist hierbei vor @@ -907,7 +907,7 @@ allem Zeile 2. Hier wird die folgende Berechnung ausgeführt: int days = start.until(end).getDays(); ``` -`days` gibt dabei die Anzahl an Tagen zwischen `start` udn `end` an. Was dabei aber auffällt, ist, +`days` gibt dabei die Anzahl an Tagen zwischen `start` und `end` an. Was dabei aber auffällt, ist, dass diese Logik bereits zu anderen Zwecken in der `getDays`-Methode vorhanden ist. Diese Methode wird verwendet, um auch von außen Zugriff auf die Anzahl der Tage zu haben. Damit kann hier eine kleine Dopplung vorgefunden werden. Diese wurde nun, wie im zweiten Code-Beispiel ersichtlich, @@ -923,58 +923,58 @@ Code Smell 2: Large Method vorher: ```java - public static void main(String[]args){ - logger.info("Starting..."); - - // setup groceries - GroceryItem item1=new GroceryItem(new Ingredient("Banane"),new Quantity(1),Unit.GRAM); - GroceryItem item2= - new GroceryItem(new Ingredient("Pineapple"),new Quantity(0.2),Unit.KILOGRAM); - GroceryItem item3= - new GroceryItem(new Ingredient("Orange juice"),new Quantity(0.1),Unit.LITER); - GroceryItem item4=new GroceryItem(new Ingredient("Apple"),new Quantity(1),Unit.PIECE); - GroceryItem item5= - new GroceryItem(new Ingredient("Nutella"),new Quantity(2),Unit.TABLESPOON); - - // setup recipe steps - RecipeStep recipeStep1= - new RecipeStep(1,"Cut some banana, apple and pineapple as the basis for this salad.", - item1,item2,item3); - RecipeStep recipeStep2=new RecipeStep(2,"Add orange juice to make it more juicy.",item4); - RecipeStep recipeStep3= - new RecipeStep(3,"Add a bit of Nutella for making it look beautiful.",item5); - - // setup recipe for - Recipe recipe1=new Recipe("Sugar-free fruit salad",recipeStep1,recipeStep2,recipeStep3); - - // setup meal - Meal meal1=new Meal(recipe1,2); - // setup meal plan - List mealList=Arrays.asList(meal1); - LocalDate startDate=LocalDate.of(2023,2,20); - LocalDate endDate=LocalDate.of(2023,2,26); - MealPlan mealPlan=new MealPlan(mealList,startDate,endDate); - - logger.info(mealPlan.toString()); - } +public static void main(String[] args){ + logger.info("Starting..."); + + // setup groceries + GroceryItem item1=new GroceryItem(new Ingredient("Banane"),new Quantity(1),Unit.GRAM); + GroceryItem item2= + new GroceryItem(new Ingredient("Pineapple"),new Quantity(0.2),Unit.KILOGRAM); + GroceryItem item3= + new GroceryItem(new Ingredient("Orange juice"),new Quantity(0.1),Unit.LITER); + GroceryItem item4=new GroceryItem(new Ingredient("Apple"),new Quantity(1),Unit.PIECE); + GroceryItem item5= + new GroceryItem(new Ingredient("Nutella"),new Quantity(2),Unit.TABLESPOON); + + // setup recipe steps + RecipeStep recipeStep1= + new RecipeStep(1,"Cut some banana, apple and pineapple as the basis for this salad.", + item1,item2,item3); + RecipeStep recipeStep2=new RecipeStep(2,"Add orange juice to make it more juicy.",item4); + RecipeStep recipeStep3= + new RecipeStep(3,"Add a bit of Nutella for making it look beautiful.",item5); + + // setup recipe for + Recipe recipe1=new Recipe("Sugar-free fruit salad",recipeStep1,recipeStep2,recipeStep3); + + // setup meal + Meal meal1=new Meal(recipe1,2); + // setup meal plan + List mealList=Arrays.asList(meal1); + LocalDate startDate=LocalDate.of(2023,2,20); + LocalDate endDate=LocalDate.of(2023,2,26); + MealPlan mealPlan=new MealPlan(mealList,startDate,endDate); + + logger.info(mealPlan.toString()); +} ``` nachher: ```java - public static void main(String[]args){ - logger.info("Starting..."); - - // generate mock data - List groceryItems=MockService.generateGroceryItems(); - List recipeSteps=MockService.generateRecipeSteps(groceryItems); - Recipe recipe=MockService.generateRecipe(recipeSteps); - Meal meal=MockService.generateMeal(recipe); - List meals=Arrays.asList(meal); - MealPlan mealPlan=MockService.generateMealPlan(meals); - - logger.info(mealPlan.toString()); - } +public static void main(String[]args){ + logger.info("Starting..."); + + // generate mock data + List groceryItems=MockService.generateGroceryItems(); + List recipeSteps=MockService.generateRecipeSteps(groceryItems); + Recipe recipe=MockService.generateRecipe(recipeSteps); + Meal meal=MockService.generateMeal(recipe); + List meals=Arrays.asList(meal); + MealPlan mealPlan=MockService.generateMealPlan(meals); + + logger.info(mealPlan.toString()); +} ``` ```java @@ -1029,9 +1029,7 @@ public class MockService { ``` Der zweite Code Smell befasst sich mit der Auslagerung der `MockService`-Klasse, die im aktuellen -Stand nicht mehr vorhanden ist. In diesem Fall ist der Code Smell eine _Large Method_: die `main` --Methode. Sie umfasst die Erstellung einiger Objekte, damit die Anwendung mit Testdaten getestet -werden konnte. Da es sich hier um vergleichsweise viele Objekte handelt, ist die Methode groß. +Stand nicht mehr vorhanden ist. In diesem Fall ist der Code Smell eine _Large Method_: die `main`-Methode. Sie umfasst die Erstellung einiger Objekte, damit die Anwendung mit Testdaten getestet werden konnte. Da es sich hier um vergleichsweise viele Objekte handelt, ist die Methode groß. Um das zu beheben, wurde die vorhandene Logik extrahiert in eine `MockService`-Klasse. Diese Klasse ist nun dafür verantwortlich, Testobjekte und -daten zu generieren und über Methodenaufrufe @@ -1040,10 +1038,6 @@ Code-Smells zeigt. ### 7.2. 2 Refactorings -[//]: # (Refactoring: Extract Method) - -[//]: # ([Commit](https://github.com/ncryptedV1/AutoChef/commit/8a66d9a6405c9ca4cf710490ae06eb810ea87978#diff-a914343e6af07030cd7b3b51d56fc5e0f541d6bd350ee0ef11324a9bc5aae66f)) - #### Refactoring 1 Refactoring 2: Extract Class (ConsoleOutputService) @@ -1061,7 +1055,7 @@ Das in den UML-Diagrammen gezeigte Refactoring ist das _Extract Class_-Refactori wurde um die `ConsoleOutputService`-Klasse zu erschaffen. Der Commit und das erste UML-Diagramm zeigen, dass die `AutoChef`-Klasse mit der `main`-Methode zunächst auch für die Konsolenausgabe verantwortlich war. Um das aber sauber voneinander zu trennen, und die Komplexität der `main` --Methode so einfach wie möglich zu halten, wurde die Logik der Consolenausgabe in eine separate +-Methode so einfach wie möglich zu halten, wurde die Logik der Konsolenausgabe in eine separate Klasse ausgelagert. Die neu geschaffene Klasse `ConsoleOutputService` umfasst neben der Logik, die vorher in der `main`-Methode existierte, auch noch weitere Funktionalitäten. Das zeigt auch das zweite UML-Diagramm. Zu sehen ist hier die `ConsoleOutputService`-Klasse mit ihren verschiedenen From 7139a4cf3cf477fc79cada4b35fd58171b9ea1c6 Mon Sep 17 00:00:00 2001 From: cuvar Date: Sun, 9 Apr 2023 12:19:16 +0200 Subject: [PATCH 36/39] docs: review chapter 8 --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index fc638c2..fba0662 100644 --- a/README.md +++ b/README.md @@ -1112,8 +1112,8 @@ Die `ConsoleOutputService`-Klasse kapselt die Details der `Logger`-Konfiguration ![Entwurfstmuster Builder Beispiel UML](http://www.plantuml.com/plantuml/proxy?cache=no&src=https://raw.githubusercontent.com/ncryptedV1/AutoChef/docs/uml/design-pattern-builder.iuml) -Der `MealPlanBuilder` ist ein Beispiel für das Entwurfsmuster Builder, da er eine einfache Schnittstelle bietet, um komplexe Objekte schrittweise aufzubauen. Mithilfe von Methoden wie `setStartDate()`, `setEndDate()` und `addMeal()` kann der Verwender des `MealPlanBuilder` einen Mahlzeiten-Plan definieren, ohne dabei die genaue Implementierung des `MealPlan` kennen zu müssen. +Der `MealPlanBuilder` ist ein Beispiel für das Entwurfsmuster Builder, da er eine einfache Schnittstelle bietet, um komplexe Objekte schrittweise aufzubauen. Mithilfe von Methoden wie `setStartDate`, `setEndDate` und `addMeal` kann der Verwender des `MealPlanBuilder` einen Mahlzeiten-Plan definieren, ohne dabei die genaue Implementierung des `MealPlan` kennen zu müssen. -Die `build()` Methode ist das Herzstück des Builders und erzeugt das fertige Objekt. Dabei wird die Konsistenz des Objekts sichergestellt und gegebenenfalls eine `IllegalStateException` geworfen, wenn zum Beispiel ein Start- oder Enddatum fehlt oder nicht alle Mahlzeiten definiert sind. +Die `build`-Methode ist das Herzstück des Builders und erzeugt das fertige Objekt. Dabei wird die Konsistenz des Objekts sichergestellt und gegebenenfalls eine `IllegalStateException` geworfen, wenn zum Beispiel ein Start- oder Enddatum fehlt oder nicht alle Mahlzeiten definiert sind. -Der Builder ist ein nützliches Muster, wenn die Erstellung von Objekten viele Parameter erfordert oder komplex ist. Indem er den Verwendet von der Komplexität des Aufbaus des Objekts abschirmt, ermöglicht er eine klare und einfache API und stellt sicher, dass die erstellten Objekte korrekt initialisiert sind. +Der Builder ist ein nützliches Muster, wenn die Erstellung von Objekten viele Parameter erfordert oder komplex ist. Indem er den Verwender von der Komplexität des Aufbaus des Objekts abschirmt, ermöglicht er eine klare und einfache API und stellt sicher, dass die erstellten Objekte korrekt initialisiert sind. From a157d8830a2261fbcabb149b5b6a9cbc6e8a4317 Mon Sep 17 00:00:00 2001 From: cuvar Date: Sun, 9 Apr 2023 12:20:09 +0200 Subject: [PATCH 37/39] docs: unify 'value object' --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index fba0662..439cc7f 100644 --- a/README.md +++ b/README.md @@ -674,7 +674,7 @@ Test-Methode: `TestUnit#getValue` ``` Diese Testmethode testet die `getValue` Getter-Methode der `Unit` Klasse. Dabei wird ein `Unit` -ValueObject angelegt mit einem initialen Wert. Das Ergebnis der `getValue` wird verglichen mit dem +Value Object angelegt mit einem initialen Wert. Das Ergebnis der `getValue` wird verglichen mit dem initialen Wert. Beide Werten sollten gleich sein. Diese Klasse ist ein Negativbeispiel, da sie einen unnötigen Test darstellt. Getter-Methoden sollten From 7c9e4cde08f6e90d26aab1602732a92b7cce68b8 Mon Sep 17 00:00:00 2001 From: cuvar Date: Sun, 9 Apr 2023 12:25:59 +0200 Subject: [PATCH 38/39] docs: matrikelnummer luca --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 439cc7f..fb58945 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ Name: Schirmer, Oliver \ Martrikelnummer: XXX Name: Müller, Luca \ -Martrikelnummer: XXX +Martrikelnummer: 3695320 Abgabedatum: 28. Mai 2023 From 7b1925bbb855f1da95c2ed30812c21ab04e95bb7 Mon Sep 17 00:00:00 2001 From: cuvar Date: Sun, 9 Apr 2023 19:41:42 +0200 Subject: [PATCH 39/39] docs: fix logger sentence --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index fb58945..45316f6 100644 --- a/README.md +++ b/README.md @@ -1104,7 +1104,7 @@ In diesem Fall verbirgt die `ConsoleOutputService`-Klasse die Komplexität der ` - `rawOut(Object msg)`: zum direkten Ausgeben von Nachrichten auf der Standardausgabe (stdout). - `rawErr(Object msg)`: zum direkten Ausgeben von Fehlermeldungen auf der Standardfehlerausgabe (stderr). -Die `ConsoleOutputService`-Klasse kapselt die Details der `Logger`-Konfiguration im statischen Block und -Initialisierung. Dies ermöglicht den Nutzern, die Logging-Funktionalität einfach zu verwenden, ohne sich um die zugrunde liegenden Details kümmern zu müssen. +Die `ConsoleOutputService`-Klasse kapselt die Details der `Logger`-Initialisierung sowie die `Logger`-Konfiguration im statischen Block. Dies ermöglicht den Nutzern, die Logging-Funktionalität einfach zu verwenden, ohne sich um die zugrunde liegenden Details kümmern zu müssen. #### 8.2. Entwurfsmuster: Builder