Fehleranalyse und Fehlerbehebung kann manchmal sehr anstrengend und frustrierend sein. Trotzdem gehört es auch zum Alltag eines Software-Entwicklers, Bugs effizient und effektiv aufzuspüren und zu beheben – sei es in der Testphase oder im laufenden Betrieb der Software. Doch wie geht man beim Bugfixing am besten vor? Dieses How-To soll helfen, Fehler im Code zu ermitteln, zu beseitigen und langfristig vielleicht sogar zu vermeiden.
Schritt 1: Indizien sammeln
Bevor man mit der Analyse beginnt, sollten alle verfügbaren Informationen gesammelt werden. Wenn bekannt ist, dass Personen den Fehler selbst direkt beobachtet haben, befragt man am besten diese zuerst. Mit den Antworten können möglicherweise schon weitere potenzielle Fehlerquellen eingegrenzt oder ausgeschlossen werden. Besonders zentral ist der Systemzustand zum Zeitpunkt des Fehlers. Allenfalls lässt sich auch auf ein Backup der Systemkonfiguration zurückgreifen. Wertvolle Quellen für Indizien sind natürlich immer Logdaten, Audit-Trails und andere Aufzeichnungen zum Zeitpunkt des Fehlers.
Typische Fragen in diesem Schritt
- Ist der Fehler nur einmal oder mehrmals aufgetreten?
- Was haben die Anwender zum Zeitpunkt des Fehlers oder davor getan?
- Konnte die beobachtende Person bereits einschränken, wann der Fehler passiert?
- Welche Software-Versionen waren im Einsatz, welches Betriebssystem, welche Bibliotheken, welche Hardware?
- Wie war das System konfiguriert?
Je mehr solcher Informationen im Vorfeld gesammelt werden können, desto besser – auch wenn es gut sein kann, dass diese trotz aller Bemühungen unvollständig bleiben. Es kann sein, dass gewisse Informationen gar nicht oder nur sehr schwer ermittelt werden können, weil man sich z. B. in einem besonders geschützten Umfeld bewegt. Vielleicht sind die Informationen aber auch durch irgendwelche Effekte oder gar durch den Fehler selbst verloren gegangen. Am Ende muss man sich mit dem zufrieden geben, was verfügbar ist.
Schritt 2: Theorie aufstellen
Bewaffnet mit den gesammelten Indizien lassen sich nun Theorien entwickeln, die erklären, warum und wie genau der Fehler aufgetreten ist. Anfangs werden diese Theorien noch sehr allgemein sein.
Mögliche Formulierungen für Theorien
- Es passiert vermutlich in der Komponente XY.
- Es klingt nach einer Race-condition in Ablauf B.
- Es scheint nur aufzutreten, wenn Einstellung E aktiv ist bzw. wenn Bedingung C zutrifft.
- Komponente D ist es sehr wahrscheinlich nicht, der entsprechende Log-Eintrag fehlt.
Später lassen sich diese Theorien immer weiter verfeinern. Gerade erfahrene Softwareentwickler sind hier im Vorteil, da sie schon viele Fehlerbilder, Systemzusammenhänge und mögliche Ursachen kennen.
Schritt 3: Fehler nachstellen
Mit einer Theorie in der Hand gehen nun gerade unerfahrene Entwickler gerne sofort dazu über, den Fehler irgendwie zu beheben. Man «weiss» ja jetzt, wo der Fehler passiert ist, also schaut man sich dort im Code einfach mal jene Passagen an, die verdächtig aussehen und korrigiert diese. Aber erst wenn man den Fehler nachstellen kann, hat man ein Mittel in der Hand, um die Auswirkungen der Änderungen im Code zu beobachten. Für das Nachstellen helfen z. B. Testumgebungen, Simulationen und Unit- oder Subsystem-Tests. Gerade mit Letzteren können Entwickler den Fehler isoliert beobachten und mit weiteren Werkzeugen wie Debuggern oder Profilern analysieren. In der Realität muss man sich aber gelegentlich damit zufriedengeben, dass ein Fehler nur mit einer bestimmten Häufigkeit oder nur auf gewissen Geräten nachgestellt werden kann («Works on my machine»). Aber auch dies kann wieder wertvolle Hinweise liefern.
Typische Fragen in diesem Schritt
- Lässt sich der Fehler mit den gesammelten Indizien nachstellen?
- Taucht der Fehler mit gewissen Variationen des Ablaufs häufiger oder seltener auf?
- Kann der Ablauf, der zum Fehler führt, eventuell sogar verkürzt werden, indem man bestimmte Schritte weglässt?
- Was ist auf dem Gerät, das den Fehler zeigt, anders als auf dem Entwickler-Rechner?
- Hat eventuell die Umgebungstemperatur einen Einfluss und verändert z. B. ein Kühlen des Prozessors die Fehlerhäufigkeit?
Antworten auf solche Fragen fliessen auch in den nächsten Schritt ein.
Schritt 4: Theorie verfeinern
Nun geht es daran, weitere Details zur aufgestellten Theorie zu finden. Wird etwa die Komponente XY als Fehlerquelle vermutet, kann untersucht werden, ob sich diese Theorie auf eine Subkomponente von XY einschränken lässt. Auf diese Weise stossen Entwickler langsam von Bibliotheken über Klassen und Methoden eventuell bis zu einzelnen Codezeilen vor.
Andererseits ist es auch möglich, dass der Fehler nur in einer ganz speziellen Kombination verschiedener Komponenten auftritt. Dann braucht es eigene Theorien pro Komponente und vielleicht sogar mehrere Korrekturen. Wie bei wissenschaftlichen Theorien kann man auch beim Bugfixing immerzu versuchen, diese zu falsifizieren.
Typische Annahmen in diesem Schritt
- Wenn jetzt tatsächlich YZ den Fehler verursacht, dann sollte der Fehler nicht auftreten, wenn wir stattdessen AB machen.
Solche Thesen helfen, den Fehler besser zu verstehen und eine Lösung zu finden, welche die tatsächliche Ursache des Fehlers behebt.
Schritt 5: Fehler beheben und mögliche Massnahmen für zukünftige Vermeidung umsetzen
Solange man sich noch nicht sicher ist, dass man die tatsächliche Ursache gefunden hat, werden die Schritte 2 bis 4 wiederholt. Irgendwann hat man eine Theorie aufgestellt, die mit genügender Sicherheit den Fehler erklärt, sodass dessen Ursache behoben werden kann. Danach wird auch hier der unerfahrene Entwickler seine Arbeit möglicherweise bereits als getan ansehen. Dies ist aber die Gelegenheit, für die Zukunft zu lernen oder sogar vorzusorgen. Hier kann und sollte man sich Gedanken machen, warum dieser Fehler überhaupt aufgetreten ist und ob sich nicht bereits jetzt Massnahmen umsetzen lassen, um ähnliche Fehler in Zukunft zu vermeiden.
Typische Fragen in diesem Schritt
- Wieso wurde der Fehler nicht durch einen Unit- oder Systemtest aufgedeckt?
- Wieso wurde der Fehler nicht im Testing festgestellt?
- Haben wir in unserem System ähnliche Situationen, welche diesen Fehler verursachen könnten, auch an anderer Stelle?
- Könnten wir mit bestimmten Massnahmen die Analyse solcher Fehler in Zukunft vereinfachen?
- Könnten die Auswirkungen solcher Fehler reduziert werden?
In verschiedenen Industrien ist dieses Vorgehen sogar formalisiert und vorgeschrieben. So kennt man im Bereich Medtech beispielsweise den Begriff und den dazugehörigen Prozess CAPA (Corrective- and preventive actions). Zudem veröffentlicht das National Transportation Safety Board unter dem Begriff Safety Recommendation Empfehlungen zur zukünftigen Vermeidung von Unfällen.
Fazit
Ja, Bugfixing kann sehr anstrengend und frustrierend sein. Mit einem strukturierten Vorgehen und dem richtigen Mindset kann man sich den Fehlern aber wie in einer guten Detektivgeschichte nähern. Die hier beschriebenen Schritte sind sehr allgemein gehalten, die Fragen müssen natürlich auf die eigenen Technologien und Werkzeuge sowie auf das eigene Umfeld angepasst werden. Ist dieser Schritt aber einmal gemacht, lassen sich in Zukunft eigene «Kriminalfälle» mit mehr Spass und Erfolg lösen.
Der Autor
Silvan Wegmann
Silvan Wegmann war als Senior Embedded Software Engineer bei bbv tätig. Mit langjähriger Erfahrung in den Bereichen Medtech, Industrie und Telekommunikation ist er besonders an modernen Architekturen und vernetzten Maschinen interessiert. Wegmann ist Co-Organisator der GNU Embedded Linux Meetups in Zürich.