Hallo Community,
Das Problem
es gibt bestimmte Fehler, die schwer zu finden sind, weil trotz x-maligem Durchschauen und Debuggen(*) - was man natürlich immer zuerst machen sollte - alles richtig aussieht und man aus der Doku oder anderen Quellen weiß, dass es so funktionieren müsste. Wenn einem dann auch noch andere Programmierer bestätigen, dass sie es ausprobiert haben und dass es so gehen sollte, ist es Zeit für folgendes Vorgehen:
Die Lösung
Man erstellt ein neues, minimales Testprojekt(**), das die fragliche Situation nachstellt. Nehmen wir an, auf einem komplexen Form funktioniert der Fokuswechsel zwischen zwei TextBoxen nicht richtig. Dann erstellt man ein Testprojekt, das nur ein Form und die zwei TextBoxen enthält und ansonsten keinen unnötigen Code. Das Testprojekt enthält also soviel wie nötig und so wenig wie möglich, um die Situation nachzustellen.
Wenn das Testprojekt den gleichen Fehler zeigt, wie das echte Projekt, ist es vielleicht noch zu groß, und sollte weiter abgespeckt werden. Wenn man selbst ein minimales Testprojekt nicht zum Funktionieren bekommt, hat der Fehler eine Ursache, die mit der hier beschriebenen Vorgehensweise nicht ermittelt werden kann. Immerhin hat man jetzt den Code so weit reduziert, dass eine Analyse stark vereinfacht ist. Man sollte in diesem Fall auch in Erwägung ziehen, dass der Fehler vielleicht nicht im Code, sondern in den Projekt- oder den Betriebssystemeinstellungen zu suchen ist.
Wenn das Testprojekt korrekt funktioniert - und über solche Fälle reden wir - gilt es, den entscheidenden Unterschied zwischen Testprojekt und echtem Projekt zu finden.
Dazu sollte man in das Testprojekt Schritt für Schritt den Code des echten Projekts einfügen, bis der inhaltliche Fehler auftritt. Natürlich muss man jeden Schritt so wählen, dass sich das Testprojekt syntaxfehlerfrei übersetzen lässt. Es ist wichtig, sich genau zu merken, welchen Schritt man gerade durchgeführt hat, denn der Schritt der den Status von kein Fehler auf Fehler ändert, liefert in aller Regel den entscheidenden Hinweis auf die Fehlerursache. Um auf Nummer sicher zu gehen, dass man den letzten Schritt exakt nachvollziehen kann, sollte man das Testprojekt vor jedem Schritt kopieren.
Alternativen und Varianten
Man kann natürlich auch genau andersherum vorgehen und aus einer Kopie des echten Projekts, sukzessive Code herausschmeißen, bis sich der Status von Fehler auf kein Fehler ändert. Aber auch hier sollte man zuerst ein minimales funktionierenden Testprojekt erstellen, damit man weiß, dass dieser Fall eintreten muss und wird. Natürlich sollte man wieder vor jedem Schritt eine Kopie erstellen.
Statt von null beginnend ein minimales Testprojekt zu bauen, kann man auch eine Kopie des echten Projekts in einem großen Schritt auf das absolut Nötige abspecken. Wenn das abgespeckte Projekt funktioniert, geht man vor wie oben beschrieben. Wenn es noch nicht funktioniert, muss man weiter abspecken. Dieses weitere Abspecken sollte wieder in kleinen Schritten erfolgen, um die Stelle, die den Unterschied macht, genau einzugrenzen.
Wenn die Differenz zwischen minimalem Testprojekt a und echtem Projekt b sehr groß ist, kann es sich anbieten, nicht sofort in kleinen Schritten vorzugehen, sondern eine Art binäre Suche anzuwenden. Dazu erstellt man ein Projekt c, das bezüglich der Code-Menge auf halben Wege zwischen a und b liegt. Tritt hier der Fehler auf, verwendet man c als neues b. Tritt der Fehler nicht auf, verwendet man c als neues a. In beiden Fällen geht man mit den gewählten Projekten in den nächsten Durchlauf und zwar solange, bis die Änderungen so klein geworden sind, dass man die Stelle, die den Unterschied macht, ausreichend genau eingegrenzt hat.
Wenn man am Ende zwei Projekte hat, deren Quellcode vollkommen identisch ist, aber von denen eins funktioniert und das andere nicht, dann liegt der Fehler vermutlich in unterschiedlichen Projekt-, Build- und/oder Compiler-Einstellungen. Oder man hat doch abweichenden Quellcode übersehen, z.B. den in automatisch generierten Quellcodedateien.
Fazit
In jedem Fall geht es darum, durch das schrittweise Annähern von einem Projekt, das den Fehler zeigt, und einem Projekt, das den Fehler nicht zeigt, den entscheidenden Unterschied soweit einzugrenzen, bis man die Fehlerursache gefunden hat.
Ob man das Testprojekt beginnend mit einem leeren Projekt erstellt oder es aus einer Kopie des echten Projekts durch rigoroses Abspecken gewinnt, und ob man - zum schrittweisen Eingrenzen des Fehlers - in das funktioniere Projekt Code einfügt oder aus einer Kopie des nicht funktionierenden Projekts Code entfernt, ist im Wesentlichen Geschmackssache.
Siehe auch
(*) [Artikel] Debugger: Wie verwende ich den von Visual Studio?
(**) Short but complete programs
herbivore
Ich selbst habe diese Methode schon mehrmals und in schwierigen Fällen angewandt.
Auf diesem Weg bin ich bisher in allen Fällen auf die Lösung für mein Problem gestossen.
Je weiter man das Projekt herunterbricht, desto weniger wird man von den unwichtigen Faktoren erschlagen und kann das Problem sehr zügig lokalisieren.
Interessant in dem Zusammenhang ist das Uni-Projekt Delta Debugging, besser erläutert unter An introduction to Delta Debugging
Vllt. wird man ja in einigen Jahren diese automatische Debugging-Methode schon in der IDE (bzw. im Test-Tool) integriert haben?!
Oftmals aber leider nicht immer kann man diese Situation durch Prototyping vermeiden... Neue Funktionen so weit möglich in einer eigenen Testanwendung entwickeln und erst wenn diese in der Testanwendung funktioniert wird dann das Konstrukt in die "scharfe" Anwendung implementiert.
Früher war ich unentschlossen, heute bin ich mir da nicht mehr so sicher...
Häufig erweist sich diese Methode als viel weniger aufwändig, als erwartet: Also man denkt: "Uff uff, komplett neue Anwendung aufbauen" - und dann springt einem der Fehler entgegen, schon beim Versuch, ihn überhaupt zu reproduzieren.
Jedenfalls bei sog. "dummen Fehlern" ist das oft so.
Der frühe Apfel fängt den Wurm.