Grundlagen der Informatik I “Programmierung”
Grundlagen der Informatik I “Programmierung” Grundlagen der Informatik I “Programmierung”
4.1.1 Analyse und Gestaltung Softwaresysteme werden nur selten losgelöst von einem konkreten Bezug zur realen Welt erstellt. Deshalb besteht die wesentliche Aufgabe beim Entwurf von Softwaresystemen fast immer darin, den für die Problemstellung relevanten Ausschnitt der realen Welt zu modellieren. Softwareentwickler stehen also vor der Aufgabe, einen Bezug zwischen der realen Welt und der Denkwelt von Computeralgorithmen herstellen zu müssen. Das verlangt zum einen, den vorgesehenen Einsatzbereich des Softwareproduktes und das zugehörige Umfeld sowie eventuelle Nebenwirkungen des Softwareeinsatzes zu verstehen, und zum anderen, einen derartigen Teil der realen Welt auf formale Modelle zu reduzieren, was zwangsläufig eine drastische Beschränkung der Sicht der realen Welt mit sich bringen wird. Informatiker befinden sich daher immer in einer Doppelrolle. Einerseits sind sie Entdecker der Gesetzmäßigkeiten des Umfelds, das sie modellieren und durch ihr Softwareprodukt beeinflussen wollen. Anderseits sind sie aber auch Gestalter (oder Erfinder) eines Modells, das den relevanten Ausschnitt der Welt wiederspiegelt und durch das entstehende Produkt wieder eine Rückwirkung auf die reale Welt – zum Beispiel auf Arbeitsabläufe innerhalb eines Betriebs – haben wird. Sie als zukünftige Informatiker müssen sich dieser Doppelrolle immer bewußt sein. Ihre Aufgaben und Ihre Verantwortung geht weit über die reine Technik (das Codieren eines Programms) hinaus: Sie müssen die bestehende Welt und natürlich auch die Problemstellung analysieren und sich dafür entscheiden, wie Sie sie durch Ihr Softwareprodukt gestalten wollen. Erst danach können Sie an eine konkrete Implementierung denken. Dementsprechend ist der Entwurf eines Softwaresystems in zwei prinzipiellen Schritten auszuführen: 1. Am Anfang eines Entwurfs steht die Analyse der Leistungen, die das Programm an seine Klienten erbringen soll. Ergebnis der Analyse ist eine Reihe von Attributen, Methoden und Funktionen, die der Anwender abrufen kann. Jede einzelne Leistung entspricht einer Routine, deren Wirkung über ihren Kontrakt beschrieben ist. Das Gesamtangebot an Leistungen des Systems wird üblicherweise als Schnittstelle bezeichnet. 1 Für jede einzelne Leistung wird ihr Kontrakt bestimmt und für das Gesamtprogramm die Zusicherungen, die alle Prozeduren als Nachbedingung garantieren – also die Invariante. Damit ist die äußere Sicht des Programms (Grobspezifikation) abgeschlossen und der inhaltliche Teil des Pflichtenhefts definiert: Es ist vollständig festgelegt, was das Programm leisten soll. Diese Aufgabe muß meist in Kooperation mit Spezialisten aus den Anwendungsbereichen geschehen, da den Informatikern meist die notwendige Kompetenz fehlt. Das befreit Informatiker allerdings nicht von der Pflicht, sich in die Denkweise der entsprechenden Dispziplinen einzuarbeiten. 2. Der nächste Schritt ist der kreative Anteil am Entwurf. Man überlegt sich, wie die einzelnen Angebote realisiert werden können. Die Beschreibung davon nennt man Feinspezifikation. Das Grundprinzip ist dabei eine Zerlegung des Systems in unabhängige kooperierende Komponenten (Separation of Concerns), also die Frage, welche Objekte benötigt werden, um die geforderten Leistungen mit geringem Aufwand zu beschreiben. Hierzu muß man versuchen, Objekte zu spezifizieren, aus deren Merkmalen man die geforderten Leistungen zusammenbauen kann. Zur Durchführung dieser Schritte ist eine Menge Einfallsreichtum und Begabung und vor allem auch eine Menge von Erfahrung und Training erforderlich. Dennoch lassen sich – zumindest für den zweiten Schritt – ein paar allgemeine Leitlinen angeben, die ein erfolgreiches Vorgehen fördern. Hierzu wollen wir zunächst noch einmal die Grundmerkmale der objektorientierten Vorgehensweise resümieren. 1 Ist der Klient ein Anwender (im Gegensatz zu Systemen, bei dem der Klient wiederum ein technisches Systems ist, wie z.B. bei ABS-Systemen, Waschmaschinen, autonomen Waffensystemen), dann wird er nicht direkt diese Routinen aufrufen sondern wird ein Menü sehen, das die einzelnen Routinen kennzeichnet. Die aktuellen Parameter werden dann innerhalb eines Dialogs abgefragt. In diesem Fall nennt man die Schnittstelle Benutzerschnittstelle (User Interface) und seine Darstellung Benutzeroberfläche.
4.1.2 Grundideen des objektorientierten Entwurfs Bei einem reinen objektorientierten Vorgehen, wie es durch die Programmiersprache Eiffel unterstützt wird, ist zu beachten, daß Klassen der einzige Strukturierungsmechanismus für Systeme sind. Sie sind unabhängige Einheiten, obwohl sie durch Benutzung und Vererbung miteinander verbunden sein können. Aus Sicht der Programmiersprache liegen alle Klassen auf der gleichen Ebene und dürfen – genau wie Routinen – nicht geschachtelt werden. Diese Eigenständigkeit von Softwareteilen ist wesentlich für Wiederverwendbarkeit und einer der wichtigsten Unterschiede zu den gängigen blockstrukturierten Sprachen. Eine Klasse kann als ein Lager von Dienstleistungen (exportierten features) angesehen werden, die auf den Exemplaren eines Datentyps verfügbar sind. Diese Dienste werden allen Kunden, die sie überhaupt benutzen dürfen, gleichermaßen zur Verfügung gestellt: • Es gibt keine Operationen mit höherer oder niedriger Bedeutung, insbesondere keine Hauptfunktion – selbst dann, wenn bestimmte Dienste häufiger in Anspruch genommen werden als andere. • Es gibt keine Vorschriften über die Reihenfolge, in der Dienstleistungen aufgerufen werden dürfen. Die Reihenfolge mag zwar bei der eigentlichen Ausführung eines Systems von großer Bedeutung sein. Es wäre jedoch ein Fehler, diese Reihenfolge schon bei der Deklaration der entsprechenden Dienstleistungen einzufrieren. Die Festlegung einer solchen Reihenfolge ist ausschließlich Sache der Kunden. Natürlich gibt es Gründe festzulegen, daß eine Operation wie das Herausnehmen von Elementen aus einer Liste nur ausgeführt werden kann, wenn zuvor eine andere Operation – das Einfügen von mindestens einem Element – stattgefunden hat. Dies ist aber eine Vorbedingung für die Ausführbarkeit der Operation und sollte auch als solche formuliert werden. Es liegt dann in der Verantwortung des Kunden, was er damit macht. Eine Folge dieses Prinzips ist, daß es keine Gründe gibt, die Anzahl der features einer Klasse zu beschränken. Hat eine Operation einen logischen Bezug zu dieser Klasse, so kann man sie hinzufügen, auch wenn unklar ist, ob sie von vielen Kunden benötigt wird. 2 Das Problem, daß eine Klasse eine unüberschaubare Größe bekommen kann, ist verhältnismäßig gering, da eine zu große Anzahl von Dienstleistungen (ein zu breites Spektrum im Lager) selten stört, da man sich bei der Betrachtung von Informationen auf die features beschränken kann, die man tatsächlich braucht. Einzig die Suche nach noch unbekannten, aber eventuell brauchbaren features wird durch eine zu große Anzahl von features etwas erschwert. Anders ist es beim Code einer einzelnen Routine. Dieser sollte kurz und überschaubar bleiben, was aber bei der objektorientierten Sichtweise von Routinen als wohldefinierte Einzeldienstleistung selten ein Problem werden wird. Aufgrund der Hervorhebung des Wiederverwendbarkeitsaspektes eignen Klassen sich besonders für einen Entwurfsstil, der sich stark auf bereits existierende Module stützt (“Bottom-up Entwurf”). 3 Die Stärke der objektorientierten Programmierung liegt in der Aktivierung fertiger Programmteile. Deshalb lohnt es, sich mit vordefinierten Klassen der Eiffel-Basisbibliothek und gegebenenfalls auch anderer Bibliotheken vertraut zu machen und neu zu entwickelnden Klassen möglichst allgemein zu gestalten, damit sie später auch in anderen Lösungen verwendet werden können. 2Das Problem, daß hierdurch beim Zusammensetzen eines Systems viel unbenutzter Code erzeugt wird, kann durch den Eiffel-Optimierer gelöst werden. 3Natürlich kann man den Entwurf nicht losgelöst von der eigentlichen Problemstellung durchführen. Ein reiner “Top-Down Entwurf”, wie er früher favorisiert wurde, konzentriert sich aber zu sehr auf das Problem und man muß wichtige Entscheidungen über die Softwarestruktur in einer Phase fällen, in der das Bild noch sehr vage ist und Wesentliches von Unwesentlichem noch nicht getrennt werden kann. Dadurch – und vor allem durch Vernachlässigung der Tatsache, daß es bereits Lösungen für ähnlich gelagerte Problemstellungen geben kann – wird der Entwickler von der Komplexität des Problems geradezu erschlagen und Fehlentscheidungen werden gravierende Folgen auf das entstehende Produkt haben. Top-Down Entwicklung von Programmen bietet sich erst während der Implementierungsphase an, nachdem die Struktur des Gesamtsystems bereits so weit untergliedert ist, daß jedes Teilproblem überschaubar ist.
- Seite 96 und 97: über den Namen eines Attributs Wer
- Seite 98 und 99: • Die Veränderung und Auswertung
- Seite 100 und 101: 3.5 Copy- und Referenz-Semantik In
- Seite 102 und 103: 3.5.2 expanded: Klassen mit Copy-Se
- Seite 104 und 105: class LIST[X] creation new feature
- Seite 106 und 107: Dieses Problem wird durch das Konze
- Seite 108 und 109: 3.7.1 Zusicherungen Eiffel logische
- Seite 110 und 111: class ARRAY[X] creation make featur
- Seite 112 und 113: class ARRAY[X] creation make featur
- Seite 114 und 115: formulieren und zu überwachen. Eig
- Seite 116 und 117: 3.8.2 Export geerbter Features Norm
- Seite 118 und 119: Ist also p ein Personenobjekt und a
- Seite 120 und 121: Definition 3.8.5 (Konformität) Ein
- Seite 122 und 123: Um die Beschreibung des Typsystems
- Seite 124 und 125: Nachkommenklassen aber folgt entity
- Seite 126 und 127: deferred class LIST[X] feature leng
- Seite 128 und 129: class STUDENT feature universität:
- Seite 130 und 131: Das Problem ist nun, daß beide Elt
- Seite 132 und 133: Prinzipiell wäre es sogar möglich
- Seite 134 und 135: Das bedeutet also, daß die Erbenkl
- Seite 136 und 137: Die eigentliche Montage eines Syste
- Seite 138 und 139: Verständlichkeit der Module: Die L
- Seite 140 und 141: dynamische Semantik: Die dynamische
- Seite 142 und 143: Constant ::= Manifest constant | Id
- Seite 145: Kapitel 4 Systematische Entwicklung
- Seite 149 und 150: jedoch dringend zu empfehlen, jegli
- Seite 151 und 152: • Ausleihe - Bücher werden nach
- Seite 153 und 154: Der Anwender ist kein echter Klient
- Seite 155 und 156: Es sei an dieser Stelle angemerkt,
- Seite 157 und 158: einen Rechner überprüft werden ka
- Seite 159 und 160: Definition 4.2.3 (Korrektheit von R
- Seite 161 und 162: Programmkonstruktion keine Rolle, d
- Seite 163 und 164: Eine Wertzuweisung entity := Ausdru
- Seite 165 und 166: 4.3.2.1 Die Rolle formaler Paramete
- Seite 167 und 168: Für eine korrekt implementierte Pr
- Seite 169 und 170: { pre} Anweisung1 { p} , { p} Anwei
- Seite 171 und 172: die alle dieselbe Variable x betref
- Seite 173 und 174: 4.3.4.4 Verifikation Die Verifikati
- Seite 175 und 176: Dieser Beweis ist in der folgenden
- Seite 177 und 178: Im allgemeinen ist es sehr schwieri
- Seite 179 und 180: from Init invariant Inv variant Var
- Seite 181 und 182: Programm Zusicherungen Prämissen n
- Seite 183 und 184: entdeckten Fehler stillschweigend h
- Seite 185 und 186: Gegenlesen nicht findet(, aber auch
- Seite 187 und 188: 4.3.10 Die Verifikation rekursiver
- Seite 189 und 190: • Die Variante einer rekursiven R
- Seite 191 und 192: Die Art der Anweisungen ist nicht a
- Seite 193 und 194: Die Formulierung von Ausdrücken mu
- Seite 195 und 196: 4.4.6 Sprachbeschreibung Die nun fo
4.1.2 Grundideen des objektorientierten Entwurfs<br />
Bei einem reinen objektorientierten Vorgehen, wie es durch die Programmiersprache Eiffel unterstützt wird,<br />
ist zu beachten, daß Klassen <strong>der</strong> einzige Strukturierungsmechanismus für Systeme sind. Sie sind unabhängige<br />
Einheiten, obwohl sie durch Benutzung und Vererbung miteinan<strong>der</strong> verbunden sein können. Aus Sicht <strong>der</strong><br />
Programmiersprache liegen alle Klassen auf <strong>der</strong> gleichen Ebene und dürfen – genau wie Routinen – nicht<br />
geschachtelt werden. Diese Eigenständigkeit von Softwareteilen ist wesentlich für Wie<strong>der</strong>verwendbarkeit und<br />
einer <strong>der</strong> wichtigsten Unterschiede zu den gängigen blockstrukturierten Sprachen.<br />
Eine Klasse kann als ein Lager von Dienstleistungen (exportierten features) angesehen werden, die auf den<br />
Exemplaren eines Datentyps verfügbar sind. Diese Dienste werden allen Kunden, die sie überhaupt benutzen<br />
dürfen, gleichermaßen zur Verfügung gestellt:<br />
• Es gibt keine Operationen mit höherer o<strong>der</strong> niedriger Bedeutung, insbeson<strong>der</strong>e keine Hauptfunktion –<br />
selbst dann, wenn bestimmte Dienste häufiger in Anspruch genommen werden als an<strong>der</strong>e.<br />
• Es gibt keine Vorschriften über die Reihenfolge, in <strong>der</strong> Dienstleistungen aufgerufen werden dürfen.<br />
Die Reihenfolge mag zwar bei <strong>der</strong> eigentlichen Ausführung eines Systems von großer Bedeutung sein. Es<br />
wäre jedoch ein Fehler, diese Reihenfolge schon bei <strong>der</strong> Deklaration <strong>der</strong> entsprechenden Dienstleistungen<br />
einzufrieren. Die Festlegung einer solchen Reihenfolge ist ausschließlich Sache <strong>der</strong> Kunden.<br />
Natürlich gibt es Gründe festzulegen, daß eine Operation wie das Herausnehmen von Elementen aus<br />
einer Liste nur ausgeführt werden kann, wenn zuvor eine an<strong>der</strong>e Operation – das Einfügen von mindestens<br />
einem Element – stattgefunden hat. Dies ist aber eine Vorbedingung für die Ausführbarkeit <strong>der</strong><br />
Operation und sollte auch als solche formuliert werden. Es liegt dann in <strong>der</strong> Verantwortung des Kunden,<br />
was er damit macht.<br />
Eine Folge dieses Prinzips ist, daß es keine Gründe gibt, die Anzahl <strong>der</strong> features einer Klasse zu beschränken.<br />
Hat eine Operation einen logischen Bezug zu dieser Klasse, so kann man sie hinzufügen, auch wenn unklar ist,<br />
ob sie von vielen Kunden benötigt wird. 2 Das Problem, daß eine Klasse eine unüberschaubare Größe bekommen<br />
kann, ist verhältnismäßig gering, da eine zu große Anzahl von Dienstleistungen (ein zu breites Spektrum im<br />
Lager) selten stört, da man sich bei <strong>der</strong> Betrachtung von Informationen auf die features beschränken kann,<br />
die man tatsächlich braucht. Einzig die Suche nach noch unbekannten, aber eventuell brauchbaren features<br />
wird durch eine zu große Anzahl von features etwas erschwert.<br />
An<strong>der</strong>s ist es beim Code einer einzelnen Routine. Dieser sollte kurz und überschaubar bleiben, was aber<br />
bei <strong>der</strong> objektorientierten Sichtweise von Routinen als wohldefinierte Einzeldienstleistung selten ein Problem<br />
werden wird.<br />
Aufgrund <strong>der</strong> Hervorhebung des Wie<strong>der</strong>verwendbarkeitsaspektes eignen Klassen sich beson<strong>der</strong>s für einen<br />
Entwurfsstil, <strong>der</strong> sich stark auf bereits existierende Module stützt (“Bottom-up Entwurf”). 3 Die Stärke <strong>der</strong><br />
objektorientierten Programmierung liegt in <strong>der</strong> Aktivierung fertiger Programmteile. Deshalb lohnt es, sich<br />
mit vordefinierten Klassen <strong>der</strong> Eiffel-Basisbibliothek und gegebenenfalls auch an<strong>der</strong>er Bibliotheken vertraut<br />
zu machen und neu zu entwickelnden Klassen möglichst allgemein zu gestalten, damit sie später auch in<br />
an<strong>der</strong>en Lösungen verwendet werden können.<br />
2Das Problem, daß hierdurch beim Zusammensetzen eines Systems viel unbenutzter Code erzeugt wird, kann durch den<br />
Eiffel-Optimierer gelöst werden.<br />
3Natürlich kann man den Entwurf nicht losgelöst von <strong>der</strong> eigentlichen Problemstellung durchführen. Ein reiner “Top-Down<br />
Entwurf”, wie er früher favorisiert wurde, konzentriert sich aber zu sehr auf das Problem und man muß wichtige Entscheidungen<br />
über die Softwarestruktur in einer Phase fällen, in <strong>der</strong> das Bild noch sehr vage ist und Wesentliches von Unwesentlichem noch<br />
nicht getrennt werden kann. Dadurch – und vor allem durch Vernachlässigung <strong>der</strong> Tatsache, daß es bereits Lösungen für ähnlich<br />
gelagerte Problemstellungen geben kann – wird <strong>der</strong> Entwickler von <strong>der</strong> Komplexität des Problems geradezu erschlagen und<br />
Fehlentscheidungen werden gravierende Folgen auf das entstehende Produkt haben. Top-Down Entwicklung von Programmen<br />
bietet sich erst während <strong>der</strong> Implementierungsphase an, nachdem die Struktur des Gesamtsystems bereits so weit unterglie<strong>der</strong>t<br />
ist, daß jedes Teilproblem überschaubar ist.