Die hier aufgeführten Bachelor- und Masterarbeiten können in Absprache auch in Form von Bachelor- bzw. Masterprojekten bearbeitet werden. Dabei wird ggf. der Umfang des Themas an die Gegebenheiten des Projektes angepasst. Falls eines der Themen als Bachelor- oder Masterprojekt durchgeführt werden soll, müssen keine Kenntnisse in Elm vorhanden sein, da diese im Rahmen des Projektes erarbeitet werden können. Falls Sie sich für andere Themen aus den Bereichen moderne Methoden der Softwareentwicklung, Compilerbau, Programmiersprachen oder Algorithmen interessieren, wenden Sie sich bitte einfach per Mail an mich.

Mining Software Repositories

Empirische Untersuchung von Piping in Elm

In dieser Arbeit soll die Verwendung der Operatoren |> und <| in Elm-Projekten bei GitHub untersucht werden. Aus mehreren vorhergehenden Abschlussarbeiten steht bereits eine Anwendung zur Verfügung, die Elm-Repositories von GitHub klont und analysiert. Diese Anwendung ist in Haskell geschrieben. Die Anwendung extrahiert aus den Repositories die Elm-Quelldateien und erzeugt mithilfe eines Parsers aus jeder Quelldatei einen abstrakten Syntaxbaum (AST). Der abstrakte Syntaxbaum eines Programms kann dann traversiert werden, um Informationen über die Elm-Programme zu extrahieren.

Die Informationen, die zu den Operatoren |> und <| erhoben werden, können sich an der Publikation An Empirical Study of Method Chaining in Java orientieren. Es soll zum Beispiel untersucht werden, wie häufig die Operatoren (ggf. im Vergleich zu normalen Funktionsanwendungen) verwendet werden. Außerdem soll untersucht werden, ob es eher lange Sequenzen von Verwendungen der Operatoren gibt oder ob die Operatoren eher einzeln verwendet werden. Daneben soll untersucht werden, in welchen Fällen die Operatoren |> und <| vor allem verwendet werden. Zum Beispiel wird der Operator |> gern zusammen mit Funktionen wie andThen und andMap verwendet. Außerdem wird der Operator <| gern verwendet, wenn das Argument, auf das die Funktion angewendet wird, mehrere Zeilen überspannt. Es soll untersucht werden, ob die Operatoren |> und <| vor allem in diesen Fällen verwendet werden oder ob es noch weitere wiederkehrende Anwendungsfälle gibt.

Um die Elm-Anwendungen zu parsen, also einen abstrakten Syntaxbaum aus dem Quelltext zu erzeugen, wird die Haskell-Bibliothek elm-format verwendet. Diese Bibliothek stellt auch den Datentyp zur Verfügung, der den abstrakten Syntaxbaum von Elm modelliert. Um einfach Informationen aus dem AST zu extrahieren, nutzt die Haskell-Anwendung die Ideen aus Uniform Boilerplate and List Processing. Diese Publikation stellt eine Ansatz zur Verfügung, wie man mit vergleichsweise wenig Code aus einem komplexen Datentyp Informationen extrahieren kann.

Falls die Zeit es erlaubt, kann die Untersuchung auf weitere funktionale Sprachen ausgedehnt werden.

Die Arbeit startet mit einer Einarbeitung in Konzepte wie Parser, abstrakter Syntaxbaum und die grundsätzliche Idee von Repository Mining-Studien.

Voraussetzungen: Grundkenntnisse in Haskell
Geeignet als: Bachelor- oder Masterarbeit

Replikation einer Studie über Bezeichner in verschiedenen Programmiersprachen

In dieser Arbeit soll die Studie Meaningful Identifier Names: The Case of Single-Letter Variables repliziert werden. Die Studie untersucht, wie lang die Bezeichner in den Programmiersprachen C, Java, JavaScript, PHP, und Perl sind und wie häufig Einbuchstabenbezeichner sind. Dazu werden Projekte von GitHub analysiert. Bei GitHub stehen die Programme zur Verfügung, die zur Durchführung der Studie genutzt wurden. Dabei werden kleine Python-Skripte genutzt und ein paar sprachspezifische Skripte zur Extraktion der Variablen aus Programmen in den verschiedenen Programmiersprachen. Bei der Replikation sollen insbesondere die Ergebnisse für die Programmiersprache JavaScript genauer überprüft werden. Die Ergebnisse für die Programmiersprache JavaScript lassen vermuten, dass ggf. minifizierte JavaScript-Dateien mit analysiert wurden. Das folgende Zitat stammt aus der Originalpublikation.

This (together with the fact that single-letter variables are very common as shown in Figure 1) may indicate that the dataset includes some minified code that was not identified correctly and removed by our filter.

Daher soll nach der Replikation insbesondere ein Konzept entwickelt werden, um minifizierte JavaScript-Module auszuschließen.

Die Arbeit startet mit einer Einarbeitung in Konzepte wie Parser, abstrakter Syntaxbaum und die grundsätzliche Idee von Repository Mining-Studien.

Voraussetzungen: Kenntnisse in Python
Geeignet als: Bachelorarbeit

Replikation einer Studie über Bezeichner in Java

In dieser Arbeit soll die Studie A Large-Scale Investigation of Local Variable Names in Java Programs: Is Longer Name Better for Broader Scope Variable? repliziert werden. Die Studie untersucht, wie Bezeichner in Java-Programmen vergeben werden. Dazu wurden 1000 GitHib-Repositories analysiert. Die Studie gibt an, dass 637 077 lokale Variablen aus 472 665 Java-Quelldateien extrahiert wurden. Das wären etwa 1,3 lokale Variable pro Java-Quelldatei. Die Daten der Studie stehen unter https://bit.ly/3xLuaLK zur Verfügung. Bei einer ersten Sichtung der Daten ist bereits aufgefallen, dass gegen die Aussage “The local variables include the formal arguments (parameters) of methods.” aus der Publikation anscheinend keine Bezeichner von Methodenparametern gesammelt werden. Daher soll die Sammlung der Daten reproduziert und überprüft werden. Die Anwendung, welche zum Sammeln der Daten genutzt wurde, wurde in Java geschrieben und steht ebenfalls unter https://bit.ly/3xLuaLK zur Verfügung.

Voraussetzungen: Gute Kenntnisse in Java
Geeignet als: Bachelorarbeit

Extraktion von Bezeichnern in F#

In dieser Arbeit soll eine bestehende Haskell-Anwendung um die Verarbeitung von F#-Programmen erweitert werden. Die Programmiersprache F# ist eine funktionale Sprache, die von Haskell inspiriert wurde und auf der .Net-Platform von Microsoft läuft. Die bestehende Haskell-Anwendung sammelt die 100 Repositories mit dem meisten Sternen von GitHub für die Programmiersprachen Haskell, Elm, PureScript und OCaml. Diese Anwendung soll um die Verarbeitung der 100 Repositories mit den meisten Sternen in F# erweitert werden. Zu diesem Zweck muss im ersten Schritt die Möglichkeit geschaffen werden, F#-Programme in Haskell zu verarbeiten. Dazu soll ein minimales F#-Programm geschrieben werden, das eine Datei mit einem F#-Programm einliest und einen abstrakten Syntaxbaum (AST) auf der Konsole ausgibt. Das F#-Programm gibt die Struktur des AST auf der Konsole aus und ein Haskell-Programm soll die Struktur einlesen und als Haskell-Datentyp zur Verfügung stellen. Dazu muss in Haskell ein Parser mithilfe von Parser-Kombinatoren geschrieben werden. Der Parser überführt die Ausgabe auf der Konsole in einen Haskell-Datentyp, der den AST darstellt. Wenn auf diese Weise erfolgreich in Haskell ein AST für ein F#-Programm erzeugt werden kann, soll prototypisch gezeigt werden, dass die Bezeichner aus einem F#-Programm gesammelt werden können.

Bei der Umsetzung als Masterarbeit soll die gesamte Integration der Sprache in das bestehende Tool umgesetzt werden. Dazu sollen Build-Konfigurationsdateien der Sprache F# analysiert werden, um die Quelldateien, die ich in einem Repository befinden, zu sammeln.

Die Arbeit startet mit einer Einarbeitung in die Grundlagen der Programmiersprache F# und Konzepte wie Parser, abstrakter Syntaxbaum und die Verwendung von Parser-Kombinatoren in Haskell.

Voraussetzungen: Gute Kenntnisse in einer funktionalen Sprache
Geeignet als: Bachelor- oder Masterarbeit

Programmiersprachen-Design

Entwicklung einer Sprache für mathematische Beweise

In der Veranstaltung Algorithmen müssen die Studierenden kleine Beweise zum asymptotischen Verhalten von Funktionen schreiben. Es ist leider sehr aufwendig, sinnvoll Rückmeldungen zu den Beweisen zu geben. Dieses Problem soll in dieser Arbeit angegangen werden.

Das erste Problem bei der Abgabe der Aufgaben besteht darin, dass die Studierenden Fotos ihrer Beweise oder PDF-Dateien abgeben. Diese Daten lassen sich im Vergleich zum Programmcode, der in den anderen Aufgaben abgegeben wird, nicht gut automatisiert analysieren. Daher soll im ersten Schritt eine Sprache für die spezielle Form von Beweisen, die die Studierenden schreiben, entwickelt werden. Diese Sprache orientiert sich stark an den mathematischen Formalismen, kann aber in Form einer Textdatei verfasst werden.

Wenn die Studierenden ihre Textdatei hochladen, sollten sie eine direkte Rückmeldung zu Ihrer Abgabe erhalten. In den Programmieraufgaben geschieht dies durch Testfälle und statische Analysen, die durch Automatisierung bei jedem Push in ein Git-Repository ausgeführt werden. Im Vergleich zu Programmieraufgaben scheitern viele Studierenden bei den Mathematikaufgaben bereits daran, korrekte Syntax zu verwenden und Variablen korrekt einzuführen, bevor sie verwendet werden. Um Feedback zu den Aufgaben zu erhalten, soll ein Parser für die sehr einfache mathematische Sprache entwickelt werden. Dieser Parser muss berücksichtigen, dass Studierende häufig bereits Probleme mit der Syntax haben und sollte gute Erklärungen für Fehler liefern.

Die Arbeit startet mit einer Einarbeitung in die grundlegende Struktur der mathematischen Beweise im Rahmen ver Veranstaltung Algorithmen sowie in die Konzepte Parser und Parser-Kombinatoren in Haskell.

Voraussetzungen: Gute Kenntnisse in einer funktionalen Sprache, grundlegende mathematische Fähigkeiten
Geeignet als: Bachelorarbeit

Konzept einer sicheren Model-View-Update-Architektur

In dieser Arbeit soll ein Konzept für eine Model-View-Update-Architektur entwickelt werden, die mehr statische Garantien erlaubt. Dabei geht es insbesondere um den Fall, dass in einem bestimmten Modellzustand nur bestimmte Nachrichten an die Anwendung möglich sind. Das Konzept soll prototypisch für eine Model-View-Update-Architektur in Haskell umgesetzt werden.

Aktuell lässt sich in der Programmiersprach Elm nicht ausdrücken, dass nur bestimmte Nachrichten in einem bestimmten Modellzustand erwartet werden. Wenn man etwa das Beispiel eines Spiels nimmt, kann das Spiel laufen oder beendet sein. Während das Spiel läuft ist es beispielsweise möglich eine Spielfigur mit den Tasten zu bewegen. Ist das Spiel beendet, sollen diese Eingaben aber nicht verarbeitet werden. Wenn die Anwendung im Beendet-Zustand eine Nachricht zum Bewegen der Spielfigur erhält, gibt es zwei Möglichkeiten. Die Nachricht kann ignoriert werden, was schnell dazu führt, dass fehlerhafte interne Zustände erhalten bleiben. Alternativ kann die Anwendung in einen Fehlerzustand wechseln, wenn ein inkonsistenter Zustand auftritt. In diesem Fall wird im Grunde ein Laufzeitfehler ausgelöst und es ist wünschenswert, solche Laufzeitfehler durch statische Fehler zu ersetzen.

Als mögliche Lösung für dieses Problem soll der Einsatz von generalisierten algebraischen Datentypen (GADTs) evaluiert werden. Die Programmiersprache Elm stellt keine generalisierten algebraischen Datentypen zur Verfügung, in der Programmiersprache Haskell können diese aber genutzt werden. Daher soll das Konzept mithilfe der Haskell-Bibliothek miso evaluiert werden. Das Framework miso ist eine Implementierung der Model-View-Update-Architektur in Haskell. Zur Illustration der Verwendung von miso enthält das miso-Repository eine Implementierung des TodoMVC.

Im Kontext von Haskell soll evaluiert werden, welche Probleme bei der Modellierung von konkreten Anwendungen im Kontext eines sicheren Model-View-Update entstehen. Während die Verwendung von GADTs zum Beispiel eine höhere statische Sicherheit ermöglicht, werden Programmierer*innen ggf. so stark eingeschränkt, dass es schwieriger ist, eine Anwendung zu strukturieren. Um diesen Aspekt zu evaluieren, sollen verschiedene typische Beispiele aus der Entwicklung von Frontendanwendungen mit der Model-View-Update-Architektur evaluiert werden.

Die Arbeit startet mit einer Einarbeitung in die Model-View-Update-Architektur und die konkrete Umsetzung in miso. Anschließend wird das Konzept der generalisierten algebraischen Datentypen (GADTs) in Haskell erarbeitet.

Voraussetzungen: Gute Kenntnisse in einer funktionalen Sprache
Geeignet als: Bachelor- oder Masterarbeit

Programmanalyse

Identifikation von schlecht gewählten Bezeichnern in Java

Im Rahmen dieser Arbeit soll die Haskell-Anwendung jlint erweitert werden. Die Anwendung wurde im Rahmen von mehreren Bachelor-Projekten entwickelt und wird dazu genutzt, den Studierenden in der Veranstaltung Algorithmen Rückmeldung zur Code-Qualität zu geben. Im Rahmen dieser Arbeit soll die Anwendung um Regeln erweitert werden, die sich auf die Qualität von Bezeichnern, also die Namen in einem Programm, beziehen. In den letzten Jahren wurden manuell Rückmeldungen zu schlecht gewählten Bezeichnern gegeben, etwa wenn ein Attribut den Namen array trug aber eine Liste enthielt. Diese manuellen Anmerkungen sollen evaluiert und wiederkehrende Muster in Form von Regeln implementiert werden.

Schlechte Bezeichner dieser Art werden in der Literatur auch als Linguistic Anti-Pattern bezeichnet. Im Rahmen der Arbeit sollen daher wissenschaftliche Publikationen aus diesem Bereich evaluiert werden und ggf. Anti-Pattern als Regeln in jlint implementiert werden. Einen Einstieg bietet die Publikation A New Family of Software Anti-Patterns: Linguistic Anti-Patterns.

Voraussetzungen: Grundlegende Kenntnisse in einer funktionalen Programmiersprache
Geeignet als: Bachelorarbeit

Entwicklung eines Identifier Dictionaries in Haskell

Die Publikation Concise and consistent naming stellt das Konzept eines Identifier Dictionaries vor. Diese Anwendung sammelt die Bezeichner, die in einem Software-Projekt verwendet werden und listet sie zusammen mit ihrem Typ auf. Die Anwendung soll dabei helfen, ungenaue oder inkonsistente Benennungen zu erkennen.

In dieser Arbeit soll eine vergleichbare Anwendung für die Programmiersprache Haskell entwickelt werden. Die größte Herausforderung besteht dabei darin, Typinformationen für die in einer Haskell-Anwendung verwendeten Variablen zu erhalten. Um dies zu erreichen, muss die Haskell-Anwendung inklusive der Auflösung von Abhängigkeiten gebaut werden. Es soll prototypisch ein Ansatz für ein solches Identifier Dictionary entwickelt werden. Zu diesem Zweck werden Hie-Dateien verwendet. Der Haskell-Compiler ist in der Lage, Hie-Dateien zu erzeugen. Bei diesen Dateien handelt es sich um Beschreibungen des Interface eines Moduls. Diese Dateien werden zum Beispiel verwendet, um Funktionalitäten einer IDE für die Programmierung mit Haskell umzusetzen. Diese Hie-Dateien müssen für ein Projekt erzeugt und gelesen werden, um ein Identifier Dictionary zu erzeugen.

Voraussetzungen: Gute Kenntnisse in Haskell
Geeignet als: Bachelor- oder Masterarbeit

Redundante Fälle in case-Ausdrücken

Um Studierenden Rückmeldungen zu Ihren Programmierabgaben in der Programmiersprache Elm zu geben, wird in einer Vorlesung die Bibliothek elm-review genutzt. In dieser Arbeit soll eine Regel für die Bibliothek elm-review entwickelt werden, die anmerkt, wenn ein Fall eines case-Ausdruckes redundant ist. Ein Fall ist redundant, wenn man den Fall entfernen kann, ohne dass sich das Verhalten der Funktion verändert. Die folgende Definition enthält eine Regel, die redundant ist.

snocList : List a -> a -> List a
snocList list element =
    case list of
        [] ->
            element :: []

        head :: [] ->
            head :: element :: []

        head :: rest ->
            head :: snocList rest element

Wenn die zweite Regel aus dieser Definition entfernt wird, verändert sich das Verhalten dieser Funktion nicht. Wenn die Funktion mit einer Liste der Form head :: [] aufgerufen wird, wird die dritte Regeln genommen, falls die zweite Regel nicht existiert. In diesem Fall wird der Aufruf snocList [] element durchgeführt, wobei element ein abstraktes Argument ist, das an die Funktion snocList übergeben wurde. Der Aufruf snocList [] element liefert als Ergebnis element :: []. Somit erhalten wir von der gesamten dritten Regel als Ergebnis head :: element :: [], was identisch zum Ergebnis ist, das die zweite Regel geliefert hätte. Daher kann in dieser Definition die zweite Regel entfernt werden.

Um zu überprüfen, ob ein Fall entfernt werden kann, muss zuerst überprüft werden, ob es zwei Pattern gibt, die sich überlappen. In der Funktion snocList überlappen zum Beispiel die Pattern head :: [] und head :: rest, da das erste Pattern ein Spezialfall des zweiten Pattern ist. Aus den beiden Pattern kann nun eine Substitution berechnet werden. Eine Substitution beschreibt, wie man aus dem einen Pattern das andere Pattern erzeugen kann. Im Fall von snocList besteht die Substitution aus rest -> []. Das heißt, wenn wir die Variable rest durch [] ersetzen, können wir aus dem Pattern head :: rest das Pattern head :: [] erzeugen.

Wenn zwei Pattern gefunden sind, die sich überlappen, kann die Substitution auf die rechte Seite des Falles angewendet werden. Wir ersetzen in unserem Beispiel also zum Beispiel im Ausdruck head :: snocList rest element alle Vorkommen von rest durch []. Wir erhalten somit head :: snocList [] element.

Voraussetzungen: Gute Kenntnisse in einer funktionalen Sprache
Geeignet als: Masterarbeit

Reimplementierungen erkennen

Um Studierenden Rückmeldungen zu Ihren Programmierabgaben in der Programmiersprache Elm zu geben, wird die Bibliothek elm-review genutzt. In dieser Arbeit soll eine Regel für die Bibliothek elm-review entwickelt werden, die anmerkt, wenn eine Funktion eine Reimplementierung einer vordefinierten Funktion ist.

Als Beispiel betrachten wir die folgende Funktion, die sich mithilfe von List.map implementieren lässt.

incAll : List Int -> List Int
incAll list =
    case list of
        a :: as ->
            a + 1 :: incAll as

        _ ->
            []

Es ist relativ aufwändig, alle möglichen Varianten dieser Funktion mit der Funktion List.map zu vergleichen. Zum Beispiel nutzt die Funktion incAll im Unterschied zu List.map ein Unterstrich-Pattern. Außerdem prüft die Funktion List.map zuerst das Pattern []. Daher soll die zu prüfende Funktion zuerst normalisiert werden. Das heißt, zuerst wird das Unterstrich-Pattern durch ein konkrete Pattern ersetzt. Wir erhalten also die folgende Definition.

incAll : List Int -> List Int
incAll list =
    case list of
        a :: as ->
            a + 1 :: incAll as

        [] ->
            []

Im nächsten Schritt werden die Pattern in eine vorgegebene Reihenfolge gebracht und wir erhalten die folgende Definition.

incAll : List Int -> List Int
incAll list =
    case list of
        [] ->
            []

        a :: as ->
            a + 1 :: incAll as

Im nächsten Schritt werden für die Variablen in den Pattern die Namen verwendet, die auch in List.map verwendet werden. Das heißt, wir erhalten die folgende Definition.

incAll : List Int -> List Int
incAll list =
    case list of
        [] ->
            []

        head :: rest ->
            head + 1 :: incAll rest

Nun kann die Funktion einfacher mit der vordefinierten Funktion List.map verglichen werden, da zum Beispiel die Pattern einfach der Reihenfolge nach verglichen werden können.

Als Beispiele für die Erkennung von Reimplementierungen können die Funktionen List.length, List.filter, List.any, List.map und List.concatMap betrachtet werden.

Voraussetzungen: Gute Kenntnisse in einer funktionalen Sprache
Geeignet als: Bachelorarbeit

Feedback zu Mathematikaufgaben

Dieses Projekt ist einer Erweiterung des Projektes Entwicklung einer Sprache für mathematische Beweise. Wenn die Abgabe der mathematischen Beweise syntaktisch akzeptiert wird, sollten die Studierenden auch Rückmeldung zur Korrektheit der Abgabe erhalten. Dazu soll der interaktive Theorembeweiser Coq verwendet werden. Coq stellt eine Programmiersprache zur Verfügung, in der Beweise programmiert werden können. Das Web-Buch Software Foundations bietet eine Einführung in die Programmiersprache Coq. Das Buch führt aber sehr viel mehr Konzepte der Sprache ein als für die Bearbeitung des Themas benötigt werden. Um einen Beweis zu überprüfen, muss ein Programm in der Sprache, die in Entwicklung einer Sprache für mathematische Beweise entwickelt wurde, in ein Coq-Programm übersetzt werden. Das Coq-Programm kann anschließend durch den Coq-Compiler auf Korrektheit überprüft werden. Wenn der Coq-Compiler das Programm akzeptiert, ist der Beweis korrekt. Im ersten Schritt muss erst einmal keine Rückmeldung gegeben werden, warum der Beweis falsch ist.

Zu Anfang der Arbeit soll eine Literaturrecherche zum Thema Vermittlung von mathematischen Fähigkeiten mithilfe von Theorembeweisern durchgeführt werden. Einen guten Einstieg bietet Waterproof: Educational Software for Learning How to Write Mathematical Proofs.

Voraussetzungen: Gute Kenntnisse in einer funktionalen Sprache, grundlegende mathematische Fähigkeiten
Geeignet als: Masterarbeit

Web-Anwendungen

Digitalisierung von Anträgen zur Anerkennung von Leistungen

In dieser Arbeit soll ein Prototyp einer Web-Anwendung entwickelt werden, die genutzt werden kann, um Anträge auf die Anerkennung von Leistungen an anderen Hochschulen digital einzureichen. In einem Studierenden-Projekt wurde bereits ein erster Prototyp einer solchen Anwendung umgesetzt. Grundsätzlich soll die Anwendung neu entwickelt werden, ggf. kann aber das UI-Design oder das Datenmodell der bestehenden Anwendung übernommen werden. Zur Umsetzung der Anwendung soll ein einfaches REST-Backend und eine Frontend-Anwendung in Elm umgesetzt werden. Zu Beginn des Projektes sollen verschiedene Alternativen für die Umsetzung des Backends evaluiert werden.

Zu Beginn des Projektes sollen verschiedene Alternativen für die Umsetzung des Backends evaluiert werden.

Voraussetzungen: Gute Kenntnisse in Elm
Geeignet als: Bachelorarbeit

Digitalisierung der Semesterplanung

Zur Verbesserung der Vorplanung eines Semesters soll ein Prototyp einer Web-Anwendung entwickelt werden, in der Dozent*innen hinterlegen können, welche Lehrveranstaltungen sie im kommenden Semester planen. Dazu gibt die Anwendung eine Liste von möglichen Veranstaltungen vor, die durch die Prüfungsordnung vorgegeben werden. Die Nutzer*innen können sich dann aus der Liste die Veranstaltungen zusammenstellen, die Sie im kommenden Semester planen. Im besten Fall ist die Anwendung im Anschluss in der Lage sehr einfache Analysen der Planung durchzuführen. Dazu gehört etwa die Analyse, welche Veranstaltungen aus der Prüfungsordnung noch nicht abgedeckt werden. Zu diesem Zweck muss in der Anwendung hinterlegt werden, wie viele Studierende in den verschiedenen Semestern in etwa erwartet werden. Ansonsten kann zum Beispiel nicht überprüft werden, ob die Anzahl der Labore oder die Anzahl der Wahlpflichtfächer ausreichend ist.

Voraussetzungen: Gute Kenntnisse in Elm
Geeignet als: Bachelorarbeit