Ostatnio w pracy pojawił się problem - mamy pewną funkcjonalność opartą na identyfikację zasobu poprzez c-string, co wygląda mniej więcej tak:
Resource->Get("Something_Foo");Przy czym filozofia była taka, aby nigdy nie zwracać błędy tylko coś w miarę sensownego (wartości domyślne). Takie podejście do identyfikacji ma zasadnicze wady:
- porównywanie stringów w run time
- łatwo o błędy - literówki, niezgodność wielkości liter
Zaleto-wadą jest też fakt, że nie trzeba definiować wcześniej tych napisów ale nie ma żadnej jednej, długiej listy.
Dlatego też w ramach refaktoryzacji postanowiliśmy zamienić napisy na typ wyliczeniowy (oraz użyć ich do szablonowych wytycznych, ale o tym napiszę w następnym poście).
W ten sposób nie będzie dość kosztownego porównywania napisów, a także możliwa jest weryfikacja poprawności nazwy w czasie kompilacji jak i wygoda dla programisty w postaci podpowiedzi.
enum res_id { Something_Foo, Something_Bar, Something_Foo_Foo }; const* char res_name[] = { "Something_Foo", "Something_Bar", "Something_Foo_Foo" };
Wypisanie tego w ten sposób i utrzymywanie w synchronizacji (biorąc pod uwagę tego, że osoby trzecie mogą zechcieć coś dopisać) nie wchodzi oczywiście w grę.
I tutaj pojawia się technika, którą nazywam "Generic Macro" (może jest na to jakaś inna nazwa?).
Otóż tworzymy plik o przykładowej nazwie resources.def w którym wpisujemy nasze dane wewnątrz jakiegoś w miarę sensownie nazwanego makra:
GENERIC_RESOURCE(Something_Foo) GENERIC_RESOURCE(Something_Bar) GENERIC_RESOURCE(SOmething_Foo_Foo)następnie wystarczy stworzyć plik res_id.hpp w którym zdefiniujemy makro tak jak chcemy a potem dołączymy nasz plik .def :
#ifndef RES_ID_HPP__ #define RES_ID_HPP__ // this will be defined in cpp file extern const char* res_name[]; // include resource.def by using macro to transform it to enum type enum res_id { #define GENERIC_RESOURCE(X) X, #include "resources.def" #undef GENERIC_RESOURCE MaxResId }; #endif //RES_ID_HPP__I teraz w pliku res_names.cpp:
#include "res_id.hpp" const char* res_names[] = { #define GENERIC_RESOURCE(X) (#X), #include "resources.def" #undef GENERIC_RESOURCE "\0" };
I w ten sposób wystarczy modyfikować tylko plik resources.def a zarówno lista napisów jak i typ wyliczeniowy zostaną wygenerowane i będą zsynchronizowane.
Jak widać czasem sprytnie użyte makra mogą przełożyć się na lepszą jakość kodu dzięki zmniejszeniu ryzyka popełnienia błędu :)
Może znajdziesz jakieś inne ciekawe zastosowanie tej techniki?