Grundlagen der Informatik I “Programmierung”
Grundlagen der Informatik I “Programmierung” Grundlagen der Informatik I “Programmierung”
Der häufigste Fehler, der bei der Bestimmung der Vorbedingung einer Wertzuweisung gemacht wird, ist die Umkehr der Richtung. Wie man sich leicht am Beispiel y=1 und b=2 klarmachen kann, gilt nicht { y 0 x > -m wp(x:=x+m, a>0) ≡ a>0 keine Änderung! wp(x:=l, x>m) ≡ l>m 4.3.2 Routinenaufruf Die Verwendung von Routinen haben wir schon im Abschnitt 3.3 angesprochen. Wir wollen dies nun vertiefen und um die zugehörigen Verifikationsregeln ergänzen. Routinen sind ein wichtiges Strukturierungsmittel bei der Implementierung von Klassen. Sie unterstützen die schrittweise Verfeinerung, da sie ermöglichen, die Programmiersprache durch selbstdefinierte Anweisungen und Ausdrücke an das jeweiligen Problem anzupassen. Dabei müssen wir unterscheiden zwischen Prozeduren und Funktionen: • Die Definition einer Prozedur entspricht der Beschreibung einer komplexeren Anweisung. Der Aufruf einer Prozedur ist somit die Durchführung einer selbstdefinierten Anweisung. Prozeduraufrufe sind neben der Wertzuweisung die einzige Form einer elementaren Anweisung. 20 Alle anderen Sprachkonstrukte bieten nur die Möglichkeit, gegebene Anweisungen zu neuen zusammenzusetzen. • Im Kontrast dazu beschreibt eine Funktion einen komplexeren Ausdruck im Sinne von Abschnitt 4.4 und nicht etwa eine Anweisung. Ein Funktionsaufruf führt also zur Berechnung eines selbstdefinierten Ausdrucks. Dies ermöglicht auch in imperativen Sprachen ein weitgehend funktionales Programmieren, wo dies von der Problemstellung her angebracht ist – wie zum Beispiel bei der Berechnung arithmetischer Funktionen wie der Fakultät oder des größten gemeinsamen Teilers. Die Verwendung von Routinen, um Probleme zu verfeinern, ist ein grundlegendes Programmierkonzept vieler Programmiersprachen. In Pascal und ähnlichen Sprachen ist es daher erlaubt, innerhalb von Routinen weitere Routinen zu definieren, um diese noch stärker zu strukturieren. In Eiffel (und C) geht das nicht, da Eiffel Routinen als Dienstleistungen von Klassen versteht und nicht etwa als selbständiges Strukturierungskonzept. In Eiffel dürfen innerhalb von Routinen keine weiteren Routinen deklariert werden. Routinen sind im wesentlichen als Kurzbeschreibung längerer Programmteile anzusehen. Der Name der Routine ist eine Abkürzung für den Anweisungsteil, der beim Aufruf der Routine ausgeführt wird. 21 Durch die Verwendung formaler Argumente wird dieser Anweisungsteil vielseitiger anwendbar, da nun die Routine zur Abkürzung aller Programmstücke benutzt werden kann, die bis auf bestimmte Parameter identisch sind. 20 Der Aufruf einer Initialisierungsprozedur ist in diesem Zusammenhang eine spezielle Form des Prozeduraufrufs. 21 Es sei an dieser Stelle noch erwähnt, daß das Routinenkonzept über den Dienstleistungs- und Strukturierungscharakter hinaus noch die Einbindung externer Routinen ermöglicht, die in anderen Programmiersprachen implementiert wurden. Dies ermöglicht es, “alte” und zum Teil sehr effiziente Software weiterzuverwenden, anstatt sie erneut in Eiffel codieren zu müssen, und dennoch klar definierte Eiffel-Schnittstellen zur Verfügung zu haben. Eine ausführlichere Beschreibung dieser Möglichkeit findet man in [Meyer, 1992, Kapitel 24].
4.3.2.1 Die Rolle formaler Parameter In erster Näherung kann man Routinenaufrufe als versteckte Textersetzungen im Programm auffassen: anstelle des Routinennamens wird der Anweisungsteil eingesetzt, in dem wieder jedes Vorkommen eines formalen Parameters durch den aktuell angegebenen Wert ersetzt wird. Die tatsächliche Realisierung eines Routinenaufrufs wird zwar (zugunsten von Effizienz, lokalen Variablen und der Möglichkeit von Rekursion) völlig anders gestaltet, aber diese Sichtweise erklärt die Beschränkungen von Eiffel im Umgang mit formalen Argumenten: Entwurfsprinzip 4.3.3 (Geschützte formale Argumente) Die formalen Argumente y1,...,yn einer durch r(y1:T1,...,yn:Tn) is do ... end definierten Routine sind innerhalb des Anweisungsteils von r geschützt und dürfen nicht direkt verändert werden. Eine direkte Veränderung von yi ist dabei eine Wertzuweisung der Form yi:=Ausdruck oder eine Veränderung des in yi enthaltenen Verweises durch Aufruf von Initialisierungsprozeduren, falls der Typ von yi eine Klasse ist. Die Art der Operationen, die eine Routine auf ihren Argumenten ausführen darf, ist also stark eingeschränkt: aktuelle Parameter werden als Werte übergeben, die nicht verändert werden dürfen. Man beachte jedoch, daß dieses Verbot nur für direkte Veränderungen gilt. Es ist durchaus erlaubt, ein Objekt zu ändern, auf das ein formales Argument yi verweist, denn hierdurch wird der Wert von yi selbst ja nicht verändert. Ein Aufruf wie yi.copy(yj) ist also durchaus erlaubt, nicht jedoch yi:=yj. Die Konsequenz davon ist, daß beim Aufruf einer Routine beliebige Ausdrücke als formale Parameter angegeben werden dürfen, solange die Typbedingung eingehalten wird. Es gibt jedoch nur eine Möglichkeit, Berechnungsergebnisse an die aufrufende Stelle zurückzuliefern, nämlich als Resultat einer Funktion. Da dieses Resultat seinerseits von einem (ggf. expanded) Klassentyp sein darf, wird hierdurch die Möglichkeit, mehrere Werte gleichzeitig zu übergeben, nicht eingeschränkt. Auf diese Art erzielt man eine klare Trennung von Wertberechnungen und Veränderungen von Objekten, was erheblich zur Verständlichkeit von Routinen beiträgt. Prozeduren (“O-Funktionen”) sind ausschließlich zur Veränderungen von Objekten da und können keine Werte zurückliefern. Funktionen (“V-Funktionen”) berechnen Werte (values) und sollten Objekte – zumindest ihre äußere Erscheinung – unverändert lassen. 22 Letzteres kann jedoch vom Compiler nicht erzwungen werden und muß daher ein methodischer Hinweis bleiben. 4.3.2.2 Lokale und qualifizierte Aufrufe Der Aufruf einer Prozedur 23 r als Anweisung kann entweder lokal oder entfernt geschehen. • Ein lokaler Aufruf bezieht sich auf das aktuelle Exemplar und ist unqualifiziert wie in r (ohne Argumente) oder r(A1,...,An) (mit Argumenten). • Ein entfernter Aufruf wird auf ein Objekt angewandt, das durch einen beliebigen Ausdruck dargestellt werden darf, und ist qualifiziert wie in entity.r (einfache Qualifizierung, ohne Argumente) oder f(a).r ( Qualifizierung durch Funktion, ohne Argumente) oder g(f(a)).h(b,x).z.r(A1,...,An) (mehrstufige geschachtelte Qualifizierung mit Argumenten). Ein mehrstufig qualifizierter Aufruf u.v.r kann als Abkürzung für x:=u.v ; x.r angesehen werden, bei der man sich die Zwischenvariable x erspart. 22 Es ist durchaus möglich, die interne Darstellung eines Objektes zu ändern, ohne daß dies nach außen Wirkung hat. Diese Möglichkeit ist zuweilen auch innerhalb von Funktionen sinnvoll und wird ausführlicher in [Meyer, 1988, Kapitel 7.7] diskutiert. 23 Funktionsaufrufe sind keine Anweisungen sondern nur Ausdrücke, die auf der rechten Seite einer Wertzuweisung, oder in den aktuellen Parametern und Qualifizierungen eines Prozeduraufrufs vorkommen dürfen – wobei Funktionsaufrufe allerdings beliebig geschachtelt sein dürfen. Die hier vorgestellten Regeln gelten allerdings für Funktionen und Prozeduren gleichermaßen.
- 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 und 146: Kapitel 4 Systematische Entwicklung
- Seite 147 und 148: 4.1.2 Grundideen des objektorientie
- 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: Eine Wertzuweisung entity := Ausdru
- 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
- Seite 197 und 198: Programm nachträglich als korrekt
- Seite 199 und 200: Der vollständige Korrektheitsbewei
- Seite 201 und 202: Dies ist die Grundidee der sogenann
- Seite 203 und 204: Als Initalanweisung genügt es, i m
- Seite 205 und 206: Beispiel 4.5.15 (Vertauschen von Se
- Seite 207 und 208: Mit einer wichtigen strategischen A
- Seite 209: Seien Sie sich bewußt, daß gerade
Der häufigste Fehler, <strong>der</strong> bei <strong>der</strong> Bestimmung <strong>der</strong> Vorbedingung einer Wertzuweisung gemacht wird, ist die<br />
Umkehr <strong>der</strong> Richtung. Wie man sich leicht am Beispiel y=1 und b=2 klarmachen kann, gilt nicht<br />
{ y 0 x > -m<br />
wp(x:=x+m, a>0) ≡ a>0 keine Än<strong>der</strong>ung!<br />
wp(x:=l, x>m) ≡ l>m<br />
4.3.2 Routinenaufruf<br />
Die Verwendung von Routinen haben wir schon im Abschnitt 3.3 angesprochen. Wir wollen dies nun vertiefen<br />
und um die zugehörigen Verifikationsregeln ergänzen.<br />
Routinen sind ein wichtiges Strukturierungsmittel bei <strong>der</strong> Implementierung von Klassen. Sie unterstützen die<br />
schrittweise Verfeinerung, da sie ermöglichen, die Programmiersprache durch selbstdefinierte Anweisungen<br />
und Ausdrücke an das jeweiligen Problem anzupassen. Dabei müssen wir unterscheiden zwischen Prozeduren<br />
und Funktionen:<br />
• Die Definition einer Prozedur entspricht <strong>der</strong> Beschreibung einer komplexeren Anweisung. Der Aufruf<br />
einer Prozedur ist somit die Durchführung einer selbstdefinierten Anweisung. Prozeduraufrufe sind neben<br />
<strong>der</strong> Wertzuweisung die einzige Form einer elementaren Anweisung. 20 Alle an<strong>der</strong>en Sprachkonstrukte<br />
bieten nur die Möglichkeit, gegebene Anweisungen zu neuen zusammenzusetzen.<br />
• Im Kontrast dazu beschreibt eine Funktion einen komplexeren Ausdruck im Sinne von Abschnitt 4.4<br />
und nicht etwa eine Anweisung. Ein Funktionsaufruf führt also zur Berechnung eines selbstdefinierten<br />
Ausdrucks. Dies ermöglicht auch in imperativen Sprachen ein weitgehend funktionales Programmieren,<br />
wo dies von <strong>der</strong> Problemstellung her angebracht ist – wie zum Beispiel bei <strong>der</strong> Berechnung arithmetischer<br />
Funktionen wie <strong>der</strong> Fakultät o<strong>der</strong> des größten gemeinsamen Teilers.<br />
Die Verwendung von Routinen, um Probleme zu verfeinern, ist ein grundlegendes Programmierkonzept vieler<br />
Programmiersprachen. In Pascal und ähnlichen Sprachen ist es daher erlaubt, innerhalb von Routinen weitere<br />
Routinen zu definieren, um diese noch stärker zu strukturieren. In Eiffel (und C) geht das nicht, da Eiffel<br />
Routinen als Dienstleistungen von Klassen versteht und nicht etwa als selbständiges Strukturierungskonzept.<br />
In Eiffel dürfen innerhalb von Routinen keine weiteren Routinen deklariert werden.<br />
Routinen sind im wesentlichen als Kurzbeschreibung längerer Programmteile anzusehen. Der Name <strong>der</strong> Routine<br />
ist eine Abkürzung für den Anweisungsteil, <strong>der</strong> beim Aufruf <strong>der</strong> Routine ausgeführt wird. 21 Durch die<br />
Verwendung formaler Argumente wird dieser Anweisungsteil vielseitiger anwendbar, da nun die Routine zur<br />
Abkürzung aller Programmstücke benutzt werden kann, die bis auf bestimmte Parameter identisch sind.<br />
20 Der Aufruf einer Initialisierungsprozedur ist in diesem Zusammenhang eine spezielle Form des Prozeduraufrufs.<br />
21 Es sei an dieser Stelle noch erwähnt, daß das Routinenkonzept über den Dienstleistungs- und Strukturierungscharakter<br />
hinaus noch die Einbindung externer Routinen ermöglicht, die in an<strong>der</strong>en Programmiersprachen implementiert wurden. Dies<br />
ermöglicht es, “alte” und zum Teil sehr effiziente Software weiterzuverwenden, anstatt sie erneut in Eiffel codieren zu müssen,<br />
und dennoch klar definierte Eiffel-Schnittstellen zur Verfügung zu haben. Eine ausführlichere Beschreibung dieser Möglichkeit<br />
findet man in [Meyer, 1992, Kapitel 24].