Методы и средства отладки, помогающие писать более качественный код

Исправление ошибок и ошибок в коде может занять много времени и иногда страстить задачу. Требуется время, чтобы узнать, как эффективно выполнять отладку. Мощная интегрированная среда разработки, например Visual Studio, может упростить работу. Интегрированная среда разработки помогает устранять ошибки и выполнять отладку кода быстрее, а также помогает создавать более удобный код с меньшим количеством ошибок. В этой статье представлено целостное представление процесса исправления ошибок, поэтому вы можете знать, когда следует использовать анализатор кода, когда использовать отладчик, как исправить исключения и как кодировать для намерения. Если вы уже знаете, что вам нужно использовать отладчик, ознакомьтесь с отладчиком.

В этой статье вы узнаете, как работать с интегрированной среду разработки, чтобы сделать сеансы программирования более продуктивными. Мы затронем несколько задач, таких как:

  • Подготовка кода к отладке с помощью анализатора кода интегрированной среды разработки

  • Исправление исключений (ошибки времени выполнения)

  • Минимизация ошибок путем написания кода с учетом его предназначения (с помощью оператора assert)

  • Аспекты использования отладчика

Чтобы продемонстрировать эти задачи, мы показываем несколько наиболее распространенных типов ошибок и ошибок, которые могут возникнуть при попытке отладки приложений. Хотя пример кода — C#, концептуальные сведения обычно применимы к C++, Visual Basic, JavaScript и другим языкам, поддерживаемым Visual Studio (за исключением отмеченных). На снимках экрана представлены примеры на C#.

Создание примера приложения с некоторыми ошибками

В следующем коде есть ошибки, которые можно исправить с помощью интегрированной среды разработки Visual Studio. Это простое приложение, которое имитирует получение данных JSON из некоторых операций, десериализация данных в объект и обновление простого списка с новыми данными.

Чтобы создать приложение, необходимо установить Visual Studio и рабочую нагрузку разработки для классических приложений .NET.

  • Установите Visual Studio бесплатно со страницы скачиваемых материалов Visual Studio, если еще не сделали этого.

  • Если необходимо установить рабочую нагрузку, но у вас уже есть Visual Studio, выберите "Сервис>получения инструментов" и "Компоненты". Запускается Visual Studio Installer. Выберите рабочую нагрузку Разработка классических приложений .NET и нажмите Изменить.

Выполните следующие действия, чтобы создать приложение:

  1. Откройте Visual Studio. В окне запуска выберите Создание нового проекта.

  2. В поле поиска введите консоль , а затем один из вариантов консольного приложения для .NET.

  3. Выберите Далее.

  4. Введите имя проекта, например Console_Parse_JSON, и нажмите кнопку "Далее" или "Создать", как применимо.

    Выберите рекомендуемую целевую платформу или .NET 8, а затем нажмите кнопку "Создать".

    Если вы не видите шаблон проекта консольного приложения для .NET, перейдите к разделу "Сервис>получения инструментов и компонентов", который открывает Установщик Visual Studio. Выберите рабочую нагрузку Разработка классических приложений .NET и нажмите Изменить.

    Visual Studio создаст консольный проект и откроет его в Обозревателе решений в области справа.

Когда проект готов, замените код по умолчанию в файле Program.cs проекта следующим примером кода:

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;
    }
}

Ищите красные и зеленые волнистые линии!

Прежде чем пытаться запустить пример приложения и отладчик, проверьте код в редакторе кода на наличие красных и зеленых волнистых линий. Они представляют ошибки и предупреждения, определенные анализатором кода интегрированной среды разработки. Красные волнистые линии обозначают ошибки времени компиляции, которые необходимо исправить, прежде чем можно будет запустить код. Зеленые волнистые линии обозначают предупреждения. Хотя часто приложение можно запустить без исправления предупреждений, они могут быть источником ошибок, а их устранение во многих случаях помогает сэкономить время и силы. Эти предупреждения и ошибки также можно просмотреть в окне Список ошибок, если вы предпочитаете представление списка.

В примере приложения вы увидите несколько красных волнистых волн, которые необходимо исправить, и зеленый, который необходимо исследовать. Вот первая ошибка.

Ошибка, представленная красной волнистой линией

Чтобы устранить эту ошибку, можно посмотреть на другую функцию интегрированной среды разработки, представленную значком лампочки.

Проверяйте лампочку!

Первая красная волнистая линия представляет ошибку времени компиляции. Наведите на нее указатель мыши, и появится сообщение The name `Encoding` does not exist in the current context.

Обратите внимание, что в нижнем левом углу от этой ошибки отображается значок лампочки. Наряду с значком отвертки значок значок отверткиЗначок лампочки лампочки представляет быстрые действия, которые помогут вам исправить или рефакторинг кода. Лампочка представляет проблемы, которые необходимо исправить. Отвертка указывает на проблемы, которые при желании можно исправить. Используйте первое предлагаемое исправление для устранения этой ошибки, щелкнув элемент using System.Text слева.

Исправление кода с помощью лампочки

При выборе этого элемента Visual Studio добавляет using System.Text инструкцию в верхней части файла Program.cs , а красная волнистая полоса исчезает. (Если вы не уверены в изменениях, примененных предлагаемым исправлением, выберите Предварительная версия ссылки на изменения справа перед применением исправления.)

Описанная выше ошибка является распространенной и обычно устраняется путем добавления в код нового оператора using. Существует несколько распространенных ошибок, таких как The type or namespace "Name" cannot be found. эти типы ошибок, могут указывать на отсутствующую ссылку на сборку (щелкните проект правой кнопкой мыши, выберите "Добавить>ссылку"), имя с ошибкой или недостающую библиотеку, которую необходимо добавить (для C#, щелкните проект правой кнопкой мыши и выберите "Управление пакетами NuGet").

Исправление оставшихся ошибок и предупреждений

В этом коде есть еще несколько волнистых линий. Здесь присутствует типичная ошибка преобразования типа. При наведении указателя мыши на волнистую линию вы увидите, что код пытается преобразовать строку в int, которая не поддерживается, если только не добавить явный код для преобразования.

Ошибка преобразования типа

Так как анализатор кода не может угадать ваше намерение, в данном случае лампочки, которые могут помочь вам, отсутствуют. Чтобы устранить эту ошибку, необходимо знать предназначение кода. В этом примере не слишком трудно увидеть, что points должно быть числовым (целочисленным) значением, так как вы пытаетесь добавить points в totalpoints.

Чтобы устранить эту ошибку, измените член points класса User с такого:

[DataMember]
internal string points;

на такой:

[DataMember]
internal int points;

Красные волнистые линии в редакторе кода исчезают.

Затем наведите указатель мыши на зеленую волнистую линию в объявлении элемента данных points. Анализатор кода сообщает, что переменной никогда не присваивается значение.

Предупреждающее сообщение для неназначенной переменной

Как правило, это указывает на проблему, которую необходимо исправить. Однако в этом примере приложения вы фактически сохраняете данные в переменной points в процессе десериализации, а затем добавляете это значение в элемент данных totalpoints. В этом примере вы знаете предназначение кода и можете спокойно проигнорировать предупреждение. Однако если вы хотите устранить это предупреждение, можно заменить этот код:

item.totalpoints = users[i].points;

следующим кодом:

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

Зеленая волнистая линия исчезает.

Исправление исключения

Когда вы исправили все красные волнистые линии и разрешенные или по крайней мере расследованные- все зеленые волнистые волнистые линии, вы готовы запустить отладчик и запустить приложение.

Нажмите клавишу F5 (Отладка > запуска отладки) или кнопку Начать отладку "Начать отладку" на панели инструментов отладки.

На этом этапе пример приложения выдает исключение SerializationException (ошибка времени выполнения). То есть приложение перехватывает данные, которые он пытается сериализовать. Так как приложение запущено в режиме отладки (подключен отладчик), помощник по исправлению ошибок отладчика переносит вас прямо к коду, выдавшему исключение, и выдает полезное сообщение об ошибке.

Возникает исключение SerializationException

Сообщение об ошибке указывает, что значение 4o не может быть проанализировано как целое число. Итак, в этом примере вы знаете, что данные указаны неправильно: 4o вместо 40. Однако если вы не управляете данными в реальном сценарии (предположим, что вы получаете его из веб-службы), что вы делаете с этим? Как это исправить?

При возникновении исключения необходимо задать себе пару вопросов:

  • Является ли это исключение простой ошибкой, которую можно исправить? или

  • Может ли это исключение возникнуть у пользователей?

В первом случае исправьте ошибку. (В примере приложения необходимо исправить плохие данные.) Если это последний вариант, может потребоваться обработать исключение в коде с помощью try/catch блока (мы рассмотрим другие возможные стратегии в следующем разделе). В примере приложения замените приведенный ниже код:

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

таким кодом:

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
}

Блок try/catch подразумевает некоторое снижение производительности, поэтому их следует использовать только тогда, когда это действительно необходимо, то есть, когда (а) они могут возникнуть в окончательной версии приложения и (б) документация по методу указывает, что необходимо проверить наличие исключения (с предположением, что документация завершена). Во многих случаях можно соответствующим образом обработать исключение, чтобы пользователь никогда с ним не столкнулся.

Ниже приведено несколько важных советов по обработке исключений:

  • Избегайте использования пустого блока перехвата, например catch (Exception) {}, который не принимает соответствующее действие для предоставления или обработки ошибки. Пустой или неинформативный блок перехвата может скрыть исключения и сделать код более сложным для отладки, а не проще.

  • Используйте блок try/catch вокруг конкретной функции, которая вызывает исключение (ReadObject в примере приложения). Если использовать его вокруг большого фрагмента кода, вы в итоге скроете расположение ошибки. Например, не используйте блок try/catch вокруг вызова родительской функции ReadToObject, как показано здесь, или вы не сможете точно определить, где возникло исключение.

    // Don't do this
    try
    {
        User[] users = ReadToObject(data);
    }
    catch (SerializationException)
    {
    }
    
  • Для незнакомых функций, которые вы включаете в приложение, особенно функции, взаимодействующие с внешними данными (например, веб-запрос), проверка документацию, чтобы узнать, какие исключения функция, скорее всего, вызовет. Это может оказаться крайне важной информацией для правильной обработки ошибок и отладки приложения.

Для данного примера приложения исправьте SerializationException в методе GetJsonData, изменив 4o на 40.

Совет

Если у вас есть Copilot, вы можете получить помощь по искусственному интеллекту во время отладки исключений. Просто найдите кнопку Ask CopilotСнимок экрана: кнопка Ask Copilot.. Дополнительные сведения см. в разделе Отладка с помощью Copilot.

Уточнение предназначения кода с помощью оператора assert

Нажмите кнопку "Перезапустить" на панели инструментов отладки (CTRL + SHIFT + F5).Перезагрузить приложение Это приведет к перезапуску приложения за меньшее количество шагов. В окне консоли вы увидите приведенные ниже выходные данные.

Значение NULL в выходных данных

Вы можете увидеть что-то в этом выходных данных неправильно. Значения имени и фамилии для третьей записи пусты!

Это подходящий момент, чтобы рассмотреть полезный и часто недооцененный подход к написанию кода, который заключается в использовании операторов assert в функциях. Добавив следующий код, вы включаете среду выполнения проверка, чтобы убедиться, что firstname и lastname нетnull. Замените приведенный ниже код в методе UpdateRecords:

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

следующим кодом:

// 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;

Добавив такие операторы assert в функции в процессе разработки, можно указать предназначение кода. В предыдущем примере мы укажем следующие элементы:

  • Для имени требуется допустимая строка.
  • Для фамилии требуется допустимая строка.

Указав таким образом намерение, вы применяете необходимые требования. Это простой и удобный способ, который можно использовать для изучения ошибок во время разработки. (Операторы assert также используются в качестве главного элемента в модульных тестах.)

Нажмите кнопку "Перезапустить" на панели инструментов отладки (CTRL + SHIFT + F5).Перезагрузить приложение

Примечание.

Код assert активен только в отладочной сборке.

При перезапуске отладчик приостанавливает выполнение на операторе assert, так как выражение users[i].firstname != null вычисляется как false вместо true.

Оператор assert разрешается в значение false

Ошибка assert указывает на проблему, которую необходимо изучить. assert может охватывать множество сценариев, в которых исключение отображается не всегда. В этом примере пользователь не видит исключения, а null значение добавляется как firstname в списке записей. Это условие может привести к проблемам позже (например, в выходных данных консоли) и может быть труднее выполнить отладку.

Примечание.

В сценариях, где вы вызываете метод для значения null, возникает NullReferenceException. Обычно следует избегать использования блока try/catch для общего исключения, то есть исключения, не привязанного к определенной функции библиотеки. Любой объект может вызывать NullReferenceException. Если вы не уверены, изучите документацию по функции библиотеки.

Во время процесса отладки рекомендуется использовать определенный оператор assert, пока не станет понятно, что его нужно заменить реальным исправлением кода. Допустим, вы решили, что пользователь может столкнуться с исключением в сборке выпуска приложения. В этом случае необходимо выполнить рефакторинг кода, чтобы убедиться, что приложение не выдает неустранимое исключение или не приводит к возникновению какой-либо другой ошибки. Итак, чтобы исправить этот код, замените приведенный ниже код:

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

таким кодом:

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

Используя этот код, вы выполняете требования к коду и убедитесь, что запись с firstname или lastname значением null не добавляется в данные.

В этом примере мы добавили в цикл два оператора assert. Как правило, при использовании assertрекомендуется добавлять операторы assert в точке входа (начале) функции или метода. В настоящее время вы просматриваете UpdateRecords метод в примере приложения. В этом методе вы можете столкнуться с проблемами, если какой-либо из его аргументов равен null, поэтому проверьте их оба с помощью оператора assert в точке входа функции.

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

Для предыдущих операторов ваше намерение заключается в том, чтобы загрузить существующие данные (db) и извлечь новые данные (users) перед обновлением.

Оператор assert можно использовать с любым видом выражения, которое разрешается в true или false. Например, можно добавить оператор assert следующего вида.

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

Приведенный выше код полезен, если необходимо указать следующее намерение: для обновления записи пользователя требуется новое значение точки больше нуля (0).

Проверка кода в отладчике

Итак, теперь, когда вы устранили все критические проблемы с примером приложения, можно двигаться дальше.

Мы рассмотрели помощник по исправлению ошибок отладчика, но отладчик является гораздо более функциональным инструментом, который позволяет совершать и другие действия, такие как пошаговое выполнение кода и изучение его переменных. Эти более мощные возможности полезны во многих сценариях, особенно в следующих сценариях:

  • Вы пытаетесь изолировать ошибку среды выполнения в коде, но не можете сделать это с помощью методов и инструментов, которые ранее обсуждались.

  • Вы хотите проверить код, то есть смотреть его во время его выполнения, чтобы убедиться, что он ведет себя так, как вы ожидаете и делаете то, что вы хотите.

    Это поучительно смотреть код во время его выполнения. Так вы можете получить дополнительные сведения о коде и выявить ошибки до того, как проявляются их очевидные симптомы.

Дополнительные сведения об использовании функций отладчика см. в статье Отладка для начинающих.

исправлять проблемы производительности;

Ошибки другого типа включают неэффективный код, из-за которого приложение выполняется медленнее или использует слишком много памяти. Как правило, оптимизация производительности осуществляется на более позднем этапе разработки приложения. Однако вы можете столкнуться с проблемами с производительностью раньше (например, вы видите, что некоторые части приложения работают медленно), и вам может потребоваться протестировать приложение с помощью средств профилирования на ранних этапах. Дополнительные сведения о средствах профилирования, таких как средство загрузки ЦП и анализатор памяти, см. в разделе Знакомство со средствами профилирования.

В этой статье вы узнали, как предотвращать и устранять многие распространенные ошибки в коде и когда использовать отладчик. Далее вы можете подробнее узнать об использовании отладчика Visual Studio для исправления ошибок.