Grundlagen der Informatik I “Programmierung”
Grundlagen der Informatik I “Programmierung”
Grundlagen der Informatik I “Programmierung”
Sie wollen auch ein ePaper? Erhöhen Sie die Reichweite Ihrer Titel.
YUMPU macht aus Druck-PDFs automatisch weboptimierte ePaper, die Google liebt.
4.2 Verifikation<br />
Beim Entwurf haben wir Vor- und Nachbedingungen an Routinen sowie Klasseninvarianten eingeführt, um<br />
“per Vertrag” die Verteilung <strong>der</strong> Aufgaben auf die einzelnen Module eines Softwaresystems klar zu regeln.<br />
Nach dem Entwurf <strong>der</strong> Systemarchitektur und <strong>der</strong> Verteilung <strong>der</strong> Einzelaufgaben ist es nun möglich, jedes<br />
Modul einzeln und unabhängig von den an<strong>der</strong>en zu implementieren. Statt einer globalen Sicht, die bisher<br />
erfor<strong>der</strong>lich war, ist es nun (endlich) möglich, lokal zu arbeiten, und auf konventionellere Programmierkonzepte<br />
zurückzugreifen, zu denen seit vielen Jahren Erfahrungen vorliegen und weiter entwickelt werden.<br />
Während dieser Implementierungsphase sind natürlich ganz an<strong>der</strong>e Qualitäten gefragt als beim Entwurf, in<br />
dem Erweiterbarkeit und Wie<strong>der</strong>verwendbarkeit die wichtigste Rolle spielten. Nun, da die Verträge erstellt<br />
sind, geht es darum, daß sie auch eingehalten werden. Ansonsten wäre die Modularisierung wertlos und eine<br />
Aufteilung <strong>der</strong> Verantwortung auf mehrere Softwareentwickler unmöglich, denn man könnte sich ja nicht<br />
darauf verlassen, daß die benutzten Dienstleitungen an<strong>der</strong>er Module auch so funktionieren, wie es vereinbart<br />
war. Zuverlässigkeit, also Korrektheit und Robustheit stehen bei <strong>der</strong> Implementierung von Klassen und ihren<br />
einzelnen Routinen im Vor<strong>der</strong>grund. Die Frage, die sich ein verantwortungsbewußter Programmierer nun<br />
stellen sollte, lautet: “Wie implementiere ich eine Routine, <strong>der</strong>en Zuverlässigkeit ich sicherstellen kann?”. Bei<br />
<strong>der</strong> Beantwortung dieser Frage sind zwei Aspekte zu berücksichtigen.<br />
Zum einen muß man natürlich wissen, welche Hilfsmittel überhaupt bei <strong>der</strong> Implementierung zur Verfügung<br />
stehen, also aus welchen Programmkonstrukten und elementaren Ausdrücken man den Anweisungsteil einer<br />
Routine aufbauen kann. Hier gibt es zwischen den meisten höheren Programmiersprachen nur geringfügige<br />
Unterschiede. Fast alle bieten Konstrukte für die in Abschnitt 1.3.2 angedeuteten Strukturierungskonzepte<br />
für Implementierungen an – also für eine Verfeinerung in eine Folge kleinerer Teilschritte, Fallunterscheidung,<br />
Wie<strong>der</strong>holung, Prozeduralisierung und ggf. auch Rekursion. 10 Da diese konzeptionell nicht so schwer zu verstehen<br />
sind und Probleme allenfalls bei <strong>der</strong> syntaktischen Beschreibungsform <strong>der</strong> Programmiersprache auftreten<br />
können, kann man sich die für eine Implementierung nötigen Grundkenntnisse relativ leicht aneignen.<br />
Zum zweiten aber – und das ist wesentlich schwieriger – muß man wissen, wie man diese Programmierkonstrukte<br />
einsetzt, um ein zuverlässiges Programm zu erhalten. Dabei läßt sich die Frage, wie man denn<br />
überhaupt eine lauffähige Routine erstellt, von <strong>der</strong> Frage nach <strong>der</strong> Korrektheit dieser Routine nicht trennen.<br />
Denn wer ein Programm entwickelt, <strong>der</strong> wird auch eine Vorstellung davon haben, warum dieses Programm<br />
denn so funktionieren soll, wie es vorgesehen ist. Wer also ein zuverlässiges Programm erstellen möchte, <strong>der</strong><br />
sollte prinzipiell auch in <strong>der</strong> Lage sein, die Korrektheit zu garantieren. Warum reichen hierfür die logischen<br />
Ausdrücke in Invarianten und Vor- und Nachbedingungen von Routinen nicht aus?<br />
Invarianten, Vorbedingungen und Nachbedingungen werden nur bei <strong>der</strong> Programmdurchführung kontrolliert,<br />
wenn die Durchführung an dieser Prüfstelle vorbeikommt. Ist die Prüfung negativ, so bedeutet dies, daß<br />
die Vorstellungen des Programmierers vom Programmablauf und <strong>der</strong> tatsächliche Programmablauf nicht übereinstimmen:<br />
ein Fehler wurde entdeckt. Ist aber die Prüfung in allen durchgeführten Fällen positiv, dann weiß<br />
man nur, daß in diesen speziellen Fällen kein Fehler gefunden wurde. Das sagt aber überhaupt nichts darüber<br />
aus, wie sich das Programm in allen an<strong>der</strong>en Fällen verhält, die in seinem späteren Einsatz vorkommen mögen.<br />
Man kann die Korrektheit eines Programms nicht mit Programmabläufen prüfen.<br />
Deshalb ist es nötig, die Korrektheit von Programmen völlig unabhängig von konkreten Eingabewerten beweisen<br />
(verifizieren) zu können, und dies in einer mathematisch präzisen Form zu tun. Prinzipiell ist es sogar<br />
wünschenswert, den Beweis in einem logischen Kalkül – also einer Erweiterung des in Abbildung 2.14 auf Seite<br />
49 vorgestellten Ableitungskalküls für die Prädikatenlogik – auszuführen, da dieser Beweis seinerseits durch<br />
10 Differenzen liegen nur in <strong>der</strong> syntaktischen Beschreibungsform dieser Konstrukte, dem Angebot mehrerer ähnlicher Konstrukte<br />
(z.B. while und for-Schleife in Pascal), und dem Umfang <strong>der</strong> vordefinierten Ausdrücke, die man nicht mehr selbst<br />
programmieren muß. Deshalb genügt es auch, diese Konzepte exemplarisch an <strong>der</strong> Sprache Eiffel zu besprechen. Hat man sie<br />
einmal verstanden, so kann man relativ leicht dasselbe Problem in einer an<strong>der</strong>en Programmiersprache implementieren, indem<br />
man im entsprechenden Manual nach <strong>der</strong> Syntax vergleichbarer Konstrukte sucht.