5.3 Korrektheit und Verifikation

Slides:



Advertisements
Ähnliche Präsentationen
Schnelle Matrizenoperationen von Christian Büttner
Advertisements

Invariante und Algorithmenentwurf
Rekursion vs. Iteration
Software-Engineering II Eingebettete Systeme, Softwarequalität, Projektmanagement Prof. Dr. Holger Schlingloff Institut für Informatik der Humboldt.
Zusammenfassung der Vorwoche
Suche in Texten (Stringsuche )
3. Berechenbarkeit Wann ist eine Funktion (über den natürlichen Zahlen) berechenbar? Intuitiv: Wenn es einen Algorithmus gibt, der sie berechnet! Was heißt,
Lösung 6.3 Denksportaufgabe
Spec# Proseminar Assertions im SS 2007 Uni Paderborn Andreas Martens Betreuer: Dipl. Inform. Björn Metzler.
Terminierung und Deadlocks Enkhbat Daginaa Betreuerin Prof. Heike Wehrheim Totale Korrektheit.
Java: Dynamische Datentypen
Formale Sprachen – Mächtigkeit von Maschinenmodellen
Übung 6.1Turing-Maschine 1.Machen Sie sich mit der Funktionsweise des Busy Beaver-Programms vertraut Vollziehen sie die 11 Schritte der ersten Turing-Tabelle.
1 Vorlesung Informatik 2 Algorithmen und Datenstrukturen (02 – Funktionenklassen) Prof. Dr. Th. Ottmann.
Vorlesung Informatik 2 Algorithmen und Datenstrukturen (02 – Funktionenklassen) Tobias Lauer.
Vorlesung Informatik 2 Algorithmen und Datenstrukturen (02 – Funktionenklassen) Prof. Dr. Th. Ottmann.
Informatik II, SS 2008 Algorithmen und Datenstrukturen Vorlesung 2 Prof. Dr. Thomas Ottmann Algorithmen & Datenstrukturen, Institut für Informatik Fakultät.
Symbolisches Model Checking mit Binary Decision Diagrams
EINI-I Einführung in die Informatik für Naturwissenschaftler und Ingenieure I Vorlesung 2 SWS WS 99/00 Gisbert Dittrich FBI Unido
Zusammenfassung Vorwoche
Projektplan: Fachgebiet Software Engineering Übersicht © Albert Zündorf, Kassel University.
Minimum Spanning Tree: MST
Modulare Programmierung
LS 2 / Informatik Datenstrukturen, Algorithmen und Programmierung 2 (DAP2)
Grundkonzepte Java - Klassendefinition
LS 2 / Informatik Datenstrukturen, Algorithmen und Programmierung 2 (DAP2)
Effiziente Algorithmen
Javakurs FSS 2012 Lehrstuhl Stuckenschmidt
BIT – Schaßan – WS 02/03 Basisinformationstechnologie HK-Medien Teil 1, 11.Sitzung WS 02/03.
Black Box Algorithmen Hartmut Klauck Universität Frankfurt SS
Quantum Computing Hartmut Klauck Universität Frankfurt WS 05/
Effiziente Algorithmen Hartmut Klauck Universität Frankfurt SS
Beweissysteme Hartmut Klauck Universität Frankfurt WS 06/
Beweissysteme Hartmut Klauck Universität Frankfurt WS 06/
Beweissysteme Hartmut Klauck Universität Frankfurt WS 06/
Beweissysteme Hartmut Klauck Universität Frankfurt WS 06/
Hartmut Klauck Universität Frankfurt WS 06/
Vom Umgang mit Daten. public void myProgram() { int[] saeulenWerte = new int[world.getSizeX()]; for (int i = 0; i < saeulenWerte.length; i++) { saeulenWerte[i]
2.4 Rekursion Klassifikation und Beispiele
Black Box Algorithmen Hartmut Klauck Universität Frankfurt SS
Informatik II Grundlagen der Programmierung Programmieren in C Programmstrukturen / Kontrollstrukturen Hochschule Fulda – FB ET Sommersemester 2014.
PHP: Operatoren und Kontrollstrukturen
1 Albert-Ludwigs-Universität Freiburg Rechnernetze und Telematik Prof. Dr. Christian Schindelhauer Informatik III Christian Schindelhauer Wintersemester.
Korrektheit von Programmen – Testen
7. Formale Sprachen und Grammatiken
Modellbasierte Software- Entwicklung eingebetteter Systeme Prof. Dr. Holger Schlingloff Institut für Informatik der Humboldt Universität und Fraunhofer.
Korrektheit von Programmen – Testen
Wann ist eine Funktion (über den natürlichen Zahlen) berechenbar?
Wiederholte Programmausführung
Praktische Informatik 1
Das Entwurfsmuster Model-View-Controller
2.4 Rekursion Klassifikation und Beispiele
Gliederung 0. Motivation und Einordnung 1. Endliche Automaten
Hello World! Javakurs 2013 Arne Kappen
JavaKara programmieren: Verzweigungen
Datentypen: integer, char, string, boolean
Aufgaben zu Rückgabewerten
Prüfungsbesprechung Barbara Scheuner
Datentypen: integer, char, string, boolean
Kniffelergebnisse.
Die Struktur einer Java-Klasse
Reihungen Prof. Dr. Christian Böhm In Zusammenarbeit mit Gefei Zhang
Unterschiedliche Kontrollstrukturen
SS 04 Christiane Rauh Christian Hellinger
Writing Correct Programs
Es gibt Klassen, die mit der Entwicklungsumgebung ausgeliefert werden
Algorithmen.
Implementieren von Klassen
Schleifen Datenfelder (Arrays) Verzweigungen
The Programming Language Pascal
 Präsentation transkript:

5.3 Korrektheit und Verifikation Korrektheit bedeutet, dass ein Algorithmus oder ein Programm das in der Spezifi-kation beschriebene Problem für beliebige Eingabedaten korrekt löst. Die Korrektheit kann immer nur in Bezug auf eine Spezifikation gezeigt werden!

Tony Hoare … there are two ways of constructing a software design: One way is to make it so simple that there are obviously no deficiencies. The other way is to make it so complicated that there are no obvious de-ficiencies.

Definitionen Ein Programm terminiert, wenn es für alle Eingaben letztendlich anhält. Ein Programm ist partiell korrekt, wenn das Ergebnis im Falle des Terminierens immer korrekt ist. Ein Programm ist total korrekt, wenn es terminiert und partiell korrekt ist. Es gibt zwei Techniken zum Prüfen der Korrektheit: das Testen, die Verifikation (formaler Beweis der Korrektheit).

Testen Als Testen (engl. debugging) bezeichnet man die Ausführung eines Programms für eine Menge von Testdaten. Die Menge der Testdaten ist endlich und in fast allen praktischen Fällen sehr viel klei-ner als die Menge der möglichen Eingabedaten. Deshalb kann Testen nur das Ver-trauen in die Korrektheit eines Programms erhöhen, nicht aber die Korrektheit be-weisen. In der Praxis erweist sich die Auswahl einer angemessenen Testdatenmenge als sehr schwierig. Ebenso schwierig ist die Analyse der Überdeckung eines Programms durch gegebene Testdaten. Hierzu hat man im Software Engineering Hilfsmittel entwickelt.

Ablauf des Testens

Beispiel für das Testen (1) Es ist ein Programm zu schreiben, das zwei ganze Zahlen multipliziert. Als Operatio-nen sollen nur Addition, Subtraktion, Multiplikation mit 2 und Division durch 2 zulässig sein.

Beispiel für das Testen (2) 1 public int mult(int x1, int y1) { 2 int x, y; long z; 3 z = 0; 4 if ((x1 > 0) && (y1 > 0)) { 5 x = x1; 6 y = y1; 7 while (x != 0) { 8 if (x % 2 == 1) 9 z = z + y; 10 y = 2 * y; 11 x = x/2; 12 } 13 } 14 return z; 15 }

Erster Ansatz: Testen als Schwarzer Kasten (engl: black box testing) Testen für alle x1, y1 < 1012 ohne Kenntnis der Programmstruktur ergibt 1012 * 1012 = 1024 Testfälle. Bei 1 ms pro Ausführung sind das ca. 3*1013 Jahre Rechenzeit! Fazit Es kann selbst beim Testen mit Zufallszahlen nur ein sehr kleiner Prozentsatz der möglichen Eingabedaten getestet werden.

Zweiter Ansatz: Testen als Weißer Kasten (engl: white box testing) Testen unter Kenntnis der Programmstruktur. Die Auswahl der Testdaten erfolgt so, dass eine möglichst gute Überdeckung des Programmcodes erreicht wird.

Anweisungstest Die Auswahl der Testdaten erfolgt so, dass jede Anweisung im Programm mindestens einmal ausgeführt wird. Vorteil: unnötige Anweisungen werden entdeckt (solche, die durch keinen Testdatenfall erreicht werden können). Es sind nur relativ wenige Testfälle nötig. Nachteil: Viele wichtige Fälle werden überhaupt nicht getestet.

Beispiel für den Anweisungstest if (b) a; Wenn die Bedingung b erfüllt ist, wird die Anweisung a ausgeführt. Damit ist sowohl die if-Anweisung als auch a einmal durchlaufen worden. Der Fall "b == false" wird nicht getestet!

Verzweigungstest Gegeben sei das Flussdiagramm oder UML-Aktivitätsdiagramm des Programms. Dann werden die Testdaten so ausgewählt, dass jede Verzweigung (Verbindungslinie zwischen Elementen des Diagramms) mindestens einmal durchlaufen wird. Vorteil: bessere Überdeckung als mit dem Anweisungstest. Nachteil: Es werden immer noch nicht alle relevanten Fälle erfasst.

Beispiel für den Verzweigungstest (1) if (b1) a1; else a2; if (b2) a3; else a4; Es gibt vier mögliche Anwei-sungsfolgen für a1 bis a4: a1;a3 a1;a4 a2;a3 a2;a4

Beispiel für den Verzweigungstest (2) Gilt nun für alle Testdatenfälle, dass sie entweder die Kombination b1 true und b2 false oder b1 false und b2 true enthalten, so sind die Anforderungen an den Verzweigungstest erfüllt (alle Pfade im Flussdiagramm werden mindestens einmal durchlaufen), aber die Ablauffolgen a1;a3 a2;a4 werden nicht getestet.

Wegetest (1) Im Wegetest werden alle möglichen Ablauffolgen von Anweisungen mindestens ein-mal durchlaufen. Für das Beispiel mult sehen wir: Es gibt zu viele mögliche Ablauffolgen wegen der Schleife! x1 y1 durchlaufene Anweisungen beliebig 3, 4, 14 1 3, 4, 5, 6, 7, 8, 9, 10, 11, 14 2 3, 4, 5, 6, 7, 8, 10, 11, 7, 8, 9, 10, 11, 7, 14 3 …

Wegetest (2) Reduktion in der Praxis: a) Schleife 0-mal durchlaufen b) Schleife 1-mal durchlaufen c) Schleife k-mal durchlaufen, k  2, fest.

Suche nach typischen Fehlern Mit etwas Erfahrung stellt man fest, dass Programmierer immer wieder ähnliche Feh-ler machen. Man entwirft daher die Testdaten so, dass sie nach diesen typischen Fehlern suchen. Beispiele: Feldindex außerhalb der Grenzen des Feldes (arrays) Anzahl der Schleifendurchgänge +/-1 Namenskollision zwischen lokalen und globalen Variablen und Parametern Rundungsfehler bei Gleitkommaoperationen. Durch Testen können auch Compilerfehler oder Fehler in den Laufzeitroutinen ge-funden werden (im Gegensatz zur Verifikation!).

Code-Inspektion (Code Review) Programmcode kann nicht nur durch den Rechner, sondern auch durch den Pro-grammierer getestet werden. Ein in der Praxis häufig eingesetztes und erstaunlich wirksames Verfahren ist die Code-Inspektion (code review, "Schreibtischtest"). Dabei wird der Programmcode eines Programmierers von einem anderen Pro-grammierer auf mögliche Fehler untersucht. Diese Methode erzieht zugleich zur strukturierten Programmierung und zur Doku-mentation durch Kommentare im Code.

Verifikation Unter Verifikation versteht man den formalen Beweis der Korrektheit eines Pro-gramms durch mathematische Methoden. Auch für die Verifikation gilt, dass sie nur für eine bestehende Spezifikation sinnvoll ist. Dazu muss bewiesen werden, dass das Programm für beliebige Eingabedaten terminiert und jeweils korrekte Ergebnisse liefert.

Formale Verifikation Beweisen statt testen preconditions P, postconditions Q, assertions A Vorteil: Beweis zeigt die Abwesenheit von Fehlern Beweise sind zuverlässig Nachteil Beweise (von Hand) sind mühsam aber es gibt dafür Maschinenunterstützung (ein aktives Forschungsgebiet der Informatik).

Partielle und totale Korrektheit Korrektheit [P] A [Q] wird zerlegt in Terminierung [P] A Wenn P beim Start von A erfüllt ist, wird A terminieren. Partielle Korrektheit {P} A {Q} Wenn P beim Start von A erfüllt ist, und A terminiert, dann wird am Ende Q gelten. {P}{Q} kann man auch als die formale Spezifikation des Programmstücks auffassen.

Assertions - mathematisch Für unsere Assertions erlauben wir jetzt: Boolesche Ausdrücke der Sprache, zum Beispiel x==N && i < 0 && liste[i]==N Quantoren ,  Beispiel: ( i > 0: liste[i]==N)  result == true Konstanten zusätzliche Variablen, die nicht im Programm vorkommen zum Beispiel M, N, A, B, C, Konvention hier: in Großbuchstaben.

Einfache Programme Wir betrachten nur Programme bestehend aus Zuweisungen von Integer-Variablen v = x+5; x = x+1; … if/else if ( x < 0) x = -x; while while (x < n) {summe = summe+x; x = x+1;} Blöcke x = 0; while ( x < 10 ) {summe = summe+x;} Mit diesen Anweisungen kann man bekanntlich jeden Algorithmus programmieren!

? Zuweisungen x > 0 x = x + y; x > y; P v = t ; Q Sei v eine Variable, t ein int-Ausdruck und v = t; eine Zuweisung. Wann ist {P} v=t ; {Q} wahr ? Wie kann man dies bestimmen? Beispiele { x > 0 } x = x+y; { x > y } : immer wahr, unabhängig von x, y. { y > 0 } x = x*y; { x > y } : wahr, falls x > 1 { x > 0 } x = x*y+y; { x > y } : wahr, falls y > 0 ? x > 0 x = x + y; x > y; P v = t ; Q

Substitution Q[x / t] : Jedes nicht gebundene x in Q wird durch t ersetzt. Q logischer Ausdruck, x Variable und t Ausdruck (vom gleichen Typ). Normalerweise durch eine einfache Textersetzung: Beispiel 1: Q  x > y, t  x+y: Q[ x / t]  (x > y)[x / x+y]  x+y > y Beispiel 2: Q  x > y, t  x*y+y: Q[ y / t]  (x > y) [y / x*y+y]  x > x*y+y Beispiel 3: Q   k: liste[k]==x+3, t  x*x Q[x / t]  ( k: liste[k]==x+3)[x / x*x]   k: liste[k]==x*x+3 Durch Quantoren gebundene Variablen werden nicht ersetzt.

Zuweisungsregel P  Q[v / t] {P} v = t; {Q} Wenn P  Q[v / t] wahr ist, dann ist {P} v=t; {Q} wahr: Regel- schreibweise P  Q[v / t] {P} v = t; {Q} Prämisse Konklusion Andere Lesart: Um {P} v = t; {Q} zu zeigen, genügt es, die logische Formel P  Q[v/t] nach-zuweisen. Die Korrektheit eines Programms wird also auf die Gültigkeit einer logi-schen Formel zurückgeführt.

Anwendung der Zuweisungsregel {P} v = t ; {Q} P  Q[v / t] Wir untersuchen einige Programme. Dabei vereinfachen wir die Prämisse immer weiter: x > 0  (x>y)[x / x+y] x > 0  (x+y > y) { x > 0 } x = x+y ; { x > y } { x > 0 } x = x+y ; { x > y } x > 0  (x > 0) true { x > 0 } x = x+y ; { x > y } { x > 0 } x = x+y ; { x > y } Also ist { x > 0 } x = x+y; { x > y } immer wahr.

Verstärkung der Vorbedingung Was muss zu Beginn des Programms S gelten, damit hinterher Q wahr ist? {?} S {Q} Sicher gilt immer: Aus {P} S {Q} und P‘  P folgt {P‘} S {Q} d.h.: mit stärkerer Vorbedingung ist das Programm erst recht richtig. Als Schlussregel geschrieben: Frage: Gibt es immer eine schwächste Vorbedingung P, so dass {P} S {Q} wahr ist? P  P‘ , {P‘} S {Q} {P} S {Q}

Schwächste Vorbedingung für Zuweisungen Beispiel Für welche Vorbedingung P gilt: {P} x = x - y; {x > y} ? Zuweisungsregel: P  (x-y) > y Äquivalent: P  x > 2*y P  x > 2*y ist die Mindestvoraussetzung, damit x = x - y zu einem Zustand führt, in dem x > y gilt. P ist die schwächste Vorbedingung von x=x-y; für x > y Im Allgemeinen gilt: Q[v / t] ist schwächste Vorbedingung von v=t; für Q

Abschwächen der Nachbedingung Wenn Q in einem Zustand z gilt und wenn Q  Q‘ allgemein gültig ist, dann gilt auch Q‘ in Zustand z. Daraus folgt die Regel: Statt {P} S {Q‘} zu beweisen, kann man dann auch beweisen: {P} S {Q} für jedes Q mit Q  Q‘. {P} S {Q}, Q  Q‘ {P} S {Q‘}

Zwischenbehauptungen Längere Programme kann man durch Zwischenbehauptungen aufteilen: { P } S1 S2 { Q } { P } S1 { R } , { R } S2 { Q } Zwischenbehauptung R

Beispiel für die Zerlegung mit Zwischenbehauptungen { sum = 0; i = 0; } { sum = 0; i = 0; while(i < N){ i = i+1; sum = sum+i; } } {N > 0  i == 0  sum == 0} {N > 0  i == 0  sum == 0}  {N > 0  i == 0  sum == 0} { while(i < N){ i = i+1; sum = sum+i;} {sum = (N+1)*N/2} {sum = (N+1)*N/2}

Weitere Zerlegung { sum = 0; } { sum = 0; i = 0; } { i = 0; } {N > 0  sum == 0} { sum = 0; i = 0; } {N > 0  sum == 0}  {N > 0  sum == 0} {N > 0  i == 0  sum == 0} { i = 0; } {N > 0  i == 0  sum == 0}

Ein weiteres Beispiel Wir wollen zeigen: Man kann zwei Integer-Variablen ohne Einführung einer Zwischen- variablen durch folgendes Progamm vertauschen: x = x – y; y = x + y; x = y – x; x’ = x – y; y’ = x’ + y = x – y + y = x x’’= y’ – x’ = x - (x - y) = y

Zwischenbehauptungen finden Zwischenbehauptungen findet man in einem Rückwärtsbeweis als schwächste Vor-bedingung: {x == A  y == B} { x = x-y; y = x+y; } {x == A  y == B} { x = x-y; y = x+y; x = y–x;} {y-x == B  y == A} {y-x == B  y == A}  {y-x == B  y == A} { x = y–x; } {x == B  y == A} {x == B  y == A}

Rückwärtsbeweis (1) { x = x-y;} { x = x-y; y = x+y; } { y = x+y;} Der Rest des Beweises geht fast automatisch: {x == A  y == B} { x = x-y;} {x == A  y == B} { x = x-y; y = x+y; } {(x+y)-x == B  x+y == A} {(x+y)-x == B  x+y == A}  {(x+y)-x == B  x+y == A} {y-x == B  y == A} { y = x+y;} {y-x == B  y == A}

Rückwärtsbeweis (2) { x = x-y;} { y = x+y;} {x == A  y == B} Zum Schluss benötigen wir einige Gleichungen für + und -. {x == A  y == B} x==A  y == B  ((x-y)+y)-(x-y) ==B  (x-y)+y==A { x = x-y;} {(x+y)-x == B  x+y == A} {(x+y)-x == B  x+y == A} (x+y)-x==B  x+y==A  (x+y)-x==B  x+y==A { y = x+y;} {y-x == B  y == A}

Bedingte Anweisungen (1) if ( B ) S1 else S2 zerlegt man in zwei Fälle: Erster Fall: Die Bedingung B ist wahr. Zweiter Fall: Die Bedingung B ist nicht wahr. {P} if (B) S1 else S2 {Q} {P  B} S1 { Q } , {P   B} S2 {Q}

Bedingte Anweisung (2) x = x+y; if (y > 0) x = x+y; else x = x-y; Bedingte Anweisungen zerlegen die Aufgabe in zwei leichtere Teilaufgaben: {x == 5  y > 0} x==5  y > 0  x+y >= 0 x = x+y; {x == 5 } {x >= 0} if (y > 0) x = x+y; else x = x-y; {x == 5   (y > 0)} x==5   (y > 0)  x-y >= 0 x = x-y; {x >= 0} {x >= 0}

Schleifen: Die Schleifen-Invariante Die Schleifeninvariante ist eine Zwi- schenbehauptung, die von der Schleife bewahrt wird. Definition: I ist Invariante der Schleife while (B) S, falls gilt : {I  B} S {I} Wenn also I zu Beginn der Schleife wahr ist, dann auch nach einem Durchlauf nach zwei Durchläufen … … zum Ende der Schleife. Wenn I eine Invariante ist und am Anfang der while-Schleife wahr ist, dann gilt am Ende der while-Schleife: I   B I while- Schleife false B ? I  B true S I I   B an diesen Stellen soll I immer wahr sein

while-Regel { I  B } S { I } { I } while (B) S { I   B } Für eine while-Anweisung: while (B) S benötigt man eine Invariante I: {I  B} S {I} Gilt I vor der while-Anweisung, so gilt nach der while-Anweisung: I   B { I  B } S { I } { I } while (B) S { I   B }

Beispiel: Invariante {M > 0  N > 0} Idee des Programms: Verändere n und m so, dass der ggT gleich bleibt Invariante: ggT(m,n) == ggT(M,N) M, N sind konstant Spezifikation { M > 0  N > 0 } { m = ggT(M,N) } Ist ggT(m,n) == ggT(M,N)  m > 0  n > 0 eine Invariante? {M > 0  N > 0} Zwischenbehauptung {ggT(m,n) == ggT(M,N)  m > 0  n > 0} m=M; n=N; while(m % n != 0) { temp = n; n = m%n; m = temp; } {n = ggT(M,N)}

Assertions in Java Die Sprache Java stellt assertions bereit, allerdings nur rudimentär. Keyword: assert Zwei Varianten von assert-Statements: assert expr1 : expr2 ; assert expr; Semantik: Falls expr1 false ist, wird ein assertion error mit Wert expr2 erzeugt. assert expr  assert expr : null;

Klasseninvarianten kontoStand ≥ 0 1 ≤ topFace ≤ 6 Eine Klasseninvariante ist eine Assertion, die gültig ist für jedes Objekt der Klasse. Jede öffentliche Methode muss die Invariante wiederherstellen. Nur während das Objekt verändert wird, darf die Assertion vorübergehend verletzt sein. Beispiele Bei einem Sparkonto muss immer gelten: kontoStand ≥ 0 Bei einem Würfel muss immer gelten: 1 ≤ topFace ≤ 6 Öffentliche Methoden sollten die Klasseninvariante überprüfen: bei void-Methoden: als letzte Aktion sonst: direkt vor der return-Anweisung

Klasseninvariante und Assertion Assertion für eine Hilfsfunktion

Beispiel: korrektes Datum Die Klasseninvariante garantiert gültige Datumsobjekte in Konstruktoren nach Manipulationen.

Nachteile der Java-Assertions //Suchalgorithmus boolean exists(int n, int[ ] liste){… assert result == true  k:0  k  liste.length: liste[k] == n return result;} Es können nur boolesche Ausdrücke der Sprache getestet werden. Keine Quantoren x  y Wie soll man so die Korrekt-heit eines Suchalgorithmus verifizieren? Wie die Korrekt-heit eines Sortieralgorithmus? geht leider nicht // Sortieralgorithmus int[] sortiere(int[] alt){… assert  x  alt.length:  y  neu.length: alt[x]==neu[y]; return neu;} geht leider nicht

“Verification is a social process“ Das Überzeugen durch einen Beweis in der Mathematik oder genauso auch eine Programmverifikation ist ein sozialer Prozess! Verifikationen sind oft lang und schwer lesbar. Die Spezifikation ist selbst informal; der Schritt vom Informalen zum Formalen ist nie verifizierbar. Die Spezifikation ist außerdem oft sehr umfangreich (zum Beispiel für “Einkommen-steuerberechnung”, “Betriebssystem”, “Compiler”) und in der Regel nicht voll-ständig. Programme interagieren mit der realen (informalen) Umwelt, zum Beispiel mit I/O-Treibern, Benutzerschnittstellen, A/D-Wandlern usw., die nicht verifizierbar sind.

Zusammenfassung Die Korrektheit von Programmen kann man durch Testen und durch Verifikation ver-bessern. Die Korrektheit kann man nur auf der Basis einer Spezifikation beweisen. Durch systematisches Testen kann man viele Fehler finden, aber ein Beweis der Korrektheit für beliebige Eingabedaten ist im Allgemeinen nicht möglich. Durch Verifikation kann man Programme als korrekt beweisen, aber das ist nur für manche Arten von Programmen sinnvoll und in der Praxis häufig sehr schwierig und umständlich.