Battleship |
Sonntag, den 10. Mai 2009 um 00:00 Uhr | |||||||||||||||||
Seite 1 von 9
EinführungIn diesem umfangreichen Artikel werden Sie viele Aspekte der Softwareentwicklung kennenlernen. Das Tutorial richtet sich an fortgeschrittene Programmierer, die mit der Programmiersprache Java vertraut sind und sich nun mit dem komplexen Prozess der Anwendungsentwicklung vertraut machen wollen. Sie werden mit der GUI-Entwicklung, Multithreading, Netzwerkprogrammierung, UML-Diagrammen, Javadoc, Anwendungsprotokollen, Algorithmen und Entwurfsmustern konfrontiert werden, um anschließend mit diesem Know-How sukzessive ihr Ziel zu erreichen. Um den komplexen Prozess der Softwareentwicklung kennenzulernen, ist es von Vorteil sich einem der Königsdisziplinen in diesem Bereich zu widmen, nämlich der Entwicklung von Spielen. Spiele vereinen zahlreiche wichtige Aspekte der Softwareentwicklung in sich. Neben der gewissenhaften Auswahl von Entwurfsmustern, sind unter anderem Algorithmen und Datenstrukturen von entscheidender Bedeutung. Schlußendlich weisen viele Spiele heutzutage auch die Möglichkeit auf, über Netzwerk miteinander zu kommunizieren und dafür ist in der Regel ein Anwendungsprotokoll erforderlich. Wir haben uns auf CodePlanet entschieden ein triviales und beliebtes Spiel zu entwickeln, allgemein bekannt unter dem Namen Schiffe versenken oder auch Seeschlacht genannt. Lassen Sie sich allerdings nicht davon täuschen. Unser sogenanntes triviales Spiel wird am Ende aus annähernd 100 Java-Klassen und über 20000 Zeilen Code (SLOC) bestehen. Neben einer komplexen Netzwerkbibliothek, die Sie später problemlos in Ihren anderen Anwendungen verwenden können, wird das Spiel auch eine eigene KI besitzen und einen Chat bereitstellen. Für die Anwendung Battleship wurde eine detaillierte Dokumentation (Javadoc) generiert. Sie finden diese Dokumentation mit der Auflistung aller Klassen und Methoden auf der Seite http://codeplanet.eu/files/battleship/javadoc/. Im nachfolgenden Abschnitt sind einige Screenshots der fertigen Applikation zu sehen. Willkommen auf der SoftwarebaustelleDer Begriff »Baustelle« wird Ihnen im Zusammenhang mit der Software wahrscheinlich etwas ungewöhnlich vorkommen. »Baustelle« - das sind doch Bauarbeiter, Zement, Wasserwagen? »Bauen« umfasst Begriffe wie Planung, Entwicklung, Überprüfung der Arbeit, doch größtenteils ist mit »Bauen« die eigentliche manuelle Arbeit gemeint: Ärmel hochgekrempelt, und los! Und genau darum geht es in diesem Tutorial. Bevor wir allerdings mit dem Bau beginnen, werden wir uns die einzelnen Teilschritte der Softwareentwicklung näher ansehen. Worauf kommt es in der Planung an und was ist vor der eigentlichen Programmierarbeit tatsächlich zu tun? Viele elementare Fragen müssen vor dem Beginn beantwortet werden. Auf diese Weise vermeiden Sie schwerwiegende Fehler und optimieren die Zeit, in der Sie den Code schreiben. Was bedeutet »Software bauen«?Die Entwicklung von Computersoftware kann ein komplizierter Prozess sein. In den letzten 25 Jahren haben Forscher mehrere unterschiedliche Tätigkeiten identifiziert, die in den Bau von Software einfließen. Hierzu zählen:
Wenn Sie bislang nur an kleineren, informellen Projekten gearbeitet haben, wird Ihnen diese Liste sicher wie Bürokratie vorkommen. Wenn Sie an Projekten mitgewirkt haben, die zu formell organisiert waren, dann wissen Sie, dass Sie den Amtsschimmel hier zu Recht wiehern hören. Es ist gar nicht so einfach, einen vernünftigen Mittelweg zwischen zu viel und zu wenig Formalismus zu finden - doch dazu später mehr. Wenn Sie sich das Programmieren selbst beigebracht haben oder hauptsächlich an informellen Projekten gearbeitet haben, werden Sie sich bisher vermutlich kaum um die Unterschiede zwischen den einzelnen Tätigkeiten gekümmert haben, aus denen sich ein Softwareprojekt zusammensetzt. Wahrscheinlich haben Sie all diese Tätigkeiten unter dem Oberbegriff »Programmierung« zusammengefasst. Und Sie sind dann sicher auch der Meinung, dass die Entwicklung von Software in erster Linie aus dem Schreiben von Quelltext besteht. Wissenschaftler verwenden für diese Phase den Begriff »Bauen« (engl. construction). »Bauen« verkörpert ziemlich genau, worum es beim Schreiben von Quelltext geht, greift jedoch ein wenig zu kurz. Darum soll diese Phase in den Kontext der gesamten Programmentwicklung gesetzt werden. Abbildung 1 verdeutlicht die Bedeutung des »Bauens« und seinen Platz innerhalb der Hierarchie der Softwareentwicklung. Abbildung 1: Die Tätigkeiten, die mit dem Bau von Software zusammenhängen Wie die Abbildung zeigt, handelt es sich beim Bauen hauptsächlich um das Schreiben von Quelltext und das Debuggen (die Fehlerbeseitigung), aber auch Detailentwurf, Testen von Einheiten, Integrationstests, Test des Gesamtsystems und andere Tätigkeiten gehören dazu. Der Bau von Software läuft auch häufig unter den Begriffen »Code schreiben«, »Quelltext schreiben« oder schlicht »Programmieren«. Diese Tätigkeit ist aber alles andere als mechanisch-stumpfsinnig; sie erfordert Augenmaß und Kreativität. Warum ist der Bauabschnitt wichtig?Als Leser dieses Tutorials stimmen Sie sicher der Aussage zu, dass die Erhöhung der Softwarequalität und der Arbeitsproduktivität des Entwicklers wichtig sind. Viele interessante Projekte unserer Zeit setzen in hohem Maß auf Software: Internet, Special Effects in der Filmindustrie, Medizintechnik, Luft- und Raumfahrt, blitzschnelle Finanzanalyse, wissenschaftliche Forschung, um nur einige Beispiele zu nennen. Diese Projekte können genauso wie bodenständigere Projekte von verbesserten Verfahren profitieren, weil viele Grundlagen identisch sind. Der Bau von Software ist ein großer und wichtiger Abschnitt der Softwareentwicklung. Abhängig vom Umfang des Projekts werden zwischen 30 und 80 Prozent der gesamten Arbeitszeit für den Bau der Software benötigt. Jede Tätigkeit, die anteilig so viel Zeit beansprucht, beeinflusst den Erfolg des Projekts entscheidend. Der Bau von Software ist der Dreh- und Angelpunkt der Softwareentwicklung. Damit die Bauausführung gut läuft, kommen vorher Anforderungsanalyse und Entwurf. Das fertige System wird anschließend unabhängig geprüft, um sicherzustellen, dass der Bau erfolgreich war. Die Bautätigkeit ist der Kern des Entwicklungsprozesses. Gerade im Bauabschnitt gibt es enorme Möglichkeiten, die Produktivität eines Programmierers zu erhöhen. In einer klassischen Studie (1968) zeigten Sackman, Erikson und Grant, dass die Produktivität einzelner Programmierer in der Bauphase stark voneinander abweicht - um das Zehn- bis Zwanzigfache! Diese Werte wurden seither durch zahlreiche andere Untersuchungen bestätigt (Curtis 1981, Mills 1983, Curtis u.a. 1986, Valett und McGarry 1989, DeMarco und Lister 1999, Boehm u.a. 200). Dises Tutorial zeigt, wie Sie einige Techniken der Spitzenkönner übernehmen können. Das Ergebnis der Bauphase, der Quelltext, ist oft die einzige exakte Dokumentation der Software. Bei vielen Projekten ist der Quelltext die einzige vernünftige Dokumentation, die den Programmierern zur Verfügung steht. Die Analyse der Anforderungen und die Entwurfsunterlagen können schnell veralten, doch der Quelltext ist stets auf dem neuesten Stand. Daher versteht es sich von selbst, dass der Quelltext von der bestmöglichen Qualität sein muss. Die konsequente Anwendung von qualitätssteigernden Techniken macht den Unterschied zwischen einem mysteriösen Wunderwerk der Technik und einem detaillierten, korrekten und daher informativen Programm aus. Und diese Techniken lassen sich mit dem meisten Erfolg eben während der Bauphase anwenden. Das Bauen ist die einzige Arbeit, die garantiert erledigt wird. Das ideale Softwareprojekt beginnt mit einer genauen Analyse der Anforderungen und dem Entwurf der Architektur, bevor die Bauphase einsetzt. Anschließend wird es sorgfältig und statistisch zuverlässig geprüft. Aber: Das wirkliche Leben sieht oft ein wenig anders aus. Oft wird der ganze Vorbereitungsquatsch beiseite geschoben und ruck, zuck mit dem Bau der Software begonnen. Auch gründliche Tests sind oft ein Fremdwort - sei es, weil nicht mehr genug Zeit vorhanden ist, sei es, weil einfach zu viele Fehler im Programm stecken. Doch egal wie schlecht geplant oder wie hastig Sie ihr Projekt durchziehen: Um den Bau der Software führt kein Weg herum. Daher wirkt sich höhere Qualität beim Bau ausnahmslos auf alle Projekte aus. Zusammenfassung
Metaphern als Hilfsmittel für die SoftwareentwicklungDie Sprache der Informatik ist ausgesprochen farbig und bildhaft. Wo sonst treffen Sie in einem sterilen, klimatisierten Raum auf Viren, trojanische Pferde, Würmer, Briefbomben, Abstürze oder fatale Fehler. Die sogenannten Bugs verdanken ihre Bezeichnung dem englischen Begriff Bug, was soviel wie Wanze, Kakerlake oder Floh bedeutet. Demenstrechend nennt man Tools zum Aufspüren und Beseitigen von Bugs auch Debugger. Diese vielen sehr plastischen Metaphern beschreiben Softwarephänomene und sollen die Hintergründe der Softwareentwicklung erhellen. Sie können Ihr Verständnis für den Prozess der Softwareentwicklung verbessern. So ist auch der Bau einer Software an die Praxis angelehnt, viele denken hierbei an den Hausbau und damit liegt man in der Tat goldrichtig. Auch bei einem Hausbau gilt es Planungsschritte zu beachten und sich gut vorzubereiten, damit das Haus am Ende auf einem soliden Fundament steht und nicht in sich zusammenfällt. Vorbereitung beim SoftwarebauBevor ein Maurer mit seiner Arbeit beginnt, studiert er die Pläne. Er überprüft, ob alle Genehmigungen erteilt worden sind, und sieht sich das Fundament an. Er bereitet sich auf einen Wolkenkratzer anders vor als auf ein Gartenhäuschen, und auf eine Hundehütte wieder ganz anders. Die Vorbereitung findet also vor dem beginn der Arbeiten statt und bezieht sich immer auf das konkrete, anstehende Projekt. In diesem Abschnitt erfahren Sie, wie Sie sich auf den Bau von Software vorbereiten sollten. Genau wie beim Bau eines Hauses hängt der Erfolg des Unternehmens in großem Maße von der Vorbereitung ab. Fehler in der Planung oder am Fundament lassen sich später kaum noch ausbügeln. Das Motto des Schreiners lautet: »Zweimal messen, einmal sägen.« Dieses Motto gilt auch für die Bauphase der Softwareentwicklung, die bis zu 65 Prozent der gesamten Projektkosten verschlingen kann. Die schlimmsten Softwareprojekte müssen zwei- oder dreimal gebaut werden. Das teuerste Element des Projekts zweimal durchführen zu müssen, bedeutet bei Software genauso eine Katastrophe wie bei jeder anderen Tätigkeit. Generell unterscheidet man zwischen zwei verschiedenen Ansätzen in der Vorbereitung, dem sequentiellen und iterativen Ansatz. Der iterative Ansatz eignet sich besonders für Projekte, in denen die Anforderungen nicht von Beginn an feststehen, er ist flexibler aber auch nicht so geradlinig, wie der sequentielle Ansatz. Werfen wir nun zunächst einen Blick auf die Pyramide der Softwareentwicklung. Die ProblemdefinitionDer Bau einer guten Software beginnt mit der Problemdefinition, nämlich was soll die Software überhaupt leisten. Die Problemdefinition steckt das Problem ab - ohne jegliche Verweise auf Lösungsmöglichkeiten. Es handelt sich hierbei um einfache Feststellungen mit einem Umfang von vielleicht ein oder zwei Seiten, die das zu lösende Problem wirklich beim Namen nennen. Das Problem sollte in normaler Sprache formuliert sein. Die AnforderungenAnforderungen beschreiben detailliert, was ein Programm tun soll; sie sind der erste Lösungsschritt. Diese Phase der Softwareentwicklung läuft unter den Bezeichnungen »Forderungskatalog«, »Pflichtenheft«, »Analyse«, oder »Spezifikation«. Dem geht eine grobe Spezifikation voraus, diese wird auch Lastenheft genannt. Hier werden unter anderem die Ziele grob festgelegt. Die ArchitekturBei der Softwarearchitektur handelt es sich um die höchste Ebene der Softwareentwicklung, um das gerüst, das die detaillierten Teile des Entwurfs trägt. Vielleicht ist Ihnen dieser Begriff auch schon einmal als »Entwurf«, »Top-Level-Design« oder »Hogh-Level-Design« begegnet. Üblicherweise wird die Architektur in einem einzigen Dokument festgelegt, das den Namen »Architekturspezifikation« oder »Top-Level-Design« trägt. Die Qualität der Architektur bestimmt die Integrität der Systemkonzeption, die wiederrum für die Gesamtqualität des Systems ausschlaggebend ist. Mit einer schlechten Architektur ist es kaum möglich, ein gutes System zu bauen. Die Architektur gibt den Programmieren die Richtung vor - auf einer Stufe, die den Fähigkeiten der Programmierer und dem Projekt angemessen sind. Sie teilt die Arbeit auf, so dass mehrere Entwickler oder Teams parallel arbeiten können. Eine gute Architektur erleichtert den Bau. Schlechte Architektur macht den Bau praktisch unmöglich. Zu jeder Systemarchitektur gehören einige elementare Komponenten.
Schlüsselentscheidungen für den BauNachdem Sie sicher sind, dass alle erforderlichen Vorarbeiten für den Bau abgeschlossen sind, wendet sich die Vorbereitung eher bauspezifischen Entscheidungen zu. Übertragen auf den Hausbau geht es um die geeignete Ausrüstung für Ihren Werkzeuggürtel und um die Beladung des Lieferwagens. Auswahl der ProgrammierspracheZu den Werkzeugen eines Programmieres gehört natürlich die Programmiersprache selbst. Die Programmiersprache, in der das System implementiert wird, ist von großer Bedeutung. Sie werden von Anfang bis Ende des Baus der Software mit ihr zu tun haben. Entscheidend für die Auswahl der richtigen Programmiersprache sind die umzusetzenden Ziele, die zur Verfügung stehenden Programmierer und die Zeit. Programmierer sind produktiver, wenn sie eine vertraute Sprache verwenden. Hochsprachenprogrammierer arbeiten produktiver und erzielen in der Regel eine höhere Qualität als ihre Kollegen, die mit maschinennahen Sprachen arbeiten (müssen). Sprachen, wie C++, Java, C# oder Delphi erhöhen gegenüber Sprachen, wie Assembler oder C die Produktivität, Zuverlässigkeit, Unkompliziertheit und Verständlichkeit um den Faktor 5 bis 15. Hochsprachen sind auch ausdrucksstärker als maschinennahe Sprachen, d.h. Sie erzielen mit weniger Zeilen Code denselben Effekt. Heutige Anwendungen werden nur noch selten in maschinennahen Sprachen geschrieben. Assembler und reines C kommen zumeist nur noch in performancekritischen Anwendungen zum Einsatz bzw. in den untersten Schichten von Systemen. Betriebbssystemkerne, wie der Linux Kernel, und rechenintensive Algorithmen werden maschinnennah implementiert, unter anderem um das Maximum an Leistung aus der Anwendung zu holen. Dementsprechend fällt die Wartung dieser Programme sehr aufwendig aus. Allein der Linux Kernel umfasst über 10 Millionen Zeilen Code, der Großteil in C und Assembler implementiert. Fragen Sie sich, ob die verwendeten Programmiertechniken eine Antwort auf die eingesetzte Programmiersprache sind oder ob die Sprache die Techniken diktiert. Programmieren sie in eine Sprache, nicht in einer Sprache. Konventionen für die ProgrammierungSoftware hoher Qualität zeichnet sich durch einen Zusammenhang zwischen dem durchgängigen Konzept der Architektur und ihrer Implementierung auf Maschinenebene, sprich ihrer Umsetzung in Quelltext aus. Die Implementierung muss der Architektur entsprechen und einen inneren Zusammenhang aufweisen. Hierauf zielen all die Richtlinien für Variablennamen, Klassennamen, Namen von Routinen, Format- und Kommentarkonventionen ab. In einem komplexen Programm sorgen also die architektonischen Richtlinien für die strukturelle Ausgewogenheit und Implementierungsrichtlinien für die Harmonie des Quelltextes, indem jede Klasse im Zusammenhang des gesamten Systems gesehen wird. Jedes große Programm benötigt eine ordnende Hand, eine Strukturierung, die die Details der Programmiersprache systematisiert. Die Schönheit eines großen Systems offenbart sich unter anderem in seinen Details, die den leuchtenden, übergeordneten Gedanken wiederspiegeln. Ohne diese disziplinierende, ordnende Hand haben Sie es sehr schnell mit wild wuchernden, kaum koordinierten Klassen und dissonanten Stilbrüchen zu tun. Solche Stilbrüche erschweren das Verständnis, weil Sie Unterschiede im Programmierstil verarbeiten müssen, die eigentlich völlig willkürlich sind. Man sollte versuchen die Komplexität an allen Stellen zu zähmen. Was würden Sie zu einem Gemälde sagen, das zwar einem großartigen Gedanken entspringt, jedoch von Künstlern mit verschiedenen Vorlieben ausgeführt wurde, so dass es teils impressionistisch aussieht, teils kubistisch und teils klassisch? Das Bild vermittelt kein einheitliches Konzept, keine konzeptionelle Integrität. Halten Sie die Programmierkonventionen fest, bevor Sie mit dem Bau der Software beginnen. Diese Konventionen sind naturgemäß so detailreich, dass sie sich auf vorhandenen Quelltext kaum noch anwenden lassen. Hinterher ist es meist zu spät. Der Entwurf beim BauBei kleinen informellen Projekten entwickelt der Programmierer den Entwurf, während er die Tatstatur bearbeitet. Der »Entwurf« könnte dann so aussehen, dass eine Klassenschnittstelle zunächst in Pseudocode geschrieben wird, bevor sie vollständig programmiert wird. Der Programmierer könnte ein paar Diagramme von den Beziehungen zwischen den Klassen zeichnen, bevor er ans Schreiben geht. Entwurf ist ein sehr weites Feld. Die Qualität eines Klassen- oder Routinenentwurfs wird zum großen Teil durch die Systemarchitektur bestimmt. Vergewissern Sie sich daher, ob die aufgestellten Anforderungen an die Architektur bei Ihrem Projekt erfüllt sind. Ein Schlüsselkonzept für den Entwurf ist das Zähmen der Komplexität. Wenn Analysen zu gescheiterten Softwareprojekten die Ursachen aufzählen, werden nur selten technische Probleme als Hauptgrund für das Fehlschlagen genannt. Projekte scheitern meist wegen schlechter Anforderungskataloge, schlechter Planung oder schlechter Verwaltung. Sind dagegen primär technische Probleme für das Scheitern eines Projekts verantwortlich, ist die Ursache oft eine außer Kontrolle geratene Komplexität. Es wurde der Software erlaubt, so komplex zu werden, dass niemand mehr weiß, was sie eigentlich tut und wie sie es tut. Wenn ein Projekt einen Punkt erreicht hat, an dem niemand mehr sagen kann, welche Auswirkungen Änderungen an einem Codeteil auf andere Bereiche haben werden, kommt die Entwicklung mit quietschenden Bremsen zum Stillstand. Komplexität ist keineswegs eine neue Erscheinung bei der Softwareentwicklung. Der Computerpionier und Turing Award Gewinner Edsger Wybe Dijkstra wies darauf hin, dass die Arbeit mit Computern die einzige Tätigkeit ist, bei der ein einzelner Geist die gesamte Strecke von einem Bit bis zu einigen hundert Megabytes überschauen muss, eine Distanz von 1 bis 109. Dieses gigantische Verhältnis ist Ehrfurcht gebietend. Dijkstra drückte es so aus: »Im Vergleich zu dieser Zahl semantischer Ebenen ist die übliche mathematische Theorie fast schon oberfächlich. Weil beim Umgang mit dem automatischen Computer tiefe konzeptionelle Hierarchien erforderlich sind, konfrontiert uns der Computer mit einer völlig neuen intellektuellen Herausforderung, für die es in unserer Geschichte kein Vorbild gibt.« Und Software wurde seit 1989 noch komplexer, Dijkstras Verhältnis von 1 bis 109 wäre heute eher bei 1 bis 1015. Dijkstra wies darauf hin, dass niemand ein Gehirn besitzt, das tatsächlich ein modernes Computerprogramm erfassen könnte und dass wir als Softwareentwickler daher überhaupt nicht versuchen sollten uns komplette Programme vollständig zu verinnerlichen. Wir sollte stattdessen unsere Programme so organisieren, dass wir uns problemlos immer auf ein bestimmtes Element konzentrieren können. Erstrebenswerte Merkmale eines guten EntwurfsEin guter Entwurf zeichnet sich durch einige charakteristische Merkmale aus. Es wäre nur zu schön, wenn Sie alle Ziele erreichen könnten - nur widersprechen einige Ziele einander. Doch hierin liegt ja gerade die Herausforderung, die dem Entwurfsprozess innewohnt: Entwurf bedeutet nichts anderes, als gute Kompromisse zu schließen. Nachfolgend werden die wesentlichen Merkmale des Entwurfsprozesses aufgelistet und kurz erklärt. Ein bereits genanntes Merkmal ist die Minimierung der Komplexität. Vermeiden sie allzu "pfiffige" Entwürfe, die nur Sie überhaupt verstehen. Streben Sie einen eleganten und »simplen« Entwurf an. Falls ihr Entwurf nicht zulässt, beim Bearbeiten eines bestimmten Teils problemlos die meisten anderen Teile des Programms zu ignorieren, taugt der Entwurf nichts. Neben der Komplexität spielt die Pflegeleichtigkeit eine wichtige Rolle. Bauen Sie die Verbindungen und Abhängigkeiten zwischen unterschiedlichen Programmteilen so weit wie möglich ab. Halten Sie sich bei Klassenschnittstellen, Kapselung und beim Verbergen von Informationen an die Prinzipien guter Abstraktion. Die Erweiterbarkeit ist ebenso essentiell. Ihr System soll sich verbessern lassen, ohne dass seine grundlegende Struktur in Mitleidenschaft gezogen wird und ohne das große Teile vollständig abgeändert werden müssen. Auch die Wiederverwendbarkeit ist ein Merkmal eines guten Entwurfs. Wenn Teile ihres Systems problemlos woanders integriert werden können, ist ihr Entwurf gut. Ein möglichst geringer Fan-in. Der Fan-in bezeichnet die Zahl der Klassen, die eine einzelne Klasse aufruft. Ein hoher Fan-in weist darauf hin, dass eine Klasse eine große Anzahl weiterer Klassen verwendet und daher unter Umständen unnötig komplex ist. Das trifft selbstverständlich nicht auf Schnittstellen zu. Die Portierbarkeit stellt sicher, dass ihr System leicht auf eine andere Umgebung übertragen werden kann. Für den Entwurf ist auch die Schlankheit vorteilhaft. Überflüssige Teile gehören nicht in das System. Voltaire sagte einst, dass ein Buch nicht dann fertig ist, wenn nichts mehr hinzugefügt werden kann, sondern dann, wenn sich nichts mehr wegstreichen lässt. Zuletzt zeichnet sich ein guter Entwurf durch den Gebrauch von Schichten aus. Versuchen sie, die Untergliederungsebenen in Schichten zu halten, so dass sich auf jeder Schicht eine zusammenhängende Sicht auf das System ergibt. Sie müssen das System durchgehend auf einer Ebene betrachten können, ohne andere Ebenen einbeziehen zu müssen. Auch zu diesem abschließenden Punkt »Entwurf beim Bau« finden Sie eine Checkliste im PDF-Format. |
|||||||||||||||||
Zuletzt aktualisiert am Montag, den 23. April 2012 um 12:59 Uhr |
AUSWAHLMENÜ | ||||||||
|