3.5 – Еще тактики отладки

Добавлено 17 апреля 2021 в 14:27

На предыдущем уроке (3.4 – Базовые тактики отладки) мы начали изучать, как вручную отлаживать проблемы. В том уроке мы высказали некоторую критику об использовании инструкций для печати отладочного текста:

  1. отладочные инструкции загромождают ваш код;
  2. отладочные инструкции загромождают вывод вашей программы;
  3. отладочные инструкции требуют модификации вашего кода как для добавления, так и для удаления, что может привести к появлению новых ошибок;
  4. отладочные инструкции должны быть удалены после того, как вы закончите работу с ними, что делает их непригодными для повторного использования.

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

Добавление условий к отладочному коду

Рассмотрим следующую программу, которая содержит несколько отладочных инструкций:

#include <iostream>
 
int getUserInput()
{
std::cerr << "getUserInput() called\n";
	std::cout << "Enter a number: ";
	int x{};
	std::cin >> x;
	return x;
}
 
int main()
{
std::cerr << "main() called\n";
    int x{ getUserInput() };
    std::cout << "You entered: " << x;
 
    return 0;
}

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

Один из способов упростить отключение и включение отладки во всей программе – сделать инструкции отладки условными с помощью директив препроцессора:

#include <iostream>
 
#define ENABLE_DEBUG // закомментируйте, чтобы выключить отладку
 
int getUserInput()
{
#ifdef ENABLE_DEBUG
std::cerr << "getUserInput() called\n";
#endif
	std::cout << "Enter a number: ";
	int x{};
	std::cin >> x;
	return x;
}
 
int main()
{
#ifdef ENABLE_DEBUG
std::cerr << "main() called\n";
#endif
    int x{ getUserInput() };
    std::cout << "You entered: " << x;
 
    return 0;
}

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

Это решает проблему с необходимостью удаления инструкций отладки и связанные с этим риски, но за счет еще большего загромождения кода. Другой недостаток этого подхода заключается в том, что если вы допустили опечатку (например, неправильно написали «DEBUG») или забыли включить заголовок в файл исходного кода, часть или вся отладка этого файла может быть не включена. Таким образом, хотя это лучше, чем версия без условий, но всё же есть возможности для улучшения.

Использование логгера

Альтернативный подход к условной отладке через препроцессор – отправить отладочную информацию в файл журнала (лог-файл, log file). Лог-файл – это файл (обычно хранящийся на диске), в котором записываются события, происходящие в программном обеспечении. Процесс записи информации в лог-файл называется логгированием. Большинство приложений и операционных систем записывают лог-файлы, которые можно использовать для диагностики возникающих проблем.

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

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

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

#include <iostream>
#include <plog/Log.h> // Шаг 1: включить заголовок логгера
 
int getUserInput()
{
	LOGD << "getUserInput() called"; // LOGD определяется библиотекой plog
 
	std::cout << "Enter a number: ";
	int x{};
	std::cin >> x;
	return x;
}
 
int main()
{
	plog::init(plog::debug, "Logfile.txt"); // Шаг 2: инициализировать логгер
 
	LOGD << "main() called"; // Шаг 3: вывод в лог, как если бы вы писали в консоль
 
	int x{ getUserInput() };
	std::cout << "You entered: " << x;
 
	return 0;
}

Вот результат работы показанного выше логгера (в файле Logfile.txt):

2018-12-26 20:03:33.295 DEBUG [4752] [main@14] main() called
2018-12-26 20:03:33.296 DEBUG [4752] [getUserInput@4] getUserInput() called

То, как вы включаете, инициализируете и используете логгер, будет зависеть от выбранного вами логгера.

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

// plog::none исключает запись большинства сообщений, по сути, отключая логирование
plog::init(plog::none , "Logfile.txt");

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

В качестве отступления...


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

Сначала получите последнюю версию plog:

  • посетите репозиторий plog;
  • вверху справа нажмите кнопку «Code» и выберите «Download ZIP», чтобы скачать последнюю версию.

Затем распакуйте весь архив в <любое место> на жестком диске.

Наконец, для каждого проекта установите каталог <anywhere>\plog-<version>\include\ в качестве включаемого каталога (include directory) в вашей IDE. Инструкции о том, как это сделать для Visual Studio и Code::Blocks приведены здесь, в конце статьи «2.10 – Заголовочные файлы».

Теги

C++ / CppDebugLearnCppДля начинающихЛоггер / LoggerОбучениеОтладкаПрограммирование

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

В случае комментирования в качестве гостя (без регистрации на disqus.com) для публикации комментария требуется время на премодерацию.