Freigeben über


Debugging-Techniken und -Tools, die Ihnen helfen, besseren Code zu schreiben

Das Beheben von Fehlern und Fehlern in Ihrem Code kann eine zeitaufwendige und manchmal frustrierende Aufgabe sein. Es dauert Zeit, um zu erfahren, wie Sie effektiv debuggen. Eine leistungsstarke IDE wie Visual Studio kann Ihre Arbeit erheblich vereinfachen. Eine IDE kann Ihnen dabei helfen, Fehler zu beheben und Den Code schneller zu debuggen und ihnen dabei zu helfen, besseren Code mit weniger Fehlern zu schreiben. Dieser Artikel bietet einen ganzheitlichen Überblick über den Fehlerbehebungsprozess, damit Sie wissen können, wann Sie den Code-Analyser verwenden, wann Sie den Debugger benutzen sollen, wie Ausnahmen behoben werden und wie Sie zielgerichtet programmieren. Wenn Sie bereits wissen, dass Sie den Debugger verwenden müssen, lesen Sie sich zuerst ‘Erster Blick auf den Debugger’ durch.

In diesem Artikel erfahren Sie, wie Sie mit der IDE arbeiten, um Ihre Codierungssitzungen produktiver zu gestalten. Wir sprechen mehrere Aufgaben an, wie zum Beispiel:

  • Vorbereiten des Codes für das Debuggen mithilfe der Codeanalyse der IDE

  • Wie man Ausnahmen (Laufzeitfehler) behebt

  • Fehler minimieren, indem man für die Absicht programmiert (mithilfe von assert)

  • Wann der Debugger verwendet werden sollte

Um diese Aufgaben zu veranschaulichen, zeigen wir einige der häufigsten auftretenden Arten von Fehlern und Bugs, die beim Debuggen Ihrer Apps auftreten können. Obwohl der Beispielcode C# ist, gelten die konzeptionellen Informationen in der Regel für C++, Visual Basic, JavaScript und andere Sprachen, die von Visual Studio unterstützt werden (mit Ausnahme der Angabe). Die Screenshots befinden sich in C#.

Erstellen Sie eine Beispiel-App mit einigen Fehlern und Bugs

Der folgende Code enthält einige Fehler, die Sie mithilfe der Visual Studio-IDE beheben können. Diese Anwendung ist eine einfache App, die das Abrufen von JSON-Daten aus einigen Vorgängen simuliert, die Daten in ein Objekt deserialisieren und eine einfache Liste mit den neuen Daten aktualisiert.

Zum Erstellen der Anwendung müssen Sie Visual Studio sowie die .NET Desktop-Entwicklungsarbeitslast installiert haben.

  • Wenn Sie Visual Studio noch nicht installiert haben, wechseln Sie zur Visual Studio-Downloads Seite, um es kostenlos zu installieren.

  • Wenn Sie die Workload installieren müssen, aber bereits über Visual Studio verfügen, wählen Sie Tools>Tools und Features abrufen aus. Das Visual Studio-Installationsprogramm wird gestartet. Wählen Sie beispielsweise die Workload .NET-Desktopentwicklung aus, und klicken Sie anschließend auf Ändern.

Führen Sie die folgenden Schritte aus, um die Anwendung zu erstellen:

  1. Öffnen Sie Visual Studio. Klicken Sie im Startfenster auf Neues Projekt erstellen.

  2. Geben Sie im Suchfeld konsolen - und dann eine der Konsolen-App-Optionen für .NET ein.

  3. Wählen Sie Weiteraus.

  4. Geben Sie einen Projektnamen wie Console_Parse_JSON ein, und wählen Sie dann nach Bedarf "Weiter " oder " Erstellen" aus.

    Wählen Sie entweder das empfohlene Zielframework oder .NET 8 aus, und wählen Sie Erstellen aus.

    Wenn die Projektvorlage "Konsolen-App für .NET" nicht angezeigt wird, wechseln Sie zu Tools>Tools und Funktionen abrufen, wodurch das Visual Studio-Installationsprogramm geöffnet wird. Wählen Sie beispielsweise die Workload .NET-Desktopentwicklung aus, und klicken Sie anschließend auf Ändern.

    Visual Studio erstellt das Konsolenprojekt, das im Projektmappen-Explorer im rechten Bereich angezeigt wird.

Wenn das Projekt fertig ist, ersetzen Sie den Standardcode in der Program.cs Datei des Projekts durch den folgenden Beispielcode:

using System;
using System.Collections.Generic;
using System.Runtime.Serialization.Json;
using System.Runtime.Serialization;
using System.IO;

namespace Console_Parse_JSON
{
    class Program
    {
        static void Main(string[] args)
        {
            var localDB = LoadRecords();
            string data = GetJsonData();

            User[] users = ReadToObject(data);

            UpdateRecords(localDB, users);

            for (int i = 0; i < users.Length; i++)
            {
                List<User> result = localDB.FindAll(delegate (User u) {
                    return u.lastname == users[i].lastname;
                    });
                foreach (var item in result)
                {
                    Console.WriteLine($"Matching Record, got name={item.firstname}, lastname={item.lastname}, age={item.totalpoints}");
                }
            }

            Console.ReadKey();
        }

        // Deserialize a JSON stream to a User object.
        public static User[] ReadToObject(string json)
        {
            User deserializedUser = new User();
            User[] users = { };
            MemoryStream ms = new MemoryStream(Encoding.UTF8.GetBytes(json));
            DataContractJsonSerializer ser = new DataContractJsonSerializer(users.GetType());

            users = ser.ReadObject(ms) as User[];

            ms.Close();
            return users;
        }

        // Simulated operation that returns JSON data.
        public static string GetJsonData()
        {
            string str = "[{ \"points\":4o,\"firstname\":\"Fred\",\"lastname\":\"Smith\"},{\"lastName\":\"Jackson\"}]";
            return str;
        }

        public static List<User> LoadRecords()
        {
            var db = new List<User> { };
            User user1 = new User();
            user1.firstname = "Joe";
            user1.lastname = "Smith";
            user1.totalpoints = 41;

            db.Add(user1);

            User user2 = new User();
            user2.firstname = "Pete";
            user2.lastname = "Peterson";
            user2.totalpoints = 30;

            db.Add(user2);

            return db;
        }
        public static void UpdateRecords(List<User> db, User[] users)
        {
            bool existingUser = false;

            for (int i = 0; i < users.Length; i++)
            {
                foreach (var item in db)
                {
                    if (item.lastname == users[i].lastname && item.firstname == users[i].firstname)
                    {
                        existingUser = true;
                        item.totalpoints += users[i].points;

                    }
                }
                if (existingUser == false)
                {
                    User user = new User();
                    user.firstname = users[i].firstname;
                    user.lastname = users[i].lastname;
                    user.totalpoints = users[i].points;

                    db.Add(user);
                }
            }
        }
    }

    [DataContract]
    internal class User
    {
        [DataMember]
        internal string firstname;

        [DataMember]
        internal string lastname;

        [DataMember]
        // internal double points;
        internal string points;

        [DataMember]
        internal int totalpoints;
    }
}

Suchen Sie die roten und grünen Zickzacklinien!

Bevor Sie versuchen, die Beispielanwendung zu starten und den Debugger auszuführen, überprüfen Sie den Code im Code-Editor auf rote und grüne Zickzacklinien. Diese stellen Fehler und Warnungen dar, die von der Codeanalyse der IDE identifiziert werden. Die roten Zickzacklinien sind Kompilierfehler, die Sie beheben müssen, bevor Sie den Code ausführen können. Bei den grünen Zickzacklinien handelt es sich um Warnungen. Obwohl Sie Ihre App häufig ausführen können, ohne die Warnungen zu beheben, können sie eine Quelle von Fehlern sein, und Sie sparen sich oft Zeit und Probleme, indem Sie sie untersuchen. Diese Warnungen und Fehler werden auch im Fenster " Fehlerliste " angezeigt, wenn Sie eine Listenansicht bevorzugen.

In der Beispiel-App sehen Sie mehrere rote Wellenlinien, die Sie korrigieren müssen, und eine grüne, die Sie überprüfen müssen. Dies ist der erste Fehler.

Fehleranzeige als rote Wellenlinie

Um diesen Fehler zu beheben, können Sie ein anderes Feature der IDE anzeigen, dargestellt durch das Glühbirnensymbol.

Überprüfen Sie die Glühbirne!

Der erste rote Wellenpunkt stellt einen Kompilierungszeitfehler dar. Zeigen Sie darauf, und die Nachricht The name `Encoding` does not exist in the current contextwird angezeigt.

Beachten Sie, dass dieser Fehler ein Glühbirnensymbol links unten anzeigt. Zusammen mit dem Schraubenziehersymbol stellt das Glühbirnensymbol Schnellaktionen dar, die Ihnen helfen können, Code inline zu beheben oder umzugestalten. Die Glühbirne stellt Probleme dar, die Sie beheben sollten . Der Schraubenzieher ist für Probleme vorgesehen, die Sie möglicherweise beheben möchten. Verwenden Sie die erste vorgeschlagene Lösung, um diesen Fehler zu beheben, indem Sie auf der linken Seite auf "System.Text " klicken.

Verwenden der Glühbirne zum Beheben von Code

Wenn Sie dieses Element auswählen, fügt Visual Studio die using System.Text Anweisung oben in der Program.cs Datei hinzu, und die rote Wellenlinie verschwindet. (Wenn Sie sich nicht sicher sind, welche Änderungen von einem vorgeschlagenen Fix angewendet wurden, wählen Sie den Link Änderungen anzeigen auf der rechten Seite aus, bevor Sie die Korrektur anwenden.)

Der vorstehende Fehler ist ein gängiger Fehler, den Sie normalerweise beheben, indem Sie Ihrem Code eine neue using Anweisung hinzufügen. Es gibt mehrere häufige, ähnliche Fehler wie diese, zThe type or namespace "Name" cannot be found.. B. Diese Arten von Fehlern können auf einen fehlenden Assemblyverweis hinweisen (klicken Sie mit der rechten Maustaste auf das Projekt, wählen Sie"Verweis>", einen falsch geschriebenen Namen oder eine fehlende Bibliothek, die Sie hinzufügen müssen (für C# klicken Sie mit der rechten Maustaste auf das Projekt, und wählen Sie "NuGet-Pakete verwalten" aus).

Beheben der verbleibenden Fehler und Warnungen

Es gibt ein paar weitere Markierungen, die in diesem Code zu sehen sind. Hier sehen Sie einen allgemeinen Typkonvertierungsfehler. Wenn Sie mit dem Mauszeiger auf die Wellenlinie zeigen, sehen Sie, dass der Code versucht, eine Zeichenfolge in einen Integer zu konvertieren, was nicht unterstützt wird, es sei denn, Sie fügen expliziten Code hinzu, um die Konvertierung vorzunehmen.

Typkonvertierungsfehler

Da der Codeanalysator Ihre Absicht nicht erraten kann, gibt es keine Glühbirnen, die Ihnen dieses Mal helfen. Um diesen Fehler zu beheben, müssen Sie die Absicht des Codes kennen. In diesem Beispiel ist es nicht schwer zu erkennen, dass points ein numerischer (ganzzahliger) Wert sein sollte, da Sie versuchen, diesen Wert zu points zu totalpoints hinzufügen.

Um diesen Fehler zu beheben, ändern Sie das points Element der User Klasse aus folgendem:

[DataMember]
internal string points;

zu diesem Thema:

[DataMember]
internal int points;

Die roten Wellenlinien im Code-Editor gehen weg.

Zeigen Sie als Nächstes auf die grüne Wellenlinie in der Deklaration des points Datenelements. Die Codeanalyse teilt Ihnen mit, dass die Variable niemals einem Wert zugewiesen wird.

Warnmeldung für nicht zugewiesene Variable

In der Regel stellt dies ein Problem dar, das behoben werden muss. In der Beispiel-App speichern Sie jedoch Daten während des Deserialisierungsprozesses in der points-Variablen und fügen diesen Wert dann dem totalpoints-Datenmitglied hinzu. In diesem Beispiel kennen Sie die Absicht des Codes und können die Warnung sicher ignorieren. Wenn Sie die Warnung jedoch entfernen möchten, können Sie den folgenden Code ersetzen:

item.totalpoints = users[i].points;

durch diesen:

item.points = users[i].points;
item.totalpoints += users[i].points;

Die grüne Wellenlinie verschwindet.

Behebung einer Ausnahme

Wenn Sie alle roten Wellenlinien behoben und alle grünen Wellenlinien zumindest untersucht oder gelöst haben, können Sie den Debugger starten und die App ausführen.

Drücken Sie F5 (Debug > Start Debugging) oder die Schaltfläche "Start DebuggingStart Debugging" in der Symbolleiste "Debug".

An diesem Punkt löst die Beispiel-App eine SerializationException Ausnahme aus (laufzeitfehler). Das heißt, die App hängt sich bei den Daten auf, die sie zu serialisieren versucht. Da Sie die App im Debugmodus (angefügter Debugger) gestartet haben, gelangen Sie mit der Ausnahmehilfe des Debuggers direkt zum Code, der die Ausnahme ausgelöst hat, und gibt Ihnen eine hilfreiche Fehlermeldung.

Eine SerializationException tritt auf

Die Fehlermeldung weist Sie an, dass der Wert 4o nicht als ganze Zahl analysiert werden kann. In diesem Beispiel wissen Sie also, dass die Daten schlecht sind: 4o sollte sein 40. Wenn Sie jedoch nicht in der Kontrolle über die Daten in einem echten Szenario sind (sagen Sie, dass Sie sie von einem Webdienst erhalten), was tun Sie damit? Wie beheben Sie dies?

Wenn Sie auf eine Ausnahme stoßen, müssen Sie ein paar Fragen stellen (und beantworten):

  • Ist diese Ausnahme nur ein Fehler, den Sie beheben können? Oder

  • Ist dies eine Ausnahme, auf die Ihre Benutzer stoßen könnten?

Wenn es sich um den früheren Fall handelt, beheben Sie den Fehler. (In der Beispiel-App müssen Sie die fehlerhaften Daten beheben.) Wenn dies der Fall ist, müssen Sie möglicherweise die Ausnahme in Ihrem Code mithilfe eines try/catch Blocks behandeln (wir betrachten andere mögliche Strategien im nächsten Abschnitt). Ersetzen Sie in der Beispiel-App den folgenden Code:

users = ser.ReadObject(ms) as User[];

mit diesem Code:

try
{
    users = ser.ReadObject(ms) as User[];
}
catch (SerializationException)
{
    Console.WriteLine("Give user some info or instructions, if necessary");
    // Take appropriate action for your app
}

Ein try/catch Block hat einige Leistungskosten, sodass Sie sie nur verwenden möchten, wenn Sie sie wirklich benötigen, d. a., wo (a) sie in der Releaseversion der App auftreten können, und wo (b) die Dokumentation für die Methode angibt, dass Sie nach der Ausnahme suchen sollten (vorausgesetzt, die Dokumentation ist abgeschlossen!). In vielen Fällen können Sie eine Ausnahme entsprechend behandeln, und der Benutzer muss sie nie kennen.

Hier sind einige wichtige Tipps für die Ausnahmebehandlung:

  • Vermeiden Sie, einen leeren Catch-Block wie catch (Exception) {} zu verwenden, der keine geeigneten Maßnahmen zum Offenlegen oder Behandeln eines Fehlers ergreift. Ein leerer oder nicht informativer Catch-Block kann Ausnahmen ausblenden, und den Code schwieriger debuggen, statt leichter zu machen.

  • Verwenden Sie den try/catch Block um die bestimmte Funktion, die die Ausnahme auslöst (ReadObjectin der Beispiel-App). Wenn Sie ihn um einen größeren Codeabschnitt verwenden, verbergen Sie die Position des Fehlers. Verwenden Sie beispielsweise nicht den try/catch Block um den Aufruf der übergeordneten Funktion ReadToObject herum, wie hier gezeigt, da Sie sonst nicht genau wissen, wo die Ausnahme aufgetreten ist.

    // Don't do this
    try
    {
        User[] users = ReadToObject(data);
    }
    catch (SerializationException)
    {
    }
    
  • Überprüfen Sie bei unbekannten Funktionen, die Sie in Ihre App einschließen, insbesondere funktionen, die mit externen Daten interagieren (z. B. eine Webanforderung), in der Dokumentation, um zu sehen, welche Ausnahmen die Funktion wahrscheinlich auslösen wird. Dies kann wichtige Informationen für die ordnungsgemäße Fehlerbehandlung und das Debuggen Ihrer App sein.

Beheben Sie für die Beispiel-App den SerializationException in der GetJsonData-Methode, indem Sie 4o zu 40 ändern.

Tipp

Wenn Sie Copilothaben, können Sie KI-Unterstützung erhalten, während Sie Ausnahmen debuggen. Suchen Sie einfach nach der Schaltfläche Copilot fragenScreenshot der Schaltfläche „Copilot fragen“.. Weitere Informationen finden Sie unter Debuggen mit Copilot.

Erläutern Sie die Absicht Ihres Codes mithilfe von "assert".

Wählen Sie die Schaltfläche RestartRestart App in der Debugsymbolleiste (STRG + UMSCHALT + F5) aus. Dadurch wird die App in weniger Schritten neu gestartet. Im Konsolenfenster wird die folgende Ausgabe angezeigt:

Nullwert in der Ausgabe

In diesem Ergebnis stimmt etwas nicht. Die Werte für Name und Nachname für den dritten Datensatz sind leer!

Dies ist ein guter Zeitpunkt, um über eine hilfreiche, häufig untergenutzte Codierungspraxis zu sprechen, nämlich die Verwendung von assert-Anweisungen in Ihren Funktionen. Durch Hinzufügen des folgenden Codes fügen Sie eine Laufzeitüberprüfung hinzu, um sicherzustellen, dass firstname und lastname nicht null sind. Ersetzen Sie den folgenden Code in der UpdateRecords Methode:

if (existingUser == false)
{
    User user = new User();
    user.firstname = users[i].firstname;
    user.lastname = users[i].lastname;

durch diesen:

// Also, add a using statement for System.Diagnostics at the start of the file.
Debug.Assert(users[i].firstname != null);
Debug.Assert(users[i].lastname != null);
if (existingUser == false)
{
    User user = new User();
    user.firstname = users[i].firstname;
    user.lastname = users[i].lastname;

Durch Hinzufügen assert von Anweisungen wie dieser zu Ihren Funktionen während des Entwicklungsprozesses können Sie dabei helfen, die Absicht Ihres Codes anzugeben. Im vorherigen Beispiel geben wir die folgenden Elemente an:

  • Für den Vornamen ist eine gültige Zeichenfolge erforderlich.
  • Für den Nachnamen ist eine gültige Zeichenfolge erforderlich.

Durch die Angabe von Absichten auf diese Weise erzwingen Sie Ihre Anforderungen. Dies ist eine einfache und praktische Methode, mit der Sie Während der Entwicklung Fehler anzeigen können. (assert Anweisungen werden auch als Hauptelement in Komponententests verwendet.)

Wählen Sie die Schaltfläche RestartRestart App in der Debugsymbolleiste (STRG + UMSCHALT + F5) aus.

Hinweis

Der assert Code ist nur in einem Debugbuild aktiv.

Beim Neustart wird der Debugger bei der assert Anweisung angehalten, da der Ausdruck users[i].firstname != null zu false anstatt zu true ausgewertet wird.

Der assert Fehler teilt Ihnen mit, dass ein Problem vorliegt, das Sie untersuchen müssen. assert kann viele Szenarien abdecken, in denen nicht unbedingt eine Ausnahme angezeigt wird. In diesem Beispiel wird dem Benutzer keine Ausnahme angezeigt, und ein null Wert wird wie firstname in der Liste der Datensätze hinzugefügt. Diese Bedingung kann später Probleme verursachen (z. B. wenn sie in der Konsolenausgabe angezeigt werden) und möglicherweise schwieriger zu debuggen.

Hinweis

In Szenarien, in denen Sie eine Methode für den null Wert aufrufen, wird ein NullReferenceException Ergebnis erzielt. Normalerweise möchten Sie vermeiden, einen try/catch Block für eine allgemeine Ausnahme zu verwenden, d. h. eine Ausnahme, die nicht an die spezifische Bibliotheksfunktion gebunden ist. Jedes Objekt kann ein NullReferenceException. Überprüfen Sie die Dokumentation für die Bibliotheksfunktion, wenn Sie nicht sicher sind.

Während des Debuggingprozesses sollten Sie eine bestimmte assert Anweisung beibehalten, bis Sie wissen, dass Sie sie durch eine tatsächliche Codekorrektur ersetzen müssen. Angenommen, dass der Benutzer die Ausnahme in einem Release-Build der App feststellen könnte. In diesem Fall müssen Sie Code umgestalten, um sicherzustellen, dass Ihre App keine schwerwiegende Ausnahme auslöst oder zu einem anderen Fehler führt. Um diesen Code zu beheben, ersetzen Sie also den folgenden Code:

if (existingUser == false)
{
    User user = new User();

mit diesem Code:

if (existingUser == false && users[i].firstname != null && users[i].lastname != null)
{
    User user = new User();

Mit diesem Code erfüllen Sie Ihre Codeanforderungen und stellen sicher, dass kein Datensatz mit einem firstname- oder lastname-Wert von null zu den Daten hinzugefügt wird.

In diesem Beispiel haben wir die beiden Anweisungen assert innerhalb einer Schleife hinzugefügt. In der Regel ist es am besten, assertAnweisungen am Einstiegspunkt (Anfang) einer Funktion oder Methode assert hinzuzufügen. Sie betrachten derzeit die UpdateRecords Methode in der Beispiel-App. In dieser Methode wissen Sie, dass Sie Probleme haben, wenn eines der Methodenargumente lautet null, also überprüfen Sie beide mit einer assert Anweisung am Einstiegspunkt der Funktion.

public static void UpdateRecords(List<User> db, User[] users)
{
    Debug.Assert(db != null);
    Debug.Assert(users != null);

Für die vorstehenden Anweisungen sollten Sie vorhandene Daten (db) laden und neue Daten (users) abrufen, bevor Sie etwas aktualisieren.

Sie können assert mit jeder Art von Ausdruck verwenden, der in true oder false aufgelöst wird. So könnten Sie z. B. eine assert anweisung wie folgt hinzufügen.

Debug.Assert(users[0].points > 0);

Der vorangehende Code ist nützlich, wenn Sie die folgende Absicht angeben möchten: Ein neuer Punktwert größer als Null (0) ist erforderlich, um den Datensatz des Benutzers zu aktualisieren.

Überprüfen des Codes im Debugger

OK, da Sie nun alle wichtigen Elemente behoben haben, die mit der Beispiel-App falsch sind, können Sie zu anderen wichtigen Dingen wechseln!

Wir haben Ihnen die Ausnahmehilfe des Debuggers gezeigt, aber der Debugger ist ein viel leistungsfähigeres Tool, mit dem Sie auch andere Dinge ausführen können, z. B. den Code schrittweise durchgehen und seine Variablen überprüfen können. Diese leistungsstärkeren Funktionen sind in vielen Szenarien nützlich, insbesondere in den folgenden Szenarien:

  • Sie versuchen, einen Laufzeitfehler in Ihrem Code zu isolieren, können dies jedoch nicht mithilfe von Methoden und Tools tun, die zuvor erläutert wurden.

  • Sie möchten Ihren Code validieren, indem Sie ihn beobachten, während er ausgeführt wird, um sicherzustellen, dass er sich in der erwarteten Weise verhält und das tut, was Sie wollen.

    Es ist lehrreich, Ihren Code während der Ausführung zu beobachten. Sie können auf diese Weise mehr über Ihren Code erfahren und häufig Fehler identifizieren, bevor sie offensichtliche Symptome manifestieren.

Informationen zur Verwendung der wesentlichen Features des Debuggers finden Sie unter Debugging für absolute Anfänger.

Leistungsprobleme beheben

Fehler einer anderen Art umfassen ineffizienten Code, der dazu führt, dass Ihre App langsam ausgeführt wird oder zu viel Arbeitsspeicher verwendet. Im Allgemeinen ist die Optimierung der Leistung etwas, was Sie später in der App-Entwicklung tun. Sie können jedoch frühzeitig Leistungsprobleme haben (z. B. sehen Sie, dass ein Teil Ihrer App langsam ausgeführt wird), und Sie müssen Ihre App möglicherweise frühzeitig mit den Profilerstellungstools testen. Weitere Informationen zu Profilerstellungstools wie dem CPU-Nutzungstool und dem Speicheranalysetool finden Sie unter Sehen Sie sich zunächst die Profilerstellungstoolsan.

In diesem Artikel haben Sie erfahren, wie Sie viele häufige Fehler in Ihrem Code vermeiden und beheben und wann sie den Debugger verwenden. Erfahren Sie als Nächstes mehr über die Verwendung des Visual Studio-Debuggers zum Beheben von Fehlern.