Bugs sind kleine, gemeine

Aber wie verwendet man den Debugger, um einen Fehler aufzuspüren?
In den meisten Fällen macht sich ein Programmfehler dadurch bemerkbar, dass das Programm nicht tut, was es tun soll oder dadurch, dass eine Ausnahme (Exception) ausgelöst wird. In letzterem Fall hat man zwar die Stelle lokalisiert, an der der Fehler seine Auswirkung gezeigt hat, aber noch nicht dessen Ursache. Um an die Fehlerursache zu kommen, müsste man wissen, welche Werte bestimmte Variablen haben. Und es wäre hilfreich, das Programm Schritt für Schritt bzw. Zeile für Zeile auszuführen und zu beobachten, wie sich diese Werte ändern. Damit haben wir bereits die wesentlichen Funktionen des Debuggers in Visual Studio vorweggenommen:
Haltepunkte (Breakpoints)
Damit man ein Programm Schritt für Schritt durchlaufen kann, muss man es zunächst anhalten bzw. pausieren. Mit einem Rechtsklick auf das Projekt -> Debuggen -> In Neue Instanz springen (Debug -> Step Into new instance) wird das Programm gestartet und an der ersten Anweisung angehalten. Meistens genügt das allerdings nicht, und man möchte direkt in eine bestimmte Methode oder eine bestimmte Schleife springen.
Um das Programm an einer beliebigen Stelle im Code anzuhalten, setzt man einen Haltepunkt. Dazu klickt man links neben die entsprechende Programmzeile oder drückt F9. Die Zeile wird dann rot eingefärbt und es erscheint ein roter Punkt (der Halte-"Punkt"). Das Programm wird jetzt immer dann pausiert, wenn diese Codestelle bei der Ausführung erreicht wird.
Bedingte Haltepunkte (Conditional Breakpoints)
In manchen Situationen hat man bereits festgestellt, dass ein Fehler immer nur unter bestimmten Umständen auftritt, z.B. wenn eine Variable i den Wert 0 hat. Damit das Programm auch nur dann angehalten wird, kann man mit der rechten Maustaste auf einen Haltepunkt klicken und im Kontextmenü den Befehl für die Bedingung auswählen. Dort kann man dann z.B. i == 0 eingeben oder (fast) beliebige andere Bedingungen.
Variablen-Überwachung (Watches)
Während das Programm angehalten ist - beispielsweise durch einen Haltepunkt oder eine Ausnahme (Exception) - kann man einfach mit der Maus über eine Variable fahren. Dann erscheint eine "DataTip" genannte Kurzinfo mit dem Typ und dem Wert der Variablen. Die meisten Fehler lassen sich bereits dadurch aufspüren, da man sehr schnell erkennt, wenn eine Variable einen unerwarteten Wert hat, wie z.B. null. Man kann die Werte übrigens dort auch direkt verändern, indem man doppelt auf den angezeigten Wert klickt.
Die "DataTips" haben einen kleinen Pin, der verhindert, dass sie automatisch wieder geschlossen werden. So kann man sich mehrere Variablen zur gleichen Zeit anschauen.
Um dabei nicht die Übersicht zu verlieren, gibt es das Überwachungs-Fenster, in dem man beliebige Anweisungen (nicht nur Variablen) eintragen, überwachen und verändern kann. Alternativ dazu kann man im Quellcode eine Anweisung markieren und über "Überwachung hinzufügen" ("Add Watch") im Kontextmenü zum Überwachungsfenster hinzufügen.
Einzelschritt-Debugging (Single Step Debugging)
Hat man sich mit den bisher genannten Möglichkeiten einen Überblick über den aktuellen Zustand des Programms geschaffen, will man oft wissen, welcher Code weiterhin ausgeführt wird, und wie sich dadurch die Werte der Variablen ändern. So kann man genau die Codezeile aufspüren, in der die Fehlerursache liegt (und sie beheben).
Das geht mit der Einzelschritt-Funktion (F11). Dann wird die Programmausführung nach jeder Anweisung wieder unterbrochen und man kann Schritt für Schritt durch das Programm gehen, um den Programmablauf genauestens nachzuvollziehen. Mit F10 kann man Methoden-Aufrufe überspringen, um zu verhindern, dass jede einzelne Anweisung innerhalb einer aufgerufenen Methode ausgeführt wird.
Noch schneller ist es manchmal, einige zusätzliche Haltepunkte an den interessanten Codestellen zu setzen und dann das Programm mit F5 weiter auszuführen. Das Programm wird dann beim ersten Haltepunkt, der erreicht wird, wieder angehalten. So lässt sich gezielt herausfinden, welche Codeblöcke bei der Ausführung durchlaufen werden und in welcher Reihenfolge sie dabei aufgerufen werden.
Die genannten Möglichkeiten des Einzelschritt-Debuggings helfen somit einerseits bei der Überwachung des Programmzustands (z.B. fehlerhafte Werte in Variablen) als auch beim Aufspüren von Fehlern im Programmablauf (z.B. bei fehlerhaften Abbruchbedingungen in Schleifen).
Nächste Anweisung setzen (Set Next Statement)
Um selbst in den Programmablauf einzugreifen, z.B. um eine Funktion ein weiteres Mal aufzurufen oder einen bestimmten Code-Block zu umgehen, gibt es im Kontextmenü die Funktion "Nächste Anweisung festlegen" ("Set Next Statement"). Alternativ kann man den kleinen gelben Pfeil links neben der aktuellen Anweisung auch mit der Maus auf eine andere Position ziehen. Führt man das Programm dann mit F5, F10 oder F11 weiter aus, wird das Programm an dieser Stelle fortgesetzt.
Fehlerbehebung
In Visual Studio gibt es eine sehr hilfreiche Funktion namens


Systematische Fehlersuche
Um die Qualität einer Anwendung sicherzustellen und Fehler systematisch aufzuspüren, sollte man unbedingt jede Funktion mithilfe von

Und noch ein Tipp: Auch bei hartnäckigen Fehlern sollte man nicht die Geduld verlieren sondern systematisch vorgehen. Eine Anleitung, wie man in solchen Fällen vorgehen kann, gibt es unter

Systematische Fehlervermeidung
Um die gewünschte Funktionalität eines Programms von vornherein und dauerhaft zu gewährleisten, gibt es nur eine Lösung, nämlich konsequentes

Fazit
Mithilfe von Funktionen wie Einzelschritt-Debugging, Haltepunkten und Variablen-Überwachung lassen sich Fehler in Programmen schnell und zielsicher aufspüren und beheben.