Отладка для абсолютных начинающих

Независимо от обстоятельств код, создаваемый разработчиками программного обеспечения, далеко не всегда работает так, как задумано. В некоторых случаях все идет совершенно не по плану! Когда происходит непредвиденное, следующая задача заключается в том, чтобы выяснить, почему, и хотя мы можем просто заманчиво просто следить за нашим кодом в течение нескольких часов, проще и эффективнее использовать средство отладки или отладчик.

К сожалению, отладчик не является той волшебной палочкой, по мановению которой будут выявлены абсолютно все проблемы в коде. Процесс отладки подразумевает пошаговое выполнение кода в средстве отладки (например, в Visual Studio) в поисках точки, в которой вы допустили ошибку при написании программы. Таким образом вы узнаете, какие исправления нужно внести в код. При этом средства отладки часто позволяют вносить временные изменения, чтобы продолжить работу с программой.

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

Проанализируйте проблему, задавая себе правильные вопросы

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

  • Что именно должен был выполнить код?

  • Что произошло на самом деле?

    Если при запуске приложения возникает ошибка (исключение), это может быть хорошо! Исключение возникает в том случае, если при выполнении кода происходит непредвиденное событие (как правило, это ошибка какого-либо рода). С помощью средства отладки вы можете перейти точно к тому месту в коде, где возникло исключение, и исследовать возможные способы исправления ситуации.

    Если произошло что-то еще, каковы признаки проблемы? Есть ли у вас предположения относительно того, в каком месте кода возникла проблема? Например, если код должен выводить какой-то текст, но при этом текст содержит ошибки, вы можете сделать вывод, что в этом случае используются неверные данные или код вывода текста содержит ошибки другого рода. При пошаговом выполнении кода в отладчике вы можете изучить каждое изменение переменных и точно определить, когда и каким образом были присвоены неверные значения.

Проверьте свои предположения

Прежде чем исследовать причины возникновения ошибки, проверьте предположения, на основании которых вы ожидаете тот или иной результат. Неявные или неизвестные вам предположения могут помешать выявлению проблемы, даже если вы прямо смотрите на ее причину в отладчике. У вас может быть длинный список возможных предположений! Чтобы проверить их, задайте себе несколько вопросов.

  • Используете ли вы нужный API (то есть соответствующие объект, функцию, метод или свойство)? Возможно, используемый вами API работает не так, как вы ожидаете. (После проверки вызова API в отладчике исправление может потребовать поездки в документацию, чтобы определить правильный API.)

  • Правильно ли вы используете API? Даже если вы выбрали нужный API, он может использоваться неправильно.

  • Нет ли в вашем коде опечаток? Некоторые опечатки, например ошибки в написании имени переменной, могут быть незаметными, особенно при работе с языками, в которых не требуется объявление переменных перед их использованием.

  • Вносили ли вы в код изменения, полагая, что они никак не связаны с возникшей проблемой?

  • Должны ли объект или переменная содержать определенное значение (или определенный тип значения) и соответствует ли это действительности?

  • Известно ли назначение кода? Отлаживать чужой код часто бывает сложнее. Если это не ваш код, возможно, для его эффективной отладки вам потребуется изучить, что он делает.

    Совет

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

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

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

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

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

В Visual Studio введите режим отладки с помощью F5 (или> команды меню "Запуск отладки" или кнопкиIcon showing Start Debugging button. "Начать отладку" на панели инструментов отладки). Если возникает исключение, помощник по исправлению ошибок Visual Studio направит вас к точке его появления и предоставит другую необходимую информацию. См. дополнительные сведения об обработке исключений в коде в разделе Приемы и инструменты отладки.

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

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

Чтобы продемонстрировать это, мы рассмотрим пример кода, который уже содержит несколько ошибок. Здесь мы используем C#, однако функции отладки также применяются для Visual Basic, C++, JavaScript, Python и других поддерживаемых языков. Также предоставлен пример кода для Visual Basic, но снимки экрана приведены для C#.

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

Затем вы создадите приложение с несколькими ошибками.

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

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

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

  2. Откройте Visual Studio.

    На начальном экране выберите Создать проект. Введите консоль в поле поиска, выберите C# или Visual Basic в качестве языка, а затем выберите консольное приложение для .NET. Нажмите кнопку Далее. Введите имя проекта, например ConsoleApp_FirstApp , и нажмите кнопку "Далее".

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

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

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

  3. Откройте файл Program.cs (или Program.vb) и замените все его содержимое по умолчанию следующим кодом. (Сначала выберите вкладку для нужного языка: C# или Visual Basic.)

    using System;
    using System.Collections.Generic;
    
    namespace ConsoleApp_FirstApp
    {
        class Program
        {
            static void Main(string[] args)
            {
                Console.WriteLine("Welcome to Galaxy News!");
                IterateThroughList();
                Console.ReadKey();
            }
    
            private static void IterateThroughList()
            {
                var theGalaxies = new List<Galaxy>
            {
                new Galaxy() { Name="Tadpole", MegaLightYears=400, GalaxyType=new GType('S')},
                new Galaxy() { Name="Pinwheel", MegaLightYears=25, GalaxyType=new GType('S')},
                new Galaxy() { Name="Cartwheel", MegaLightYears=500, GalaxyType=new GType('L')},
                new Galaxy() { Name="Small Magellanic Cloud", MegaLightYears=.2, GalaxyType=new GType('I')},
                new Galaxy() { Name="Andromeda", MegaLightYears=3, GalaxyType=new GType('S')},
                new Galaxy() { Name="Maffei 1", MegaLightYears=11, GalaxyType=new GType('E')}
            };
    
                foreach (Galaxy theGalaxy in theGalaxies)
                {
                    Console.WriteLine(theGalaxy.Name + "  " + theGalaxy.MegaLightYears + ",  " + theGalaxy.GalaxyType);
                }
    
                // Expected Output:
                //  Tadpole  400,  Spiral
                //  Pinwheel  25,  Spiral
                //  Cartwheel, 500,  Lenticular
                //  Small Magellanic Cloud .2,  Irregular
                //  Andromeda  3,  Spiral
                //  Maffei 1,  11,  Elliptical
            }
        }
    
        public class Galaxy
        {
            public string Name { get; set; }
    
            public double MegaLightYears { get; set; }
            public object GalaxyType { get; set; }
    
        }
    
        public class GType
        {
            public GType(char type)
            {
                switch(type)
                {
                    case 'S':
                        MyGType = Type.Spiral;
                        break;
                    case 'E':
                        MyGType = Type.Elliptical;
                        break;
                    case 'l':
                        MyGType = Type.Irregular;
                        break;
                    case 'L':
                        MyGType = Type.Lenticular;
                        break;
                    default:
                        break;
                }
            }
            public object MyGType { get; set; }
            private enum Type { Spiral, Elliptical, Irregular, Lenticular}
        }
    }
    

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

    название галактики, расстояние, тип галактики.

Выполнить приложение

Нажмите клавишу F5 или кнопкуIcon showing Start Debugging button. "Начать отладку" на панели инструментов отладки, расположенной над редактором кода.

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

Tadpole  400,  Spiral
Pinwheel  25,  Spiral
Cartwheel, 500,  Lenticular
Small Magellanic Cloud .2,  Irregular
Andromeda  3,  Spiral
Maffei 1,  Elliptical

Но вместо этого вы увидите следующие выходные данные:

Tadpole  400,  ConsoleApp_FirstApp.GType
Pinwheel  25,  ConsoleApp_FirstApp.GType
Cartwheel, 500,  ConsoleApp_FirstApp.GType
Small Magellanic Cloud .2,  ConsoleApp_FirstApp.GType
Andromeda  3,  ConsoleApp_FirstApp.GType
Maffei 1, 11,  ConsoleApp_FirstApp.GType

По выходным данным и коду мы видим, что GType — это имя класса, содержащего тип галактики. Но мы пытаемся отобразить сам тип (например, Spiral, то есть спиральная), а не имя класса!

Отладка приложения

  1. Пока приложение еще работает, вставьте точку останова.

    Щелкните правой кнопкой мыши рядом с Console.WriteLine методом, чтобы получить контекстное меню и выберите точку останова вставить точку> останова в всплывающем меню.

    foreach (Galaxy theGalaxy in theGalaxies)
    {
        Console.WriteLine(theGalaxy.Name + "  " + theGalaxy.MegaLightYears + ",  " + theGalaxy.GalaxyType);
    }
    

    В месте установки точки останова в левом поле появится красный круг.

    При возникновении проблемы в выходных данных вы начинаете отладку, просматривая предыдущий код, который задает выходные данные в отладчике.

  2. Нажмите кнопку "ПерезапуститьIcon showing RestartApp button in Debug toolbar." на панели инструментов отладки (CTRL + SHIFT + F5).

    Выполнение приложения приостановится в заданной точке останова. Место приостановки отладчика будет выделено желтым цветом (при этом желтая строка кода еще не выполнена).

  3. Наведите указатель мыши на переменную GalaxyType справа. После этого разверните theGalaxy.GalaxyType слева от значка гаечного ключа. Как вы можете видеть, GalaxyType содержит свойство MyGType, которому присваивается значение Spiral.

    Screenshot of the Visual Studio Debugger with a line of code in yellow and a menu open below the Galaxy GalaxyType property.

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

  4. Во время отладки этого же кода установите указатель мыши в конец элемента theGalaxy.GalaxyType и измените его на theGalaxy.GalaxyType.MyGType. Хотя вы можете внести изменения, редактор кода показывает ошибку, указывающую, что он не может скомпилировать этот код. (В Visual Basic ошибка не отображается, и этот раздел кода работает.)

  5. Нажмите клавишу F11 (Шаг отладки>или кнопка "Шаг в шаг" на панели инструментов отладки), чтобы выполнить текущую строку кода.

    При нажатии клавиши F11 отладчик переходит на одну инструкцию вперед и выполняет соответствующий код. F10 (Step Over) — это аналогичная команда, и оба полезны при обучении использованию отладчика.

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

    Screenshot of the Visual Studio Debugger with a line of code highlighted in red and a message box with the Edit option selected.

    Примечание.

    Для отладки кода примера Visual Basic пропустите следующие несколько шагов, пока не будет указано, как нажать кнопку "ПерезапуститьIcon showing Restart app button in Debug toolbar.".

  6. Выберите "Изменить" в окне сообщения "Изменить и продолжить". Сообщение об ошибке будет выведено в окне Список ошибок. Эта ошибка указывает, что 'object' не содержит определение для MyGType.

    Screenshot of the Visual Studio Debugger with a line of code highlighted in red and an Error List window with two errors listed.

    Хотя мы задаем каждую галактику как объект типа GType (имеющий свойство MyGType), отладчик не считает theGalaxy объектом типа GType. Почему? В этом случае необходимо проверить код, который задает тип галактики. Вы увидите, что класс GType содержит свойство MyGType, однако все же что-то работает не так. Разгадка кроется в сообщении об ошибке для object. Интерпретатор языка воспринимает его как объект object вместо ожидаемого типа GType.

  7. Анализируя код, в котором задается тип галактики, вы можете увидеть, что свойство GalaxyType класса Galaxy задается как object вместо GType.

    public object GalaxyType { get; set; }
    
  8. Измените предыдущий код следующим образом:

    public GType GalaxyType { get; set; }
    
  9. Нажмите кнопку "ПерезапуститьIcon showing Restart app button in Debug toolbar." на панели инструментов отладки (CTRL + SHIFT + F5), чтобы перекомпилировать код и перезапустить.

    Когда отладчик приостановит выполнение на строке Console.WriteLine, вы можете навести указатель мыши на theGalaxy.GalaxyType.MyGType и убедиться, что значение задано правильно.

  10. Удалите точку останова, щелкнув ее кружок в левом поле (также для этого можно выбрать команду Точка останова>Удалить точку останова). После этого нажмите клавишу F5 для продолжения.

    Приложение запускается и отображает выходные данные. Это выглядит хорошо, но вы заметили одно. Вы ожидали, что небольшая галактика Magellanic Cloud будет отображаться как нерегулярная галактика в выходных данных консоли, но она не показывает вообще типа галактики.

    Tadpole  400,  Spiral
    Pinwheel  25,  Spiral
    Cartwheel, 500,  Lenticular
    Small Magellanic Cloud .2,
    Andromeda  3,  Spiral
    Maffei 1,  Elliptical
    
  11. Установите точку останова на этой строке кода перед инструкцией switch (инструкцией Select в Visual Basic).

    public GType(char type)
    

    Здесь задается тип галактики, поэтому нам необходимо изучить эту строку более пристально.

  12. Нажмите кнопку "ПерезапуститьIcon showing Restart app button in Debug toolbar." на панели инструментов отладки (CTRL + SHIFT + F5), чтобы перезапустить.

    Отладчик приостановит работу в строке кода, где вы задали точку останова.

  13. Наведите указатель мыши на переменную type. Отображается значение S (после кода символа). Вас интересует значение I, поскольку эта галактика должна иметь тип Irregular (неправильная).

  14. Нажмите клавишу F5 и снова наведите указатель мыши на переменную type. Повторяйте этот шаг, пока в переменной type не появится значение I.

    Screenshot of the Visual Studio Debugger with a line of code in yellow and a window with the type variable value of 73 I.

  15. Теперь нажмите клавишу F11 (шаг отладки>).

  16. Нажимайте клавишу F11 до тех пор, пока вы не остановитесь в строке кода с инструкцией switch для значения "I" (инструкция Select для Visual Basic). Здесь вы увидите очевидную ошибку, связанную с опечаткой. Вы ожидали, что код будет выполнен дальше до места, где для MyGType задается тип галактики Irregular, однако вместо этого отладчик полностью пропускает этот код и приостанавливает работу в разделе default инструкции switch (инструкция Else для Visual Basic).

    Screenshot showing the typo error.

    Взглянув на код, вы заметите опечатку в инструкции case 'l'. Оно должно иметь значение case 'I'.

  17. Выберите в коде и case 'l' замените его case 'I'на .

  18. Удалите точку останова и нажмите кнопку "Перезапустить ", чтобы перезапустить приложение.

    Теперь все ошибки исправлены и приложение выдает ожидаемые результаты.

    Нажмите любую клавишу, чтобы завершить работу приложения.

Итоги

Если вы сталкиваетесь с проблемой, воспользуйтесь отладчиком и командами пошагового выполнения, такими как F10 и F11, для поиска области кода, в которой возникают ошибки.

Примечание.

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

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

  • Проверьте переменные и убедитесь, что они содержат значения того типа, который вы ожидаете. Если переменная содержит недопустимое значение, найдите, где оно было задано. Для этого может потребоваться перезапустить отладчик, проверить стек вызовов или выполнить одновременно оба этих действия.

  • Проверьте, выполняет ли ваше приложение код, который вы ожидаете. (Так, в примере приложения должна была выполняться инструкция switch, задающая тип галактики Irregular, однако нужный код был пропущен из-за опечатки.)

Совет

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

Следующие шаги

Из этой статьи вы узнали общие принципы отладки приложений. Теперь вы можете приступить к изучению других возможностей отладчика.