252
Lernziele zum Kapitel 8 Nach diesem Kapitel sollten Sie 1. verschiedene Verfahren der analytischen Qualitätssicherung kennen und einordnen können, 2. zu einem gegebenen Programm oder Programmteil einen kompakten Kontrollflußgraphen erstellen und kontrollflußorientierte Testverfahren anwenden können, 3. aus einer gegebenen Spezifikation Ein- und Ausgabeäquivalenzklassen ableiten und funktionale Testverfahren anwenden können, 4. Inspektion und Walk-Through erklären und voneinander abgrenzen können, 5. die Zusammenhänge zwischen verschiedenen Testaktivitäten und Testdokumenten erläutern können.
253
8. Analytische Qualitätssicherung 8.1 Motivation und grundlegende Definitionen Mit dem vorigen Kapitel sind unsere Ausführungen über grundlegende (imperative) Programmierkonzepte beendet. Während es bisher darum ging, wie man einfache, wohlstrukturierte Programme erstellt, beschäftigen wir uns im letzten Teil des Kurses mit der analytischen Qualitätssicherung, die Verfahren zur Sicherstellung der Korrektheit bzw. Fehlerfreiheit von Programmen zum Gegenstand hat. Fehler werden zu jedem Zeitpunkt der Programmentwicklung gemacht, und es wäre naiv zu glauben, daß eine höhere Sprache oder eine bessere Methodik die Korrektheit von Programmen so gut wie sicherstellt. Die Fehlerquelle ist der Mensch selbst, und solange Menschen Programme entwickeln, werden diese fehlerhaft sein. Allerdings werden die Anzahl und Qualität der Fehler sehr wohl auch durch die verwendete Entwicklungsmethodik und -umgebung bestimmt. Hieraus resultiert unsere erste Faustregel:
erste Faustregel
Jedes Programm ist fehlerhaft, es sei denn, wir haben uns vom Gegenteil überzeugt. Welche Folgerungen müssen wir daraus ziehen? Zunächst vor allem eine: Die Funktionsfähigkeit von praxistauglichen Programmen darf sich nicht auf Vermutungen stützen, da ein Fehlverhalten oft schwerwiegende Folgen haben kann. Wie können wir nun die Korrektheit bzw. Fehlerfreiheit eines Programms sicherstellen? Dazu erinnern wir uns an die Problemspezifikation in Abschnitt 2.1 und formulieren etwas präziser: ein korrektes Programm erzeugt für jede Eingabe, welche die Vorbedingung erfüllt, eine Ausgabe, welche die Nachbedingung erfüllt. Wird die Korrektheit eines Programms formal bewiesen, sprechen wir von der Verifikation eines Programms. Formale Verifikationsverfahren sind mathematisch kompliziert und sehr aufwendig durchzuführen. Desweiteren sind sie nur anwendbar, wenn eine formale Problemspezifikation vorliegt. Diese Einschränkungen machen Verifikationsmethoden nur in bestimmten Anwendungsbereichen sinnvoll einsetzbar. Für die meisten administrativen und kaufmännischen Programme stehen formale Verifikationen in keinem vernünftigen Kosten-Nutzen-Verhältnis. Ganz anders sieht es aber in Bereichen aus, in denen inkorrekte Programme extrem teure Schäden hervorrufen oder sogar Menschenleben fordern können. Zu diesen sicherheitskritischen Programmen gehören z.B. Steuerungs- und Überwachungsprogramme von (Kern-) Kraftwerken, Verkehrsleitsysteme, Flugüberwachungssysteme (denken Sie an den Frankfurter Flughafen), Programme, die Flugzeuge steuern und überwachen (würden Sie noch in den Airbus steigen, wenn dessen besonders kritischen Softwarekomponenten nicht verifiziert wären?) oder Programme für NASA-Weltraumprojekte. Ist ein vollständiger formaler Nachweis der Korrektheit nicht durchführbar, müssen wir uns nach einfacher durchzuführenden (aber auch schwächeren) Methoden um-
Verifikation
254
Testen
Kurseinheit IV
sehen. Eine solche Methode ist das Testen. Unter Testen verstehen wir jede Tätigkeit, die ein Programm mit dem Ziel untersucht, (möglichst viele) Fehler aufzufinden. Das Testen umfaßt nur die Fehlersuche und nicht die Fehlerkorrektur. Alle noch so ausgeklügelten Testverfahren können jedoch nur die Anwesenheit von Fehlern, aber niemals deren Abwesenheit feststellen. Ein Programm, das nach dem Testen keine Fehler mehr zeigt, muß noch lange nicht korrekt sein. Wir müssen daher daran interessiert sein, so viele Fehler wie möglich zu finden, um die Wahrscheinlichkeit für seine Korrektheit zu erhöhen. Daher sollte der Programmtest nicht vom Autor des Programms selbst durchgeführt werden, da die Gefahr einer Betriebsblindheit zu groß ist. Besser ist es, das Testen von geschulten Testern durchführen zu lassen, die außer ihrer Objektivität auch noch über entsprechendes Spezialwissen verfügen.
zweite Faustregel
Damit erhalten wir eine zweite Faustregel: Der Programmtest sollte möglichst nicht vom Programmautor durchgeführt werden. Wichtig ist, zwischen einem Fehler und einer Fehlerursache zu unterscheiden. Definition 8.1.1
Fehler Fehlerursache
Ein Fehler ist ein nach außen sichtbares Fehlverhalten eines Programms. Eine Fehlerursache ist ein inkorrekter Programmteil, der zu einem Fehler des Programms führt. Verschiedene Fehler können dabei Ausdruck derselben Fehlerursache sein. ❑ Ein Fehler liegt z.B. vor, wenn die Ausgabe eines Programms nicht der Nachbedingung der Spezifikation genügt, das Programm in eine Endlosschleife gerät oder „abstürzt“. Eine Fehlerursache kann z.B. eine falsche Anweisung, eine vergessene Initialisierung oder eine falsche Schleifenbedingung sein. Die Begriffe Testen und Debuggen grenzen wir folgendermaßen ab: Definition 8.1.2
Testen Debuggen
Als Testen bezeichnen wir das gezielte Suchen nach Fehlern. Testen umfaßt nicht das Beheben von Fehlern. Debuggen (engl. to debug: entwanzen) ist das Beheben von Fehlern. Zu einem festgestellten Fehler wird die Fehlerursache aufgespürt und anschließend korrigiert. ❑ Wir unterscheiden weiterhin zwischen Testdaten und Testfällen. Definition 8.1.3
Testdatum Testfall
Ein Testdatum ist ein Tupel bestehend aus zulässigen Eingabedaten und dem erwarteten Ergebnis bzw. den erwarteten Ausgabedaten. Unter einem Testfall verstehen
255
8. Analytische Qualitätssicherung
wir eine aus der Spezifikation oder dem Programmtext abgeleitete Menge von Testdaten. ❑ Für den Rest dieses Kapitels wollen wir die Begriffe Programm und Prozedur/Funktion synonym verwenden, solange keine Mißverständnisse möglich sind. Ein zum Test vorliegendes Programm bezeichnen wir auch als Prüfling. Beispiel 8.1.4 Wir betrachten die folgenden Typdefinitionen und die bekannte Fakultätsfunktion1: type tDefBereich = 0..12; tNatZahl = 0..maxint; function Fakultaet ( inZahl : tDefBereich): tNatZahl; { berechnet die Fakultaet von inZahl >= 0 } var i : integer; temp : tNatZahl; begin temp := 1; for i := 2 to inZahl do temp := i * temp; Fakultaet := temp end; { Fakultaet } Aus der Definition der Fakultätsfunktion wählen wir in Abhängigkeit vom Parameter inZahl zwei Testfälle: Testfall 1:
{(inZahl, inZahl!) | inZahl > 0}
Testfall 2:
{(0, 1)}.
Sinnvolle Eingabedaten für den ersten Testfall sind die obere Grenze des definierten Bereichs und eine oder mehrere Zahlen aus dem Bereichsinneren. Der zweite Testfall besteht nur aus einem Testdatum, dessen Eingabedatum die untere Bereichsgrenze ist. Als Testdaten wählen wir z.B. folgende Zahlenpaare aus Eingabedatum und richtigem Ergebnis: Testdaten 1:
(1, 1), (2, 2), (10, 3628800), (12, 479001600)
Testdaten 2:
(0, 1).
1. Die obere Grenze des Definitionsbereichs berücksichtigt die Bedingung inZahl! < maxint.
Prüfling
256
Kurseinheit IV
Ein dritter möglicher Testfall mit Eingabedaten inZahl < 0 braucht bei dieser Implementation nicht weiter untersucht zu werden. Er wird vom Typsystem von PASCAL erkannt. ❑
8.2 Klassifikation der Verfahren der analytischen Qualitätssicherung analytische Qualitätssicherung
Die verschiedenen Ansätze, welche die Korrektheit bzw. Fehlerfreiheit von Programmen zum Gegenstand haben, werden unter dem Begriff analytische Qualitätssicherung zusammengefaßt. Dazu gehören jedoch nicht nur die Verfahren der Verifikation, Analyse, Testen und Debuggen, sondern auch die zugehörigen organisatorischen Maßnahmen, also die Planung und Kontrolle von Testaktivitäten. Ein wichtiger Gesichtspunkt der analytischen Qualitätssicherung ist, daß sie eine die Programmentwicklung begleitende Tätigkeit darstellt und nicht erst am Ende der Entwicklung, z.B. in Form von Testen, einsetzt. Diesen Aspekt werden wir hier nicht weiter verfolgen, sondern in den Kursen über Software Engineering vertiefen. Die Verfahren der analytischen Qualitätssicherung unterscheiden wir zunächst aufgrund ihrer unterschiedlichen Zielsetzungen. Verifizierende Verfahren beweisen die Korrektheit eines Programms, analysierende Verfahren quantifizieren bestimmte Programmeigenschaften oder stellen diese dar und testende Verfahren versuchen, Fehler aufzudecken.
verifizierende Verfahren
Verifizierende Verfahren beweisen die Korrektheit eines Programms bezüglich seiner Spezifikation. Enthält der Beweis selbst keine Fehler, so ist dies die höchste Qualitätsstufe, die überhaupt erreichbar ist. Allerdings erfordern verifizierende Verfahren eine formale Programmspezifikation, sind sehr komplex und selbst kleine Beweise sind ohne Werkzeugunterstützung kaum möglich. Für sicherheitskritische Anwendungen ist allerdings die Verifikation wichtiger Programmteile unverzichtbar.
analysierende Verfahren
Analysierende Verfahren sind keine Tests im eigentlichen Sinn. Sie erlauben weder Korrektheitsbeweise noch dienen sie direkt der Fehlersuche, sondern sind eher — wertvolle, weil leicht automatisierbare — Hilfsinstrumente. Metriken wie die zyklomatische Zahl oder die Halstead-Metriken erlauben die Quantifizierung von Eigenschaften wie strukturelle Komplexität, Programmlänge oder Kommentierungsgrad und ermöglichen so insbesondere vergleichende Beurteilungen von Dokumenten (z.B. Programmlistings). Die Überschreitung einer kritischen, aus den Erfahrungen früherer Projekte abgeleiteten Maßzahl kann dabei ein Hinweis auf eine erhöhte Fehlerwahrscheinlichkeit sein und zu einer genaueren Untersuchung Anlaß geben. Durch Tabellen und Graphen, wie z.B. Kontrollflußgraphen und cross reference-Listen, werden gezielt spezifische Details eines Dokuments herausgearbeitet und in kondensierter, einfach interpretierbarer Form dargestellt. Einige moderne Compiler bieten derartige Funktionen bereits optional an. Wir werden auf analysierende Verfahren nicht näher eingehen und verweisen den interessierten Leser auf die in [Lig90], [Dum93] angegebene Literatur und speziell [Hal77], [McC76], [Inc90].
zyklomatische Zahl Halstead-Metrik
257
8. Analytische Qualitätssicherung
Testverfahren gliedern sich in statische und dynamische Testverfahren. Bei statischen Verfahren wird das Programm nicht ausgeführt. Statische Verfahren zeichnen sich nach Liggesmeyer durch drei Merkmale aus [Lig90]:
statische Verfahren
- Das Programm wird nicht ausgeführt, - erkannt werden nicht nur fehlerhaftes Verhalten, sondern Fehlerursachen selbst und - die Korrektheit des Programms (bzgl. seiner Spezifikation) wird nicht bewiesen. Ein wesentlicher Vorteil statischer Verfahren ist, daß ihre Anwendung nicht auf ausführbare Programme beschränkt bleibt, sondern daß sie im Grunde für alle Arten von Softwaredokumenten geeignet sind. Außerdem sind statische Verfahren einfach und bei sorgfältigem Einsatz sehr effektiv. Die wichtigste statische Technik ist der Review, in dem ein Softwaredokument von einer Gruppe von Testern mit bestimmter Zielsetzung „gelesen“ wird. Einige der in Reviews entdeckten Fehler können auch durch automatische Werkzeuge wie statische Programmanalysatoren erkannt werden, welche die Programmqualität schnell und einfach verbessern. Statische Programmanalysatoren kann man als Ergänzung des Compilers betrachten, der ja nur die syntaktische Korrektheit des Programms sicherstellt. Im Gegensatz zu statischen Testverfahren setzen dynamische Verfahren zur Fehlererkennung die Ausführbarkeit des Programms voraus. Liggesmeyer grenzt dynamische Testverfahren von den übrigen Verfahren durch drei spezifische Merkmale ab [Lig90]:
Review statische Programmanalysatoren
dynamische Verfahren
- Das Programm wird mit konkreten Testdaten ausgeführt, - das Programm wird in der realen Umgebung getestet, - es handelt sich um Verfahren, welche die Korrektheit des getesteten Programms nicht beweisen können. Dynamische Verfahren sind also systematische Ausprägungen dessen, was wir intuitiv unter Testen verstehen: Das „Füttern“ eines ausführbaren Programms mit mehr oder weniger willkürlich gewählten - Eingabedaten. Anhand der Systematik zur Auswahl der Testfälle werden strukturorientierte, funktionale und diversifizierende dynamische Tests unterschieden. Bei strukturorientierten Tests können die Testfälle kontroll- oder datenflußbezogen abgeleitet werden, funktionale Tests benutzen die Programmspezifikation für die Testfalldefinition und diversifizierende Verfahren vergleichen die Resultate verschiedener Programmversionen (für dieselbe Problemstellung). Insgesamt bilden die ca. 40 verschiedenen dynamischen Verfahren die mit Abstand stärkste Gruppe unter den Testverfahren. Neben der hier vorgestellten Klassifikation dynamischer Verfahren finden sich in der Literatur noch andere Systematisierungen, deren bekannteste wir der Vollständigkeit halber erwähnen. Als Unterscheidungskriterium werden hier Strukturinfor-
strukturorientierter Test funktionaler Test diversifizierende Verfahren
258
White Box-Verfahren Black Box-Verfahren Grey Box-Verfahren
Kurseinheit IV
mationen herangezogen, die für die Definition von Testfällen notwendig sind. White Box-Verfahren basieren auf der (internen) Struktur des Programms, die aus diesem Grund bekannt sein muß. Ist die Struktur für den Test unerheblich, so spricht man von Black Box-Verfahren. Testverfahren, die dieser Klassifikation nicht eindeutig zugeordnet werden können, weil sie zum Teil White Box- und zum Teil Black Box-Charakter besitzen, heißen Grey Box-Verfahren. Eine Klassifikation der Verfahren zur analytischen Qualitätssicherung ist in Abbildung 8.1 schematisch dargestellt, wobei die wichtigsten Vertreter der einzelnen Klassen aufgeführt sind. Die Verfahren, die in diesem Kapitel vorgestellt werden,
259
8. Analytische Qualitätssicherung haben wir mit einem „✍" bzw. „ε“ markiert. Mit„✍" markierte Verfahren gehören zum Pflichtstoff, „ε“ weist auf Exkurse hin. verifizierend
Programmverifikation symbolische Programmausführung
analysierend
Metriken Graphen & Tabellen Anomalienanalyse
testend statisch
statische Programm-Analysatoren ε Reviews ✍ Audits
dynamisch
strukturorientiert kontrollflußbezogen
Anweisungsüberdeckung ✍ Zweigüberdeckung ✍ Bedingungsüberdeckung einfache minimale mehrfache ✍ mehrfache Boundary-interior Pfadtest ✍ Pfadüberdeckung
datenflußbezogen ε
Defs/Uses-Kriterien all defs-Kriterium all uses-Kriterium ε all du-paths-Kriterium Datenkontext-Überdeckung
funktional
funktionale Äquivalenzklassenbildung ✍ Ursache-Wirkungs-Analyse Mutationen-Test ε
diversifizierend
Zufallstest
Pertubationen-Test ε Back to Back-Test ε
✍
Test spezieller Werte ✍ Grenzwertanalyse
✍
Abbildung 8.1 : Klassifikation der Verfahren der analytischen Qualitätssicherung.
❑
260
Kurseinheit IV
8.3 Kontrollflußbezogene Verfahren kontrollflußbezogene Verfahren
Kontrollflußbezogene dynamische Verfahren sind White Box-Verfahren. Anhand der (statischen) Kontrollstruktur des Programms (und ggf. der Programmspezifikation) werden Testfälle bestimmt und das Programm mit entsprechenden Testdaten ausgeführt. Das Programm wird also in erster Linie „gegen sich selbst“ und nur untergeordnet gegen seine Spezifikation getestet. 8.3.1 Kontrollflußgraphen Der Kontrollfluß strukturierter Programme wird durch Anweisungsfolgen, Selektion und Iteration gesteuert. Für kontrollflußbezogene Tests bereitet man den Quelltext üblicherweise graphisch auf, um den Kontrollfluß zu visualisieren. Häufig verwendet wird neben dem Programmablaufplan der Kontrollflußgraph, der eine abstraktere Sichtweise ermöglicht. Wir geben hierzu die folgende Definition an. Definition 8.3.1.1
Kontrollflußgraph
Zweig
Block eines Kontrollflußgraphen
Der Kontrollflußgraph eines Programms P ist ein gerichteter Graph G = (N, E, nstart , nfinal). N ist die Menge der Knoten, E ⊆ N × N die Menge der gerichteten Kanten, nstart ∈N der Startknoten, der keine Vorgänger besitzt, und nfinal ∈N der Endknoten, der keine Nachfolger hat. (Sehen wir von dem ausgezeichneten Endknoten ab, können wir einen Kontrollflußgraphen als verallgemeinerten Baum (vgl. Abschnitt 6.1) betrachten, der Zyklen aufweisen kann und in dem die Mengen der Nachfolger zweier unterschiedlicher Knoten nicht disjunkt sein müssen.) Der Startknoten nstart dokumentiert den Eintrittspunkt des Programms, das heißt den Beginn seines Anweisungsteils, und der Endknoten nfinal kanalisiert alle Austrittspunkte. Knoten ungleich nstart und nfinal stellen Programmzeilen dar, wobei wir unterstellen, daß der Programmtext den im Kurs vorgeschriebenen Layoutregeln genügt. Insbesondere beginnt jede Anweisung in einer neuen Zeile. Eine gerichtete Kante von einem Knoten i zu einem Knoten j, auch Zweig genannt, beschreibt einen statischen Kontrollfluß von i nach j. Ein Block (eines Kontrollflußgraphen) ist eine nicht-leere Folge von Knoten n1, ..., nk ∈ N \ {nstart , nfinal}, k ≥ 1, für deren zugehörige Folge von Programmzeilen z(n1), ..., z(nk) gilt: - Sie wird ausschließlich beginnend mit der ersten Zeile z(n1) ausgeführt. - Wird z(n1) ausgeführt, dann werden danach - falls k ≥ 2 - z(n2), ..., z(nk) in dieser Reihenfolge genau einmal ausgeführt. Für k = 1 wird nur z(n1) ausgeführt. - Die Anzahl k der Programmzeilen ist bezüglich der ersten beiden Eigenschaften maximal. Blöcke sind ein Hilfsmittel, um zu einer kompakteren Form des Kontrollflußgraphen zu gelangen, da alle Knoten eines Blocks zu einem einzigen Knoten reduziert werden können. Eine weitere Reduktion der Anzahl der Knoten erhalten wir, indem wir für Programmzeilen, die nur aus Schlüsselworten (z.B. begin, end, repeat, else, ...) bestehen, keinen eigenen Knoten vorsehen, sondern sie einem Nachbar-
261
8. Analytische Qualitätssicherung
knoten zuordnen. Ist diesem Nachbarknoten keine derartige Programmzeile (mit nur einem Schlüsselwort) zugeordnet, bricht das Verfahren ab. Anderenfalls wird das Verfahren iteriert. Außerdem werden Kommentarzeilen nicht berücksichtigt. Wir nennen einen Kontrollflußgraphen, dessen Knoten bzw. Blöcke nach obigen Regeln reduziert worden sind, einen kompakten Kontrollflußgraphen. Eine Folge aus Knoten und Blöcken, in der aufeinanderfolgende Elemente im Kontrollflußgraphen durch einen Zweig verbunden sind und die mit dem Startknoten beginnt und mit dem Endknoten endet, heißt Pfad (durch den Kontrollflußgraphen). ❑ Kontrollflußbezogene Verfahren zielen darauf ab, eine bestimmte Menge von Pfaden beim Test zu durchlaufen. Zu jedem Pfad erhalten wir einen (evtl. leeren) assoziierten Testfall, indem wir die (laut Programmspezifikation) zulässigen Eingabedaten auf genau solche einschränken, welche die Ausführung der dem Pfad zugeordneten Anweisungsfolge (und nur dieser) bewirken. Wir unterscheiden in Bezug auf die tatsächlich ausgeführten Pfade folgende Überdeckungskriterien:
kompakter Kontrollflußgraph
Pfad
assoziierter Testfall
Die Anweisungsüberdeckung (auch C0-Test) ist das einfachste Kriterium. Die Testfälle werden so gewählt, daß alle Knoten des Kontrollflußgraphen mindestens einmal besucht werden.
Anweisungsüberdekkung, C0-Test
Die Zweigüberdeckung (C1-Test) ist strenger als die Anweisungsüberdeckung. Es sind alle durch Selektion oder Iteration bedingten Verzweigungen im Kontrollfluß mindestens einmal zu verfolgen.
Zweigüberdeckung, C1-Test
Die Bedingungsüberdeckung zieht die Bedingungen in den Schleifen und Auswahlkonstrukten des zu testenden Programms zur Definition von Tests heran.
Bedingungsüberdekkung
Die Pfadüberdeckung nimmt unter den kontrollflußbezogenen Verfahren eine Sonderstellung ein. Sie stellt in ihrer vollständigen Form das umfassendste kontrollflußorientierte Kriterium dar, ist jedoch aufgrund der bereits in kleinen Programmen unpraktikabel hohen Anzahl von Testfällen kaum überprüfbar. Aus diesem Grund existieren Testverfahren, die sich der vollständigen Pfadüberdeckung auf unterschiedliche Arten annähern.
Pfadüberdeckung
Im folgenden stellen wir zu jedem Überdeckungskriterium ein konkretes Testverfahren vor. Beispiel 8.3.1.2 Als begleitendes Beispiel dient uns eine PASCAL-Funktion, der folgende Problembeschreibung zugrunde liegt. Gesucht ist eine Funktion, der als Parameter ein Ziffernstring übergeben wird, die den Wert der dadurch definierten (positiven) Zahl errechnet und diesen zurückliefert. Der Ergebnistyp ist real.
begleitendes Beispiel
262
Kurseinheit IV
Zur Definition eines Ziffernstrings geben wir folgende Syntaxdiagramme an: Ziffer
0 1 2 3 4 5 6 7 8 9
ganzZahl
Ziffer ganzZahlNichtLeer Ziffer
Dezimalbruch .
ganzZahl
ganzZahlNichtLeer
Ziffernstring
Dezimalbruch
ganzZahlNichtLeer
.
Beispiele für zulässige Ziffernstrings sind: 17.25
.25
17
17.
Beispiele für unzulässige Ziffernstrings sind: leere Eingabe
.
A.25
17.A
Nur ein Ziffernstring, der gemäß den obigen Syntaxdiagrammen aufgebaut ist, wird als korrekte Eingabe anerkannt und sein Wert berechnet. Jeder Ziffernstring, der nicht dieser Spezifikation entspricht, führt zur Rückgabe eines Fehlercodes.
8. Analytische Qualitätssicherung
Zur Bearbeitung des übergebenen Ziffernstrings werden die Funktionen length, liesZeichen und pruefeZeichen benutzt. length bestimmt die Länge einer Zeichenkette, liesZeichen liefert das Zeichen an der angegebenen Position einer Zeichenkette zurück und pruefeZeichen überprüft, ob das Zeichen an der angegebenen Position aus der Menge {’0’, ’1’, ’2’, ’3’, ’4’, ’5’, ’6’, ’7’, ’8’, ’9’} stammt. Außerdem setzen wir die Existenz der Funktion konvertiere voraus, die zur Konvertierung einer Ziffer (Typ char) in die zugehörige real-Zahl eingesetzt wird. Alle vier Funktionen sind bereits getestet bzw. verifiziert, so daß wir ihre Korrektheit voraussetzen können. In der untenstehenden entsprechenden PASCAL-Funktion werteZiffernfolgeAus haben wir auf Kommentare aus Platzgründen weitgehend verzichtet.1 function werteZiffernfolgeAus ( inZiffernString : string) : real; { liefert den Wert des uebergebenen Ziffernstrings als real-Zahl zurück, benutzt die Funktionen length, liesZeichen, pruefeZeichen und konvertiere } const FEHLERCODE = -1.0; type tWoBinIch = (vorDemKomma, nachDemKomma); tNatZahl = 0..maxint; var Zeichen : char; Wert, Genauigkeit : real; Position : tNatZahl; woBinIch : tWoBinIch; fehlerfrei : boolean; begin Wert := 0.0; Genauigkeit := 1.0; woBinIch := vorDemKomma; fehlerfrei := true; Position := 1; while (Position