3.5 — Więcej taktyk debugowania

W poprzedniej lekcji (3.4 — Podstawowa taktyka debugowania), zaczęliśmy odkrywać, jak ręcznie debugować problemy. Podczas tej lekcji przedstawiliśmy kilka krytycznych uwag dotyczących używania instrukcji do drukowania tekstu debugowania:

  1. Instrukcje debugowania zaśmiecają kod.
  2. Instrukcje debugowania zaśmiecają dane wyjściowe programu.
  3. Instrukcje debugowania wymagają modyfikacji kodu w celu dodania i usunięcia, co może wprowadzić nowe błędy.
  4. Instrukcje debugowania muszą zostać usunięte po ich zakończeniu, co sprawia, że nie nadają się do ponownego użycia.

Możemy złagodzić niektóre z tych problemów. Podczas tej lekcji omówimy kilka podstawowych technik wykonywania tej czynności.

Uwarunkowanie kodu debugowania

Rozważ następujący program zawierający pewne instrukcje debugowania:

#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 << '\n';
 
    return 0;
}

Gdy skończysz z instrukcjami debugowania, będziesz musiał je usunąć lub skomentować. Jeśli później będziesz chciał je ponownie dodać, lub odkomentuj.

Jednym ze sposobów ułatwienia wyłączania i włączania debugowania w całym programie jest uzależnienie instrukcji debugowania za pomocą dyrektyw preprocesora:

#include <iostream>
 
#define ENABLE_DEBUG // comment out to disable debugging

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 << '\n';
 
    return 0;
}

Teraz możemy włączyć debugowanie po prostu poprzez komentowanie/odkomentowanie #define ENABLE_DEBUG. Dzięki temu możemy ponownie wykorzystać wcześniej dodane instrukcje debugowania, a następnie po prostu je wyłączyć, gdy już z nimi skończymy, zamiast konieczności usuwania ich z kodu. Gdyby był to program wieloplikowy, #define ENABLE_DEBUG znalazłoby się w pliku nagłówkowym, który jest dołączany do wszystkich plików kodu, dzięki czemu możemy komentować/odkomentować #define w jednym miejscu i propagować go do wszystkich plików kodu.

Rozwiązuje to problem konieczności usuwania instrukcji debugowania i związanego z tym ryzyka, ale kosztem jeszcze większego bałaganu w kodzie. Inną wadą tego podejścia jest to, że jeśli popełnisz literówkę (np. napiszesz błędnie „DEBUG”) lub zapomnisz dołączyć nagłówek do pliku kodu, część lub całość debugowania dla tego pliku może nie zostać włączona. Więc chociaż jest to lepsze niż wersja bezwarunkowa, nadal jest miejsce na ulepszenia.

Korzystanie z rejestratora

Alternatywnym podejściem do debugowania warunkowego za pośrednictwem preprocesora jest wysyłanie informacji debugowania do dziennika. dziennik to sekwencyjny zapis zdarzeń, które miały miejsce, zwykle opatrzony znacznikiem czasu. Proces generowania logu nazywa się logowaniem. Zazwyczaj dzienniki są zapisywane w pliku na dysku (zwanym plik dziennika), dzięki czemu można je później przeglądać. Większość aplikacji i systemów operacyjnych zapisuje pliki dziennika, które można wykorzystać do diagnozowania występujących problemów.

Pliki dziennika mają kilka zalet. Ponieważ informacje zapisywane w pliku dziennika są oddzielone od danych wyjściowych programu, można uniknąć bałaganu spowodowanego mieszaniem normalnych danych wyjściowych i wyników debugowania. Pliki dziennika można również łatwo wysłać do innych osób w celu diagnozy — więc jeśli ktoś korzystający z Twojego oprogramowania ma problem, możesz poprosić tę osobę o przesłanie pliku dziennika, co może pomóc w wskazaniu, gdzie leży problem.

C++ zawiera strumień wyjściowy o nazwie std::clog , który jest przeznaczony do używania do zapisywania informacji rejestrowania. Jednak domyślnie std::clog zapisuje do standardowego strumienia błędów (tak samo jak std::cerr). I choć zamiast tego możesz przekierować go do pliku, jest to obszar, w którym ogólnie lepiej jest skorzystać z jednego z wielu dostępnych narzędzi do rejestrowania stron trzecich. To, którego użyjesz, zależy od Ciebie.

Dla celów ilustracyjnych pokażemy, jak wygląda wysyłanie danych do rejestratora przy użyciu plog loggera. Plog jest zaimplementowany jako zestaw plików nagłówkowych, więc można go łatwo dołączyć w dowolnym miejscu, a także jest lekki i łatwy w użyciu.

#include <plog/Log.h> // Step 1: include the logger headers
#include <plog/Initializers/RollingFileInitializer.h>
#include <iostream>

int getUserInput()
{
	PLOGD << "getUserInput() called"; // PLOGD is defined by the plog library

	std::cout << "Enter a number: ";
	int x{};
	std::cin >> x;
	return x;
}

int main()
{
	plog::init(plog::debug, "Logfile.txt"); // Step 2: initialize the logger

	PLOGD << "main() called"; // Step 3: Output to the log as if you were writing to the console

	int x{ getUserInput() };
	std::cout << "You entered: " << x << '\n';

	return 0;
}

Oto dane wyjściowe z powyższego rejestratora (w pliku Logfile.txt ):

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

Sposób dołączania, inicjowania i używania rejestratora będzie się różnić w zależności od wybranego konkretnego rejestratora.

Zauważ, że dyrektywy kompilacji warunkowej również nie są wymagane przy użyciu tej metody, ponieważ większość rejestratorów ma metodę ograniczania/eliminowania zapisu danych wyjściowych do dziennika. Dzięki temu kod jest znacznie łatwiejszy do odczytania, ponieważ linie kompilacji warunkowej powodują duży bałagan. W przypadku plog logowanie można tymczasowo wyłączyć, zmieniając instrukcję init na następującą:

	plog::init(plog::none , "Logfile.txt"); // plog::none eliminates writing of most messages, essentially turning logging off

Nie będziemy używać ploga na przyszłych lekcjach, więc nie musisz się martwić jego nauką.

Na marginesie…

Jeśli chcesz samodzielnie skompilować powyższy przykład lub użyć ploga we własnych projektach, możesz postępować zgodnie z poniższymi instrukcjami, aby go zainstalować:

Najpierw pobierz najnowszą wersję plogu:

  • Wejdź na stronę plog repo.
  • Kliknij zielony przycisk Kod w prawym górnym rogu i wybierz „Pobierz zip”

Następnie rozpakuj całe archiwum na somewhere na swoim dysku twardym.

Na koniec dla każdego projektu ustaw katalog somewhere\plog-master\include\ jako include directory w Twoim IDE. Instrukcje, jak to zrobić dla Visual Studio, znajdziesz tutaj: A.2 - Używanie bibliotek w Visual Studio i Code::Blocks tutaj: A.3 - Używanie bibliotek w Code::Blocks. Ponieważ plog nie zawiera prekompilowanego pliku biblioteki, możesz pominąć części związane z prekompilowanymi plikami bibliotek.

Plik dziennika będzie zazwyczaj tworzony w tym samym katalogu, co plik wykonywalny.

Wskazówka

W większych projektach lub wymagających wydajności mogą być preferowane szybsze i bogatsze w funkcje rejestratory, takie jak spdlog.

guest
Twój adres e-mail nie zostanie wyświetlony
Znalazłeś błąd? Zostaw komentarz powyżej!
Komentarze związane z poprawkami zostaną usunięte po przetworzeniu, aby pomóc zmniejszyć bałagan. Dziękujemy za pomoc w ulepszaniu witryny dla wszystkich!
Awatary z https://gravatar.com/ są połączone z podanym adresem e-mail.
Powiadamiaj mnie o odpowiedziach:  
190 Komentarze
Najnowsze
Najstarsze Najczęściej głosowane
Wbudowane opinie
Wyświetl wszystkie komentarze