Was tust Du, wenn Du die Fehlerursache in Deinem Programm nicht finden kannst?
Ich mache pünktlich Feierabend und fahre nach Hause. Sich übermäßig lange mit der Fehlersuche zu quälen, bringt nichts. Oft hatte ich die rettende Idee schon unterwegs in der U-Bahn oder irgendwann in der Nacht:
„Den Seinen gibt´s der Herr im Schlaf“
Und dann gibt es noch systematische Lösungsansätze:
maschinelles Vergleichen mit früheren Softwareversionen. Oft hilft ein genauerer Blick auf die Unterschiede im Programmtext.
in manchen Fällen kann man das Programm auf einen zu untersuchenden Aspekt reduzieren, um die Fehlersuche zu erleichtern.
Wenn das nicht hilft, muss man halt findig sein.
Am besten ist es, wenn man versucht, fehlerfrei zu programmieren. Dies setzt voraus, dass man sein Programm gedanklich beherrscht. Wenn das nicht mehr der Fall ist, man sich also verrannt hat, hat man ohnehin verloren.
Also, so ganz zufrieden bin ich mit dieser lapidaren Antwort nicht. Bin im Moment auch schon etwas müde. Da hab ich mal gesucht, was es zum Thema Testen/Fehlersuche/Programmieren von mir auf Quora schon gibt. Und bin fündig geworden:
Josef Müller
· 1 J
Was ist, wenn Entwickler ihre Zeit nicht mit Testen verbringen möchten?
Da kann ich ja aus meinen beruflichen Erfahrungen berichten. Zunächst ist der Entwickler gut beraten, von Haus aus in Entwurf und Realisierung eine hohe Qualität anzustreben. Ein nachträgliches "Hineintesten" von Qualität ist nämlich nicht möglich. Es gibt da diesen berühmten Spruch von Edsger W. Dijkstra: “Program testing can be used to show the presence of bugs, but never to show their absence!” Eine Zeitlang hatte ich mich für formale Korrektheitsbeweise interessiert. Baber, Robert: Fehlerfreie Programmierung für den Software-Zauberlehrling Für mich war das teilweise sehr schwere Kost, aber für die Programmierpraxis konnte ich doch einiges lernen: Den dynamischen Ablauf eines Programms gedanklich zu durchdringen, ist nur teilweise hilfreich (der sogenannte Schreibtischtest). Zielführender ist es, in die statische Programmstruktur logische Aussagen einzubetten (gedanklich oder in Kommentaren) und anhand derer sich von der Korrektheit zu überzeugen. Auch hatte ich Standard-Konstrukte, über deren Korrektheit ich einmal gründlich nachgedacht hatte und die ich dann in identischer Form immer wieder einsetzen konnte: Eine einheitliche Schablone zur Programmierung eines Fifo-Speichers, der auch in Verbindung mit Interrupt-Routinen fehlerfrei arbeitet. Für das Warten, bis eine logische Bedingung erfüllt ist, eine einheitliche Struktur für Sender- und Empfängerseite (zum Vergrößern bitte auf das Bild klicken): Anmerkung zu den obigen Code-Fragmenten: da verbirgt sich keine Race-Condition. Das funktioniert deswegen, weil die Routinen für das Event-Flag geeignet programmiert sind: void RTX_send_signal(uint32_t tnr) { if (tnr < NR_OF_TASKS) { signal_merker[tnr] = true; task_is_ready[tnr] = true; task_was_activated = true; } } void RTX_wait_for_signal() { rtx_os_switch(&saved_SPs[nr_der_aktiven_task]); __disable_interrupt(); while (signal_merker[nr_der_aktiven_task] == false) { task_is_ready[nr_der_aktiven_task] = false; __enable_interrupt(); rtx_os_switch(&saved_SPs[nr_der_aktiven_task]); __disable_interrupt(); } signal_merker[nr_der_aktiven_task] = false; __enable_interrupt(); } Um Systeme übersichtlich strukturieren zu können, fand ich endliche Zustandsautomaten sowie Multi-Tasking sehr nützlich. Nebenbei kam dies auch der Effizienz der Programme zugute. Eine Zeitlang hatte ich im Projekt einen freiberuflichen Mitarbeiter, der manchmal recht sorglos programmierte und sich dann darauf verließ, dass die Kollegen beim Testen die Fehler finden würden. Der Projektleiter wäre fast verzweifelt, weil die Fehler nie zuverlässig behoben wurden, sondern immer wieder neu auftauchten. Er verlor ziemlich das Vertrauen in die Software-Entwicklung und ich musste das dann büßen. Ein Beispiel: Beim Einlesen der Daten eines Temperatur-Sensors war bei negativen Temperaturen die Umrechnungsroutine fehlerhaft und löste eine Exception aus. Dieser Fehler ist erst im Winter vor Ort an der Anlage aufgefallen. Den Fehler zu beheben, war eine Kleinigkeit. Aber der Projektleiter zwang mich, dafür einen Prüfpunkt in den SW-Testplan einzubauen. Die Folge: dies kostete viel Zeit, Unmengen an Kältespray und es hat einige Elektronikplatinen ziemlich ruiniert. Ich selber machte natürlich auch Fehler. Eher bei den einfachsten Dingen Fehler der dämlichen Sorte. Schwierige Dinge (z.B. ein kleiner Multi-Task-Kernel) waren von Anfang an fehlerfrei. Also habe auch ich getestet, sogar relativ viel. Aber vorwiegend ad hoc: Was ist das? Es sind Tests während der laufenden Entwicklung, die so nebenbei durchgeführt werden. Kaum geplant und schon gar nicht dokumentiert. Mehr so aus dem Bauch heraus. Man ist im Flow und hat bestimmte Teile der Software im gedanklichen Fokus. Und man testet genau daraufhin. Es geht schnell und man behält die Konzentration - man bleibt im Flow. Man lernt die Software kennen und manche Sachen findet man durch Zufall. Fehler und Schwächen im Entwurf erkennt man frühzeitig und Verbesserungen sind einfacher und mit weniger Aufwand möglich. Das Management ist aber damit höchst unzufrieden. Dem Entwickler traut man sowieso nicht (ich habe meistens allein gearbeitet) und man kann kein Papier vorweisen. Also verlangt man geplante und dokumentierte Tests: Von Vorteil ist es, dass man die Durchführung der Tests dokumentiert hat und dass man Tests reproduzierbar wiederholen kann. Das war es aber auch schon mit den Vorteilen und die Nachteile sind massiv: * für den Entwickler ist es eine Katastrophe, wenn er dies parallel zur laufenden Arbeit durchführen soll. Er fällt aus dem Flow, die gedanklichen Zusammenhänge gehen verloren (man ist ja noch permanent am Planen und Entwerfen). In einer separaten Phase nach dem Abschluss der Entwicklung wäre es machbar. * ein separates Test-Team stört die SW-Entwicklung weniger. Aber ich könnte mir vorstellen, dass es zu lästigen Diskussionen kommt. Ich hatte diese Situation ja, als ich Software für Messgeräte der Kernstrahlungsmesstechnik entwickelt habe. Aber der TÜV hat erst dann die Software geprüft, als sie von mir fertig entwickelt war. Und er hat auch nie einen echten Softwarefehler gefunden (es ging eher um Formalismen und Aspekte der "Schönheit"). * ich habe die Erfahrung gemacht, dass die Erstellung eines Testplans sowie die Durchführung der Tests zeitlich enorm aufwendig ist. Ganz grob geschätzt, sind ad hoc-Tests um einen Faktor 10 … 100 effektiver. Und teilweise auch besser, da sie vom Charakter her eher einem "white box"-Test gleich kommen. Das Argument für Testpläne und damit für die Reproduzierbarkeit von Tests ist ja, dass man bei Erweiterungen der Software oder bei Fehlerbehebungen sich vergewissern möchte, dass keine neuen Fehler eingeschleust wurden. Dem kann ich nur teilweise zustimmen. Eigentlich leistet man damit einen Offenbarungseid: man gibt zu, dass man die Kontrolle verloren hat und sich hinsichtlich der Konsequenzen einer Änderung unsicher ist …
Und wo ich so am Schreiben bin, fällt mir noch eine interessante Anekdote ein. Es kann nämlich auch vorkommen, dass man nur mit sehr viel Glück einen Fehler entdeckt, aber Diagnose und Behebung des Fehlers einfach sind:
Eines Tages wurde ich in den Testraum gerufen, weil der Rechner (über den man mittels ISDN telefonieren konnte) abgestürzt war und den berüchtigten blauen Bildschirm zeigte. Da fiel mir auf, dass der Hörer nicht eingehängt war. Über den Task-Manager fand ich heraus, dass während eines Telefonats sukzessive Windows-Handles verbraucht wurden: wenn nach dem Start des Rechners in der Summe eine Telefoniezeit von 6 Stunden aufgelaufen ist, kommt es zum Absturz. Des Rätsels Lösung:
Im Verbindungsstück zwischen Sound-Karte und ISDN-Controller hat die Software fortlaufend Handles angefordert, sie aber nicht ordnungsgemäß zurückgegeben. Dieser Fehler war in einer Applikation des Herstellers der ISDN-Karte enthalten und ich hatte ihn in meine Software übernommen. Hätte ich die entsprechende Windows-Dokumentation gründlich gelesen, wäre mir das nicht passiert.
Das Ganze hat ziemliches Aufsehen erregt, weil in einer Reihe von Notruf-Zentralen, die über ganz Deutschland verteilt sind, vor Ort die Software getauscht werden musste. Von meinem Chef kam aber kein einziges Wort eines Vorwurfs.
Und da fällt mir noch ein Beispiel ein, wie tückisch es sein kann, wenn man blindlings probiert:
Ich hatte das Problem, dass nach einem Update der Software im laufenden Betrieb der I2C-Bus seine Funktion einstellte. Als Ursache sah ich ein Problem mit dem I2C-Bus-Controller des Prozessors. Weil der in der Programmierung schon sehr unfreundlich zu handhaben war. Während des Flashens hatte ich den Controller mehrmals gestoppt und neu gestartet. Also ließ ich ihn bis zum Neustart nach dem Flashen durchlaufen und dann funktionierte es - so glaubte ich.
Erst als ich schon im Ruhestand war, dämmerte mir, dass der Fehler immer noch bestand und ich lediglich die Wahrscheinlichkeit für das Auftreten verringert hatte. Weil der I2C-Bus die tückische Eigenschaft hat, dass bei einem abgebrochenen Lesevorgang des Bus-Masters der I2C-Bus blockieren kann. Was man auflösen muss, indem man den Sender des Slaves leer taktet.
In einem früheren Projekt hatte ich das alles schon mal richtig abgehandelt - weil ich dazu eine Applikationsschrift gelesen hatte. Aber ich hatte dies komplett vergessen und im Handbuch zum Mikrocontroller oder in den Datenblättern der I2C-Bus-Bausteine gab es nicht mehr die geringsten Hinweise auf diese Problematik.
In einigen E-Mails habe ich meinem Nachfolger meine Überlegungen dazu mitgeteilt. Ich hoffe, dass er es richtig hingekriegt hat.