Język C++ udostępnia makro assert, które można bardzo łatwo włączać i wyłączać. Zobaczmy, jak tego używać!

Dwie fazy w życiu aplikacji

Aplikacja przechodzi w swoim życiu fazę testową i produkcyjną. W fazie testowej aplikacja powinna być jak najbardziej wrażliwa na wszelkiego rodzaju błędy, tak, by łatwo było je wychwycić i poprawić. W fazie produkcyjne zaś powinna być jak najbardziej na błędy odporna  – tak, by nie denerwować użytkownika.

Faza testowa – włączamy asercje

Mamy prosty programik, który drukuje imię i nazwisko Użytkownika:

#include <cassert>
#include <iostream>
using namespace std;

struct User
{
	string fname;
	string lname;
};

void printUser(const User *user)
{
	assert(NULL != user); // najpierw sprawdzamy poprawność danych
	cout << user->fname << " " << user->lname; // potem drukujemy
}

Zakładamy, że funkcja printUser nie może być wywoływana z parametrem NULL. Jeśli tak się stanie, oznacza to, że coś w programie nie gra!

Ponieważ funkcja printUser sprawdza poprawność podanego parametru, wywołanie jej z parametrem NULL spowoduje natychmiastowe zatrzymanie aplikacji i wyświetlenie komunikatu:

cpp: main.cpp:50: void printUser(const User*): Assertion `__null != user' failed.
Aborted

Z komunikatu wynika, że powodem zatrzymania aplikacji była niespełniona asercja w pliku main.cpp w linii 50. W ten oto sposób funkcja sama się debuguje – sprawdza poprawność parametrów i raportuje o błędzie.

Faza produkcyjna – wyłączamy asercje

Zeby wyłączyć asercje w kodzie produkcyjnym, dodajemy linijkę #define NDEBUG przed załączeniem nagłówka <cassert>:

#define NDEBUG // wyłączamy makro assert
#include <cassert>
#include <iostream>
using namespace std;

struct User
{
	string fname;
	string lname;
};

void printUser(const User *user)
{
	assert(NULL != user); // najpierw sprawdzamy poprawność danych
	cout << user->fname << " " << user->lname; // potem drukujemy
}

Drugi sposób, wygodniejszy, to skompilować program z odpowiednimi opcjami:

g++ -D NDEBUG -o program program.cpp.

Teraz wywołanie funkcji printUser(NULL) nie spowoduje już sprawdzenia asercji. Dostaniemy niewiele mówiący błąd:

Segmentation fault

Zauważ, że asercja nadal jest w kodzie! Jedyne co zrobiliśmy, to wyłączyliśmy ją dodając linijkę #define NDEBUG.

Czego nie robić z assert

Ważna rzecz – przeczytaj, żeby się później nie zdziwić.

Lepiej nie kombinować ze wstawianiem wywołań funkcji do makra assert. Przykładowo, taka konstrukcja nie jest zbyt dobrym pomysłem:

void setup()
{
	assert(true == loadConfigFiles());
}

A dlaczego? Ano dlatego, że kiedy zdefiniujemy symbol NDEBUG, prekompilator wyrzuci z kodu asercję razem z całym wywołaniem funkcji loadConfigFiles! A to najpewniej nie jest naszym zamiarem…

Podsumowanie

Asercje pozwalają pisać samodebugujące się aplikacje, co bardzo przydaje się w czasie jej testowania.

Dużą zaletą asercji jest to, że można bardzo wyłączyć je, nie usuwając ich z kodu źródłowego. Dzięki temu asercje nie mają wpływu na wydajność działania ostatecznej wersji programu.

Zeby wyłączyć asercje z kodu produkcyjnego, wystarczy zdefiniować symbol NDEBUG przed załączeniem nagłówka <cassert>.

Related Posts: