In diesem Artikel befassen wir uns mit dem Thema Programmfehler, das aufgrund seiner Auswirkungen auf verschiedene Aspekte der Gesellschaft in letzter Zeit an Relevanz gewonnen hat. Programmfehler ist in verschiedenen Bereichen zum Diskussionsthema geworden, hat zu widersprüchlichen Meinungen geführt und großes Interesse an seinem Einfluss auf das tägliche Leben geweckt. In diesem Sinne ist es wichtig, die Auswirkungen von Programmfehler sowie seine Entwicklung im Laufe der Zeit und seine Zukunftsprojektion gründlich zu analysieren. Auf diese Weise möchten wir einen umfassenden Überblick über Programmfehler bieten und relevante Informationen bereitstellen, die es uns ermöglichen, seine Bedeutung im aktuellen Kontext zu verstehen.
Programmfehler oder Softwarefehler oder Software-Anomalie, häufig auch Bug (englisch) genannt, sind Begriffe aus der Softwaretechnik, mit denen für Software-Systemkomponenten Abweichungen zu einem geforderten oder gewünschten Sollzustand bezeichnet werden. Diese können auftreten, wenn z. B. eine bestimmte Festlegung der Spezifikation fehlerhaft ist oder falsch umgesetzt wurde („Fehlhandlung“), und führen zunächst zu einem internen „Fehlerzustand“ im Programm, der wiederum während der Programmausführung zu einem unerwarteten Verhalten oder Ergebnis führt oder führen kann („Fehlerwirkung“).
Zur möglichst vollständigen Erkennung und Behebung von Programmfehlern wird üblicherweise in den Prozessen der Softwareentwicklung, d. h. vor dem tatsächlichen, „produktiven“ Einsatz von Software, die Projektphase „Softwaretest“ durchlaufen, wobei eine Validierung durchgeführt wird. Dabei auftretende Fehler sind üblich und sie zu finden ist Ziel des Testens,[1] während Fehler im laufenden Betrieb je nach Fehlerwirkung u. U. kritische Anomalien/Störungen darstellen. In der Praxis treten Computerprogramme ohne Programmfehler selten auf. Als Qualitätsmerkmal für Programme kennt man u. a. die Fehlerdichte. Sie bezeichnet die Anzahl an Fehlern pro 1.000 Zeilen Code (Kilo Source Lines of Code) bzw. je Function Point.
Als spezielle Instrumente zur Suche nach den Ursachen für Fehler in Programmen sind sogenannte Debugger hilfreich, mit denen ein Programm Schritt für Schritt ausgeführt und kontrolliert werden kann. Bei besonders kritischer Software (z. B. Flugzeugsteuerung) wird mitunter eine (aufwendige) formale Verifikation durchgeführt.
Zur Erfassung und Dokumentation werden sogenannte Bugtracker (wie Bugzilla oder Mantis) eingesetzt. Diese nehmen sowohl Fehlerberichte als auch Verbesserungsvorschläge und Wünsche (sog. Feature-Requests) der Nutzer oder allgemeine Vorgänge auf. Siehe auch Fehlermanagement.
Der Vorgang des Beseitigens eines Programmfehlers wird umgangssprachlich Bugfixing genannt. Das Ergebnis der Verbesserung wird in der Fachsprache als Bugfix, Patch oder Softwarepatch bezeichnet.
Ein Programm- oder Softwarefehler ist, angelehnt an die allgemeine Definition für „Fehler“
Konkret definiert sich der Fehler danach als
Nach ISTQB[3] bildet sich der Begriff 'Fehler' aus den folgenden Zusammenhängen:
Als Synonym für „Fehler“ oder ergänzend dazu sind auch Ausdrücke wie Problem, Defekt, Abweichung, Anomalie, Mangel gebräuchlich. Damit kann die „Fehlerschwere“ auch begrifflich unterschieden werden, z. B. die Verletzung von Vorschriften zum Programmierstil, die Lieferung falscher Ergebnisse oder ein Programmabbruch.
Das Wort bug bedeutet im Englischen „Schnabelkerf; Wanze“ und umgangssprachlich „landlebender Gliederfüßer“ oder „(insektenartiges) Ungeziefer“.[4] Im Jargon amerikanischer Ingenieure ist seit dem späten 19. Jahrhundert die Bedeutung „Fehlfunktion“ oder auch „Konstruktionsfehler“ bezeugt; diesem Wortgebrauch liegt die (scherzhafte) Vorstellung zugrunde, dass sich kleines Krabbelvieh am Getriebe, der Leitung usw. zu schaffen macht. Die ältesten Belege sind zwei Briefe Thomas Edisons aus dem Jahr 1878 an William Orton, den Präsidenten der Telegraphiegesellschaft Western Union, bzw. Tivadar Puskás, den Erfinder der Telefonzentrale, in denen es heißt:
“ I did find a ‘bug’ in my apparatus, but it was not in the telephone proper. It was of the genus ‘callbellum.’[5]”
„ Ich fand in der Tat einen ‚Bug‘ in meinem Apparat, allerdings nicht im Telefon selbst. Er war von der Gattung ‚callbellum‘.“
sowie
“The first step is an intuition, and comes with a burst, then difficulties arise – this thing gives out and then that ‘Bugs’ – as such little faults and difficulties are called – show themselves .”
„Der erste Schritt ist ein intuitiver Gedanke, der in einem Ausbruch kommt, doch dann tauchen Schwierigkeiten auf – das Ding funktioniert nicht mehr und dann, dass ‚Bugs‘ – wie solche kleinen Fehler und Schwierigkeiten genannt werden – sich zeigen .“
Edison ist zwar nicht Erfinder, aber immerhin Kronzeuge für eine schon damals kursierende Bedeutung des Wortes. Die Verknüpfung des Begriffs mit Computern geht möglicherweise auf die Computerpionierin Grace Hopper zurück. Sie verbreitete die Geschichte, dass am 9. September 1945 eine Motte in einem Relais des Computers Mark II Aiken Relay Calculator zu einer Fehlfunktion führte. Die Motte wurde entfernt, in das Logbuch geklebt und mit folgender Notiz versehen: “First actual case of bug being found.” (deutsch: „Das erste Mal, dass tatsächlich ein ‚Ungeziefer‘ gefunden wurde.“). Die Legende der Begriffsfindung hält sich hartnäckig, obwohl die Logbuch-Eintragung gerade darauf verweist, dass der Begriff schon zuvor gängig war. Zudem irrte Grace Hopper sich hinsichtlich des Jahres: Der Vorfall ereignete sich tatsächlich am 9. September 1947. Die entsprechende Seite des Logbuchs wurde bis Anfang der 1990er Jahre am Naval Surface Warfare Center Computer Museum der US-Marine in Dahlgren, Virginia, aufbewahrt. Zurzeit befindet sich diese Logbuchseite mit der Motte am Smithsonian Institute.[7]
In der Softwaretechnik (siehe auch[8]) wird zwischen folgenden Typen von Fehlern in Programmen unterschieden:
Lexikalische und Syntaxfehler verhindern in der Regel die Kompilierung des fehlerhaften Programms und werden daher frühzeitig erkannt. Bei Programmiersprachen, die sequentiell interpretiert werden, bricht das Programm üblicherweise erst an der syntaktisch/lexikalisch fehlerhaften Stelle ab.
Sonstige Fehlerbegriffe
Bei manchen Projekten wird nicht der Begriff Bug verwendet, sondern man spricht zum Beispiel von Metabugs, bei denen ein Bug ein Element einer Aufgabenliste darstellt. Bei einigen Projekten spricht man stattdessen auch von „Issues“ (Angelegenheiten), da sich dieser Ausdruck nicht auf Programmfehler beschränkt.
Konkrete Beispiele von Fehlern mit medial besonderer Wirkung finden sich in der Liste von Programmfehlerbeispielen.
Softwarefehler sind weit mehr als nur ärgerliche Begleitumstände für Softwareentwickler, sondern sie verursachen aus betriebswirtschaftlicher und ökonomischer Sicht erhebliche Kosten. Die IX-Studie 1/2006[9] zeigte z. B. folgende für Deutschland ermittelte Werte:
In derselben Studie wird auch die Entwicklung der Softwarequalität für die Zeit von 2002 bis 2004 untersucht – mit dem Ergebnis:
Für die USA werden die Kosten von Programmierfehlern 2022 auf 2,41 Billionen Dollar geschätzt, davon 1,81 Billionen für Fehler im Betrieb, 607 Milliarden für die Fehlersuche und -behebung und 260 Milliarden für fehlerbedingte Projektabbrüche.[10]
Besonders viele Misserfolge zeigt ein Bericht des obersten Rechnungshofs für neue Projekte (1985) bei der US-Bundesverwaltung,[11] wonach
Die Standish Group International stellte fest:[12] Durchschnittlich überschreiten Projekte
Als Gründe für Projektabbrüche aufgrund schlechter Softwarequalität ermittelte Ewusi-Menach folgende Faktoren:[11]
Die Kosten für Debugging und Beseitigung bei in späteren Projektphasen bzw. in Produktion gefundenen Fehlern übersteigen üblicherweise die Kosten für das Verhindern und Finden von Fehlern.[13] Studien belegen daher, dass es oft günstiger ist, qualitativ hochwertige Software zu entwickeln, als qualitativ minderwertige.[14]
Generell gilt:[15] Je früher im Entwicklungsprozess der Fehler auftritt und je später er entdeckt wird, umso aufwändiger wird es, den Fehler zu beheben. Im Schnitt rechnet man mit Kosten von 16.000 Dollar je in Produktion gefundenem Bug, während die Kosten für das Verhindern von Fehlern während der Designphase bei 25 Dollar liegen.[16]
Am wichtigsten ist eine gute und geeignete Planung des Entwicklungsprozesses. Hierfür gibt es bereits etliche Vorgehensmodelle, aus denen ein geeignetes ausgewählt werden kann.
Ein Problem ist, dass die Korrektheit eines Programms nur gegen eine entsprechend formalisierte Spezifikation bewiesen werden kann. Eine solche Spezifikation zu erstellen kann jedoch im Einzelfall ähnlich kompliziert und fehlerträchtig sein, wie die Programmierung des Programms selbst.
Softwareexperten sind sich darüber einig, dass praktisch jedes nicht-triviale Programm Fehler enthält. Deshalb wurden Techniken entwickelt, mit Fehlern innerhalb von Programmen tolerant umzugehen. Zu diesen Techniken gehören defensives Programmieren, Ausnahmebehandlung, Redundanz und die Überwachung von Programmen (z. B. durch Watchdog-Timer) sowie die Plausibilisierung des Programmes während der Entwicklung und der Daten während des Programmablaufs.
Die Entwicklung immer abstrakterer Programmierparadigmen und Programmierstile wie die funktionale Programmierung, objektorientierte Programmierung, Design by contract und die aspektorientierte Programmierung dienen unter anderem der Fehlervermeidung und Vereinfachung der Fehlersuche. Aus den zur Verfügung stehenden Techniken für das Problem ist eine geeignete auszuwählen. Ein wichtiger Punkt hierbei ist aber auch, dass für das jeweilige Paradigma erfahrene Programmierer zur Verfügung stehen müssen, sonst entsteht oft der gegenteilige Effekt.
Ferner ist es sehr nützlich, von den Entwicklungswerkzeugen möglichst viele Aufgaben der Fehlervermeidung zuverlässig und automatisch erledigen zu lassen, was z. B. mit Hilfe von strukturierter Programmierung erleichtert wird. Dies betrifft zum einen bereits Kontrollen wie Sichtbarkeitsregeln und Typsicherheit, sowie die Vermeidung von Zirkelbezügen, die bereits vor der Übersetzung von Programmen vom Compiler übernommen werden können, aber auch Kontrollen, die erst zur Laufzeit durchgeführt werden können, wie zum Beispiel Indexprüfung bei Datenfeldern oder Typprüfung bei Objekten der objektorientierten Programmierung.
Darüber hinaus wird eine Reihe fortgeschrittener Anwendungen angeboten, die entweder den Quellcode oder den Binärcode analysieren und versuchen, häufig gemachte Fehler automatisiert zu finden. In diese Kategorie fallen etwa Programme zur Ausführungsüberwachung, die üblicherweise fehlerhafte Speicherzugriffe und Speicherlecks zuverlässig aufspüren. Beispiele sind das frei erhältliche Tool Valgrind und das kommerzielle Purify. Eine weitere Kategorie von Prüfprogrammen umfasst Anwendungen, die Quell- oder Binärcode statisch analysieren und etwa nicht geschlossene Ressourcen und andere Probleme auffinden und melden können. Darunter fallen etwa FindBugs, Lint und Splint.
Es ist durchaus sinnvoll, dass der Test vor dem eigentlichen Programm entwickelt wird. Damit wird erreicht, dass nicht ein Test geschrieben wird, der zu dem bereits geschriebenen Programm passt. Dies kann durch Ermittlung von Testfällen anhand der Spezifikation bereits während der Analyse- bzw. Designphase erfolgen. Die Ermittlung von Testfällen in diesem frühen Stadium der Softwareentwicklung ermöglicht zudem die Prüfung der Anforderungen an das Programm auf Testbarkeit und Vollständigkeit. Die anhand der Spezifikation ermittelten Testfälle sind die Basis für die Abnahmetests – die kontinuierlich über den gesamten Entwicklungsprozess verfeinert und z. B. für eine Testautomatisierung vorbereitet werden können.
Manche Softwareanbieter führen Testphasen teilweise öffentlich durch und geben Betaversionen heraus, um die unvorhersehbar vielfältigen Nutzungsbedingungen verschiedener Anwender durch diese selbst testen und kommentieren zu lassen.
Tritt ein Fehler während des Betriebs auf, so muss versucht werden, seine Auswirkungen möglichst gering zu halten und seinen Wirkungskreis durch Schaffung von „Schutzwällen“ oder „Sicherungen“ einzudämmen. Dies erfordert zum einen Möglichkeiten der Fehlererkennung und zum anderen, adäquat auf einen Fehler reagieren zu können.
Ein Beispiel zur Fehlererkennung zur Laufzeit eines Computerprogrammes sind Assertions, mit deren Hilfe Bedingungen abgefragt werden, die gemäß Programmdesign immer erfüllt sind. Weitere Mechanismen sind Ausnahmebehandlungen wie Trap und Exception.
Durch die Implementierung von Proof-Carrying Code kann die Software zur Laufzeit ihre Zuverlässigkeit in gewissem Rahmen gewährleisten und sicherstellen.
Völlige Fehlerfreiheit für Software, die eine gewisse Komplexitätsgrenze überschreitet, ist praktisch weder erreich- noch nachweisbar. Mit steigender Komplexität sinkt die Überblickbarkeit, insbesondere auch, wenn mehrere Personen an der Programmierung beteiligt sind. Selbst teure oder vielfach getestete Software enthält Programmierfehler. Man spricht dann bei gut brauchbaren Programmen nicht von Fehlerfreiheit, sondern von Robustheit. Eine Software gilt dann als robust, wenn Fehler nur sehr selten auftreten und diese dann nur kleinere Unannehmlichkeiten mit sich bringen und keine größeren Schäden oder Verluste verursachen.
In Spezialfällen ist ein Beweis der Fehlerfreiheit (bzgl. der festgelegten Anforderungen) eines Programms möglich. Insbesondere in Bereichen, in denen der Einsatz von Software mit hohen finanziellen, wirtschaftlichen oder menschlichen Risiken verbunden ist, wie z. B. bei militärisch oder medizinisch genutzter Software oder in der Luft- und Raumfahrt, verwendet man zudem eine „(formale) Verifizierung“ genannte Methode, bei der die Korrektheit einer Software formal-mathematisch nachgewiesen wird. Dieser Methode sind allerdings wegen des enormen Aufwands enge Grenzen gesetzt und sie ist daher bei komplexen Programmen praktisch unmöglich durchzuführen (siehe auch Berechenbarkeit). Allerdings gibt es mittlerweile Werkzeuge, die diesen Nachweis laut eigenen Angaben zumindest für Teilbereiche (Laufzeitfehler) schnell und zuverlässig erbringen können.
Neben der mathematischen Verifizierung gibt es noch eine praxistaugliche Form der Verifizierung, die durch die Qualitätsmanagement-Norm ISO 9000 beschrieben wird. Bei ihr wird formal nur dann ein Fehler konstatiert, wenn eine Anforderung nicht erfüllt ist. Umgekehrt kann demnach ein Arbeitsergebnis (und damit auch Software) als ‚fehlerfrei‘ bezeichnet werden, wenn es nachweisbar alle Anforderungen erfüllt. Die Erfüllung einer Anforderung wird dabei durch Tests festgestellt. Bringen alle zu einer Anforderung definierten Tests die erwarteten Ergebnisse, so ist die Anforderung erfüllt. Gilt dies für die Tests aller Anforderungen (korrektes und vollständiges Testen vorausgesetzt), so wird „fehlerfrei bzgl. der Anforderungen“ gefolgert. Sind die den Tests zugrundeliegenden Anforderungen fehlerhaft oder unvollständig, so arbeitet die Software dementsprechend dennoch nicht „wie gewünscht“.
Aufgetretene Fehler werden im Allgemeinen im Fehlermanagement systematisch bearbeitet. Nach der IEEE-Norm 1044 (Klassifizierung von Softwareanomalien) durchläuft dabei jeder Fehler einen sogenannten Klassifizierungsprozess, bestehend aus den vier Schritten Erkennung (Recognition), Analyse (Investigation), Bearbeitung (Action) und Abschluss (Disposition).[17] In jedem dieser Schritte werden die Verwaltungsaktivitäten Aufzeichnen (Recording), Klassifizieren (Classifying), Wirkung identifizieren (Identifying Impact) ausgeführt.[18]
Kriterien, nach denen Fehler dabei klassifiziert werden können, sind u. a. (mit Beispielen):
Mit Hilfe von Metriken „sollten die Ergebnisse auch Anlass zur Suche nach den Ursachen hinter den Problemen sein“.[1] „Fehlerklassifikationen bilden die Grundlage für standardisierte Verfahren zur Fehlerbehandlung und unterstützen zudem eine kontinuierliche Qualitätsverbesserung im Sinne des Qualitätsmanagements.“[19] Weitere Angaben je Fehler wie eine ausführliche Fehlerbeschreibung, betroffene Programme, beteiligte Personen etc. begleiten die Maßnahmen zur Behebung der Fehler und dokumentieren diese. Näheres siehe BITKOM-Leitfaden.[19]
Vereinfachend werden Programmfehler im Fehlerbearbeitungsprozess häufig nur nach der Fehlerschwere, das schließt außerdem die Fehlerwirkung und den Behebungsaufwand ein, in Kategorien/Klassen wie A, B, C, … oder 1, 2, 3, … usw. eingeteilt. Beispiele siehe BITKOM-Leitfaden,[19] insbesondere im Anhang.
Die Folgen von Programmfehlern können hochgradig unterschiedlich sein und sich in vielfältiger Weise zeigen. Werden Fehler im Rahmen der Entwicklungsprozesse entdeckt, so beschränken sich die Fehlerfolgen außerdem auf die Überarbeitung der Software (Codekorrekturen, Konzeptüberarbeitung, Dokumentation …) – je nach Situation mit mehr oder weniger großen Auswirkung auf das Projektbudget und die Projektdauer. Dagegen wirken erst im Produktivbetrieb erkannte Fehler nicht selten ungleich kritischer, zum Beispiel können sie Prozess-Störungen oder Produktionsstillstand bewirken, Imageschäden hervorrufen, den Verlust von Kunden und Märkten verursachen, Regresspflichten auslösen oder gar das Unternehmen in Existenzgefahr bringen. Fehler in technischen Anwendungen können im schlimmsten Fall zu Katastrophen führen.
Konkrete Beispiele für Programmfehler und deren Folgen finden sich in der Liste von Programmfehlerbeispielen.
Manche Programmfehler sind nur äußerst schwer oder gar nicht zuverlässig reproduzierbar. Bei der Wiederholung eines zuvor gescheiterten Vorgangs unter scheinbar unveränderten Bedingungen ist die Wahrscheinlichkeit gegeben, dass sich diese Fehler nicht erneut äußern. Es gibt zwei mögliche Gründe für dieses Verhalten: Zum einen kann es zu Verzögerungen zwischen der Fehleraktivierung und dem letztlich auftretenden Problem beispielsweise einem Programmabsturz kommen, welche die tatsächliche Ursache verschleiern und deren Identifikation erschweren. Zum anderen können andere Elemente des Softwaresystems (Hardware, Betriebssystem, andere Programme) das Verhalten der Fehler in dem betrachteten Programm beeinflussen. Ein Beispiel hierfür sind Fehler, die in nebenläufigen Umgebungen mit mangelnder Synchronisation (genauer: Sequentialisierung) auftreten. Wegen der hieraus folgenden Wettlaufsituationen (Race Conditions) können die Prozesse in einer Reihenfolge abgearbeitet werden, welche zu einem Laufzeitfehler führt. Bei einer Wiederholung der gleichen Aktion ist es möglich, dass die Reihenfolge der Prozesse unterschiedlich ist und kein Problem auftritt.
Die Reproduzierbarkeit eines Programmfehlers lässt sich anhand dessen Beobachtungsgüte in Kategorien/Klassen wie A, B, C ... oder 1, 2, 3 ... usw. einteilen. Die Beobachtungsstufe eines Fehler kann sich über die Nutzungszeit der Software verändern, so kann sich bspw. ein nicht reproduzierbarer Fehler zu einem eindeutig reproduzierbaren Fehler entwickeln. Bei nicht ohne weiteres reproduzierbaren Fehlern, welche aber wiederholt aufgetreten sind, können Experten zur Ursachensuche hinzugezogen werden.