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:

obr

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ąć