#028 - Coding standard/wykrywanie stylu
Użycie “ClangFormat” oraz “clangFormat detector”
Wpis miał na celu przedstawienie narzędzia “StyleDetector” - jednak stwierdziłem, że wyjaśnie trochę całą otoczkę z CodingStandardami, autoformatterami a “StyleDetector” przedstawię na samym końcu - więc jeśli interesuje Cię tylko to narzędzie - przeskocz na koniec wpisu.
Coding standard - w dużym skrócie co to?
Jest to dokument opisujący jak ma wyglądać wyprodukowany w firmie kod pod względem stylistycznym np.
- układ klamr
- białe znaki po słowach kluczowych/wyrażeniach arytmetycznych
- styl nazewnictwa funkcji/zmiennych (np. camel-case itp)
- zasady odnośnie “early-return”, jednego punktu wyjścia z funkcji itp.
Jest to bardzo “niskopoziomowy” dokument, który moim zdaniem jest obowiązkowy - dzięki niemu (oczywiście o ile jest przestrzegany - to inna kwestia) cała baza kodu jest “spójna” - pracowałem już w projektach bez niego, gdzie po zerknięciu na funkcję można było stwierdzić który developer to napisał - zdecydowanie tak nie powinno być.
Jednym z plusów trzymania się wyznaczonego CSa jest, a może bardziej problemem w przypadku jego braku - etap mergowania zmian w kodzie wprowadzonych przez różnych developerów. Jeśli mają oni np. różny styl klamr często będą “walczyć ze sobą” poprawiając otoczenie swojej poprawki dostosowując je do swojego stylu - zdecydowanie utrudnia to mergowanie oraz ma dodatkową wadę - łamie zasadę SRP dla commitów - nie powinno się dotykać kodu, który nie jest powiązany z naszą zmianą.
Wspólny CS ułatwia i maintenance i wcześniejszą fazę review - na niej powinno się wyłapywać niezgodności - dużo osób twierdzi, że review nie jest od wyłapywania stylistycznych błędów, a nawet jeśli to wchodzi w review to nie wyłapie się wszystkich - i w pełni się zgadzam, dlatego obowiązkowo należy korzystać tooli do tego! Wtedy więcej czasu w review mamy na sprawdzenie, czy dodany kod spełnia requirementy odnośnie nowej funkcjonalności (o ile takie są…).
Co w przypadku “legacy” - czy jeśli nagle w firmie wprowadzamy tool do automatycznego formatowania to należy przepuścić przez niego cały kod? Moim zdaniem tak - jeśli mówimy tu o automatycznym narzędziu nie zepsuje ono logiki kodu. Jest jeden bardzo dobry argument przeciw - utracimy wtedy dostępną w łatwy sposób historię zmian z “git blame” - będą one przykryte dodatkowym commitem. Trzeba taką decyzję podjąć całym zespołem, ale 2 rzeczy, które mogą zbić ten argument to:
- czy faktycznie informacje w commitach są użyteczne w tym projekcie - skoro nie było nawet CSa itp? czy była trzymana zasada SRP dla commitów?
- “git blame” oferuje opcję do pominięcia zmian, które dotyczą tylko znaków białych - więc przegląd najnowszych zmian jest dalej “łatwy” (nawiasem mogę polecić aplikację, która ma świetny przegląd historii zmian plików - gitAhead - jest to kombajn jak sourceTree itp - ale ja z niego korzystam tylko do tej jednej opcji).
CodingStandard - jego definiowanie często przeradza się w niepotrzebną “gównoburzę”. Wiele kwestii/decyzji nie ma znaczącej przewagi nad innymi i sprowadza się do osobistych preferencji. Ustanowienie CS dla firmy to praca dla całego zespołu programistów i spisanie ustaleń, które często są kompromisami.
Miałem gdzieś zapisany piękny obrazek odnośnie “spornych” rzeczy w projekcie - jak często/łatwo można je znaleźć w projekcie i jak są ważne, ale totalnie nie mogę znaleźć - TODO wkleić po znalezieniu.
Formattery kodu
Na rynku dostępnych jest wiele autoformatterów zaczynając od tych wbudowanych w IDE po typowo konsolowe narzędzia umożliwiające też podpięcie do dowolnego IDE - nie liczę tu tooli “online” gdzie trzeba przekleić kod - to nie jest do zastowowań komercyjnych w dużych projektach.
Prawdopodobnie najpopularniejszy dla C/C++ ostatnio jest “ClangFormat” - https://clang.llvm.org/docs/ClangFormat.html.
W skrócie jego uruchomienie polega na podaniu pliku do sformatowania oraz pliku zawierającego opis stylu (istnieją też wbudowane style itp).
Tworzenie pliku stylu
W internecie można też znaleźć “generatory” pliku styli - udostępniają one wszystkie dostępne opcje jako checkboxy/comboboxy i prezentują live wynik formatowania - przykład takiej strony: https://zed0.co.uk/clang-format-configurator/.
Występuje tutaj jeden problem - co jeśli już w firmie funkcjonuje CodingStandard, ale chcemy go zautomatyzować przez ClangFormat - należy wtedy dobrać wszystkie parametry, aby odzwierciedlały nasze ustawienia. A czy można zrobić odwrotnie - wygenerować plik stylu na podstawie już istniejącego kodu?
Może się to przydać w przypadku zasad, które ciężko zmapować na reguły z ClangFormat lub są tak naprawdę zlepkiem kilku ustawień w odpoweiedniej konfiguracji.
Szukałem już takiego rozwiązania kilka lat temu, ale dopiero niedawno po odkopaniu tematu znalazłem bardzo fajne rozwiązanie - jest to aplikacja “Clang-Format Editor”, której jedną z funkcjonalności jest “Style Detector”.
Jak użyć “Style Detector”?
Aplikacja jest dostępna tutaj: https://www.clangpowertools.com/clang-format-editor.html.
Jej dokumentacja z pełnym procesem oraz poradnikiem video: https://www.clangpowertools.com/blog/getting-started-with-clang-format-detector.html.
Przykład znalezionych zasad dla mojego przykładu:
Nie będę więc dublował tego opisu, ale przedstawię moje porady odnośnie jego używania:
- należy podać kilka/kilkanaście plików do analizy - wtedy więcej specyficznych reguł będzie wykrytych
- MEGA WAŻNE - należy zadbać, aby podane przykłady były “spójne” jeśli w niektórych plikach jest spacja po “if” a w innych nie aplikacja wyświetli o tym informację, ale do finalnego pliku wpisze przypadek, który wykryła najczęściej
- jeśli dobrze zadbamy o zestaw plików wejściowych, ale aplikacja nie daje w outpucie dokładnie takiego samego pliku jak podajemy (tzn podświetla zmiany w diffie) - na 99% tego nie przeskoczymy. W moim przypadku była to zasada spacji po klamrze zamykającej typedefy ze strukturami - finalnie podjęliśmy decyzję, że dostosujemy się w tej kwestii do narzędzia.
- warto zapisać nawet w osobne repo pliki, które były źródłem, aby w przypadku wprowadzania zmian łatwo na nich przetestować nową konfigurację - zawsze może się okazać, że jakiegoś przypadku nie przewidzieliśmy w CSie i należy to przegenerować - moje przykłady znajdują się tutaj: https://gitlab.com/embedownik/clangformatterexample
- kody do wrzucenia nie muszą być “produkcyjne” - można utworzyć “fejkowego” main.c i utworzyć tam przykładowe funkcje, struktury, typedefy, wielowymiarowe tablice w takim stylu jaki chcemy osiągnąć