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>.
May 22nd, 2011 on 23:08
Bardzo fajny opis :) Świetna rzecz o której nie mówi się często. Jestem mile zaskoczony że istnieje taki mechanizm :)