Melden Sie sich hier an, um auf Kommentare und die Whitepaper-Datenbank zugreifen zu können.

Kein Log-In? Dann jetzt kostenlos registrieren.

Falls Sie Ihr Passwort vergessen haben, können Sie es hier per E-Mail anfordern.

Der Zugang zur Reseller Only!-Community ist registrierten Fachhändlern, Systemhäusern und Dienstleistern vorbehalten.

Registrieren Sie sich hier, um Zugang zu diesem Bereich zu beantragen. Die Freigabe Ihres Zugangs erfolgt nach Prüfung Ihrer Anmeldung durch die Redaktion.

06.04.1990 - 

Hundertprozentige Korrektheit ist eine Illusion, aber

Fehlertolerantes Design macht SW-Bugs weniger wahrscheinlich

Software ist niemals ganz fehlerfrei. Selbst in gut ausgetesteten Programmen können immer wieder Softwarefehler auftreten - mit zum Teil gravierenden Folgen. Unter der Bezeichnung "Fehlertolerante Software" stellt Christhard Pilder* ein Konzept zur Verringerung von Fehlern vor.

Software ist allgegenwärtig. An vielen Stellen regeln und steuern Programme Prozesse, die zum Teil Oberhaupt erst durch Softwareunterstützung realisierbar wurden. Man denke nur an moderne Produktionsanlagen oder gar an Flugzeugsteuerungen, bei denen von der korrekten Funktionsweise der Software Menschenleben abhängen. Auch im kommerziellen Bereich ist Software inzwischen so unentbehrlich, daß Softwarefehler einen beträchtlichen wirtschaftlichen Schaden verursachen können.

Bekanntermaßen hat sich die Qualität der Software aber nicht in dem Maße entwickelt wie ihre Einsatzhäufigkeit. DV-Fachleute wissen aus Erfahrung, daß Software praktisch niemals fehlerfrei ist. Die Theorie zeigt sogar, daß es keine absolut fehlerfreie Software geben kann.

Es gibt eine Reihe praxiserprobter Techniken und Methoden, die Zuverlässigkeit von Software zu erhöhen. Einige dieser Verfahren setzen - wie die strukturierte Analyse - schon in der Anforderungsphase an, andere, zum Beispiel Programminspektionen, in der Programmierung, wieder andere im Testbereich. Doch alle diese Verfahren - auch in ihrer Kombination - bringen normalerweise noch kein vollständig befriedigendes Ergebnis.

Die Grundidee ist Progrommredundanz

Das Konzept der fehlertoleranten Software stellt einen ganz anderen Ansatz zur Senkung der Software-Fehlerquote dar. Die Grundüberlegung dieses Ansatzes ist recht einfach: Da es nicht möglich ist, absolut fehlerfreie Software zu produzieren, muß der Software-Entwickler diese Tatsache in seiner Arbeit explizit berücksichtigen. Die Software soll quasi in die Lage versetzt werden, Fehler selbst zu erkennen und zu beheben. Ähnliche Überlegungen sind auf dem Hardwaresektor bereits seit langem üblich.

Doch wie kann die Software zum einen Fehler erkennen, an die bei der Programmierung niemand gedacht hat (ansonsten wären sie schließlich schon abgefangen worden)? Und wie kann sie zum anderen auf diese Fehler reagieren? Wenn das gelingt, würde sich die Software sozusagen am eigenen Schopf aus dem Sumpf ziehen.

Wie bereits angedeutet, kann Fehlertoleranz nur durch Redundanz an Programmen erreicht werden. Soll ein bestimmtes Modul in einer fehlertoleranten Umgebung ablaufen, so wird es nicht - wie bisher üblich - in einer einzigen Ausführung von einem Programmierer (oder Programmierteam) erstellt. Vielmehr programmieren verschiedene Mitarbeiter unabhängig voneinander jeweils eine Variante desselben Moduls Diese Varianten werden dann auch von verschiedenen Personen getestet.

Auch wenn ein Programmierer einen Fehler macht, der nur unter selten auftretenden Bedingungen sichtbar wird und damit bei normalen Tests unentdeckt bleibt, ist es recht unwahrscheinlich, daß ein anderer Programmierer exakt denselben Fehler programmiert (er wird dafür natürlich andere Fehler gemacht haben).

Liegen also mehrere unabhängig voneinander programmierte und getestete Varianten vor, so arbeitet mit hoher Wahrscheinlichkeit mindestens eine. Variante fehlerfrei, Die eigentliche Schwierigkeit bei der Entwicklung fehlertoleranter Software liegt damit in der Frage: Wie kann erreicht werden, daß jeweils nur die Variante läuft, die den betreffenden Fall fehlerfrei bearbeitet?

Es gibt heute im wesentlichen zwei Verfahren, um Fehlertoleranz zu erreichen: die Recovery-Block-Technik und das N-Version-Programming. Diese beiden Techniken unterscheiden sich vor allem in der Methode, mit der eine korrekt arbeitende Variante herausgefüttert wird.

Eine Variante noch der anderen

Unter Verwendung der Recovery-Block-Technik funktioniert eine fehlertolerante Softwareumgebung folgendermaßen: Zuerst wird eine Variante des betreffenden Programmes gestartet, danach der Output einem Akzeptanztest unterzogen. Ist der Test in Ordnung, so gilt das Programm als beendet. Andernfalls wird der Zustand, der vor Aufruf der Variante geherrscht (daher der Name Recovery Block), und eine zweite Variante gestartet.

Dieser Vorgang läuft so lange, bis entweder eine Variante ein akzeptables Ergebnis erzielt oder aber alle Varianten zu falschen Ergebnissen kommen. Im zweiten Fall kann das Programmsystem nicht mehr ordnungsgemäß weiterarbeiten.

Akzeptanz ist nicht immer einfach

Die Schwierigkeit dieses Verfahrens liegt beim Akzeptanztest. Wie kann ein solcher Test aussehen? Es gibt eine Reihe von Anwendungen, wo ein solcher fest recht einfach zu konstruieren ist. Soll zum Beispiel die Lösung eines komplexen Gleichungssystems gefunden werden, so kann der Akzeptanztest darin bestehen, "die Probe" zu machen, also die gefundene Lösung in das Gleichungssystem einzusetzen und die Gleichungen zu überprüfen.

Ähnliches gilt im kommerziellen Bereich: Hat ein Programm zum Beispiel Zinsen ermittelt, kann - ausgehend von den errechneten Zinsen und dem Zinssatz - das zinspflichtige Kapital ermittelt und dieser Wert mit dein tatsächlichen Kapital verglichen werden. Eine solche Vorgehensweise ist auch bei Kontrollsummen in Buchhaltungsprogrammen denkbar.

In anderen Fällen ist die Entwicklung eines Akzeptanztests nicht so einfach: Geht es zum Beispiel darum, eine optimale Reiseroute zu bestimmen, die eine fest vorgegebene Anzahl von Orten miteinander verbindet (ein klassisches Traveller-Salesman-Problem), wird ein Akzeptanztest schon sehr viel schwieriger.

Zwar wäre es in einem solchen Fall möglich zu kontrollieren, ob wirklich alle vorgegebenen Orte von der vorgeschlagenen Route tangiert werden. Zusätzlich müßte dann die Länge der Route mit einer einfach zu ermittelnden (und keineswegs optimalen) Lösung verglichen werden, um offensichtlich unsinnige oder falsche Lösungen auszuschließen. Damit wäre jedoch keinesfalls sichergestellt, daß die gefundene Route wirklich günstig gewählt ist.

Oft nur noch Plausibilitätsprüfung

Vergleichbare Schwierigkeiten gibt es auch im kommerziellen Bereich - zum Beispiel bei einem Programm, das aufgrund von Daten über Aktienkurse Empfehlungen in bezug auf Aktientransaktionen geben soll. Je schwieriger es ist, ein klares und leicht überprüfbares Kriterium für die Korrektheit eines Programmergebnisses anzugeben, desto mehr wird der Akzeptanztest nur noch Plausibilitäten prüfen können (und ist damit lediglich gegenüber groben Fehlern tolerant).

Durch den Akzeptanztest ergeben sich leider neue Fehlerquellen, denn er wird ebenfalls in Form von Software realisiert und damit selbst fehleranfällig. Zwei wesentliche Fehlertypen kommen hier vor: Zum einen kann der Akzeptanztest ein falsches Ergebnis akzeptieren, wenn er entweder falsch programmiert ist oder eine zu grobe Plausibilität abfragt; zum anderen weisen solche Tests bisweilen korrekte Ergebnisse als falsch zurück.

Ein Akzeptanztest kann die Wahrscheinlichkeit eines Fehlerauftritts nur dann deutlich. senken, wenn seine Komplexität deutlich niedriger ist als die des eigentlichen Programms (weil dann die Wahrscheinlichkeit eines Fehlers entsprechend geringer ist) oder wenn seine Korrektheit aus anderen Gründen sehr viel leichter sicherzustellen ist als die des eigentlichen Programms. Im zweiten Fall ließe sich beispielsweise die Korrektheit des Programms formal untersuchen; möglicherweise liegen auch intensive Testerfahrungen vor.

Eine weitere Schwierigkeit des Recovery-Block-Verfahrens liegt in der Wiederherstellung des Ausgangszustandes, die dann erforderlich wird, wenn eine weitere Variante aufgerufen werden muß. Wurde beispielsweise nur im Hauptspeicher ohne Rückgriff auf sonstige Ressourcen gerechnet, so kann die Wiederherstellung des Ausgangszustandes einfach sein; schwierig bis unmöglich wird sie, wenn Daten gelesen oder verarbeitet werden müssen, die zwischenzeitlich von Dritten abgeändert wurden.

Etwas anders geht das N-Version-Programming an die Sache heran. In diesem Fall werden alle N-Varianten desselben Programms gleichzeitig gestartet. Zum Schluß wertet ein Entscheidungsalgorithmus die Ergebnisse der einzelnen Varianten aus, wobei er definiert, welches von den Ergebnissen als richtig anzusehen ist.

Eine bestimmte Form des Entscheidungsalgorithmus sieht genauso aus wie ein Akzeptanztest beim Recovery-Block-Verfahren: Das Ergebnis der ersten Variante wird überprüft; es wird entweder aktzeptiert oder das Ergebnis der nächsten Variante analysiert etc. Der wesentliche Unterschied zum Recovery-Block besteht darin, daß es keine Probleme mit der Rekonstruktion des ursprünglichen Zustandes gibt, da alle Programme gleichzeitig laufen (dafür handelt man sich andere Probleme ein).

Entscheidung durch Abstimmung

In der Regel besteht der Entscheidungsalgorithmus aber nicht in einem Akzeptanztest, sondern in einer Art Abstimmung. Wenn mindestens zwei Varianten dasselbe Ergebnis ermittelt haben, wird es als richtig angesehen. Es gilt nämlich als sehr unwahrscheinlich, daß von verschiedenen Programmierern erstellte und gut ausgetestete Programme denselben Fehler produzieren.

Der Vorteil gegenüber der Recovery-Block-Technik ist klar: Es wird kein Verfahren benötigt, mit dem ein einzelnes Ergebnis auf Korrektheit überprüft wird. Vielmehr prüfen die Programmvarianten sich untereinander; außerdem ist die Programmierung eines Akzeptanztests.

Natürlich können beim N-Version-Programming dieselben Fehlertypen auftreten wie bei der Recovery-Block-Technik, das heißt, korrekte Ergebnisse werden als falsch zurückgewiesen oder falsche Ergebnisse als richtig akzeptiert. Gerade der erste Fehlertyp kann beim N-Version-Programmung sehr leicht auftreten. Ein Vergleich zwischen verschiedenen Lösungen ist nämlich nur darin möglich, wenn es eine eindeutige Lösung gibt, also zum Beispiel das Gleichungssystem genau eine Lösung besitzt.

Mitunter ganz unterschiedliche Lösungen

Nun sind zwar in kommerziellen Programmen - gerade im Bereich des Rechnungswesens - die Ergebnisse in der Regel so eindeutig, daß sich hier keine besonderen Probleme ergeben. Doch beim oben beschriebenen Problem des Traveller-Salesman sieht es ganz anders aus.

Da es in der Regel wirtschaftlich nicht vertretbar ist, das Optimum für eine vorgegebene Menge von Orten zu ermitteln, können die Programme nur relativ nötige Lösungen ermitteln. Und damit ist es möglich, daß alle Programmvarianten korrekt arbeiten und doch völlig unterschiedliche Lösungen offerieren.

Man könnte in diesem Beispiel natürlich die Route mit der niedrigsten Gesamtlänge auswählen, doch geht diese Lösung schon über eine reine Abstimmung hinaus.

Ein anderes Problem entsteht, wenn das Ergebnis des Programms Fließkommazahlen enthält. Da die Datenverarbeitung intern nur mit einer endlichen Anzahl von Ziffern arbeitet, entstehen bekanntlich Rundungsdifferenzen. Damit besteht auch die Gefahr, daß zwei Programmvarianten zwar korrekt arbeiten, doch die Ergebnisse sich aufgrund verschiedener Rechenalgorithmen und entsprechend unterschiedlicher Rundungen unterscheiden.

Um ein solches Problem zu umgehen, muß festgelegt wir. den, wie groß die Differenz zwischen zwei Ergebnissen sein darf, um beide noch als "identisch" anzusehen. Dieser Punkt ist im kommerziellen

Bereich schon wesentlich wichtiger, da hier immer Rundungsdifferenzen auftauchen, die ausgeglichen werden müssen.

Eine Kombination von beiden Methoden

Einen Versuch, die Vorteile der beiden oben beschriebenen Methoden zu kombinieren, stellt das Verfahren des "Consensus Recovery Block" dar. Dabei wird zunächst wie beim N-Version-Programming verfahren. Erkennt der Abstimmungsalgorithmus eine Entscheidung als richtig an, ist das Programm beendet; kann der Abstimmungsalgorithmus kein Ergebnis akzeptieren, wird jede einzelne Variante von einem Akzeptanztest überprüft.

Beendet ist das Programm, sobald ein Ergebnis den Test er. folgreich passiert hat. Durch diese Kombination kann man die beim N-Version-Programming recht hohe Wahrscheinlichkeit, daß ein richtiges Ergebnis nicht als solches erkannt wird, reduzieren, da spätestens der Akzeptanztest das Ergebnis als richtig erkennt.

Ist die Steuerung, die die verschiedenen Programmvarianten aufruf, mit den einzelnen Varianten zu einem Gesamtsystem verbunden, wird bei einem Laufzeitfehler in einer Variante das Gesamtsystem nicht mehr weiterarbeiten können, da die Steuerung ihrerseits nicht mehr aufgerufen wird. Um auch einen solchen Fall abzufangen, muß die Verbindung der verschiedenen Programmvarianten zur Steuerung über das Betriebssystem aufrechterhalten werden.

Dazu ein Beispiel: Die Programmvarianten versorgen wenn ordnungsgemäß beendet - bestimmte (Betriebs-)Systemvariablen mit einem vordefinierten Wert. Die Steuerung fragt diese Variable permanent ab; sobald dort der erwartete Wert gespeichert ist, weiß sie, daß die betreffende Variante ordnungsgemäß zu Ende gebracht ist.

Sind die Systemvariablen nach einer vorgegebenen Zeit immer noch nicht versorgt, so geht die Steuerung davon aus, ; daß die betreffende Variante abgestürzt ist. Bei der Weiterarbeit werden folglich nur die Ergebnisse der korrekt abgeschlossenen Varianten berücksichtigt.

Ist fehlertolerantes Programmieren wirklich was Neues? Besteht also überhaupt ein wesentlicher Unterschied zwischen herkömmlichen Plausibilitätsprüfungen, wie sie in kritischen Programmen üblich sind, und der "fehlertoleranten Software"? Reicht es nicht aus, wenn in kritischen Programmen der Output noch einmal überprüft wird? Die Antwort auf diese Fragen ist eindeutig: Natürlich haben Plausibilitätsprüfungen teilweise denselben Zweck wie fehlertolerantes Programmdesign, nämlich Softwarefehler zu erkennen, doch geht die Technik der Fehlertoleranz noch einen wesentlichen Schritt weiter. Bei fehlertolerantem Design wird nämlich ein Fehler nicht nur als solcher erkannt, sondern das System reagiert auch darauf, indem es eine weitere Variante aufruft.

Die Fehlertoleranz muß mit sehr hohen Kosten erkauft werden, da Programme in mehreren - normalerweise mindestens drei-Varianten zu programmieren und zu testen sind. Da Software-Entwicklungen ohnedies schon sehr teuer sind, kann fehlertolerantes Design keine Alternative zum gründlichen Testen sein. Ein guter Test ist nämlich wesentlich billiger als n-facher Programmieraufwand mit n-fachem, reduziertem Testaufwand.

Auch liegt die Basis des fehlertoleranten Designs gerade in der Annahme, daß in verschiedenen Programmvarianten jeweils unterschiedliche Fehler sind. Nun gibt es aber typische Programmierfehler, die immer wieder vorkommen und durch systematische Tests recht sicher entdeckt werden können.

Verzichtet man auf den Test, steigt also die Wahrscheinlichkeit, daß in mehreren Programmvarianten ein und derselbe Fehler auftritt. Damit kann aber keines der vorgestellten Verfahren korrekt arbeiten. Vielmehr besteht insbesondere beim N-Version-Programming sogar die Gefahr, daß der doppelt gemachte, identische Fehler vom Abstimmungsalgorithmus als korrektes Ergebnis interpretiert wird.

Beim fehlertoleranten Softwaredesign wird die prinzipielle Fehleranfälligkeit von Software nicht ignoriert, sondern bewußt mitberücksichtigt. In keinem Fall kann allerdings eines der vorgestellten Verfahren ein hundertprozentig richtiges Ergebnis erzielen, da durch den Akzeptanztest oder den Abstimmungsalgorithmus neue Fehlerquellen auftreten. Es wird leidlich die Wahrscheinlichkeit eines Fehlers - entscheidend reduziert.

Nur für sehr kritische Programme

In bezug auf die Kosten ist ein fehlertolerantes Design aufgrund der Mehrfachentwicklung im Vergleich zu herkömmlicher Systementwicklung so aufwendig, daß es sich nur bei wenigen, sehr kritischen Programmen lohnen dürfte. Insbesondere Real-Time-Systeme, bei denen Programmierer oder Operator keine Zeit haben, Fehler zu orten und zu beseitigen, werden ein wichtiges Anwendungsgebiet sein.

Allerdings fällt gerade bei Real-Time-Systemen das Problem ins Gewicht, daß die diversen Programmvarianten naturgemäß mehr Hardwareressourcen beanspruchen als eine einzige Variante. Im Einzelfall muß also überprüft werden, ob der Entscheidungsalgorithmus einschließlich der Varianten, die gegebenenfalls parallel ablaufen müssen - überhaupt noch schnell genug ist für den Echtzeitbetrieb.

Im kommerziellen Bereich wird ein vollständig fehlertolerantes Design, wie es oben beschrieben wurde, selten sinnvoll sein. (Völlig unwirtschaftlich wäre es beispielsweise, ein Buchhaltungssystem insgesamt fehlertolerant zu programmieren.) Doch sind einige Aspekte der Fehlertoleranz auch für die kommerziellen Programme von erheblichem Interesse.

Ein auf die Bedürfnisse kommerzieller Anwendungen abgestimmtes fehlertolerantes Design könnte so aussehen, daß nicht mehrere Varianten desselben Programms entwickelt werden, aber der Output von besonders wichtigen oder komplexen Programmen vor der Weiterverarbeitung durch ein zweites Programm auf Korrektheit oder Plausibilität überprüft wird. Wenn dieses Kontrollprogramm einen Fehler feststellt, gibt es eine Meldung an den Operator oder Anwender und unterbricht die weitere Verarbeitung.

Dabei müßte das Kontrollprogramm natürlich personell völlig getrennt von dein Arbeitsprogramm entwickelt werden. Durch ein solches Design wäre es möglich, mit verhältnismäßig geringen Kosten an wichtigen Systemstellen die Wahrscheinlichkeit eines unentdeckt bleibenden Fehlers deutlich zu reduzieren.

Dr. Christhard Pilder ist bei der KPMG Deutschen Treuhand-Unternehmensberatung im Frankfurt zuständig für Systementwicklung und Software-Engineering.

Standardwerke über CASE

Helmut Balzert, CASE, Systeme und Werkzeuge, BI-Wissenschaftsverlag, Mannheim 1989, 532 Seiten, 78 Mark

QED Information Sciences, CASE, The Potential and the Pitfalls,

QED Information Sciences Inc. Wellesley/Massachusetts 1989, 565 Seiten, 108 Mark Larry E. Towner, CASE, Concepts and Implementation,

IBM-Series, McGraw-Hill, New York 1989, 332 Seiten, 115 Mark

Carma McClure, CASE is Software Automation, Prentice-Hall-International Ltd., London 1989, 290 Seiten, 110 Mark

M. Schindler, Computer Aided Software Design,

Building Quality with CASE, Wiley, London 1989, 394 Seiten, 80 Mark