Na tej stronie zebrałem narzędzia i techniki ułatwiające pracę w projektach embedded. Ich stosowanie może wyraźnie podnieść naszą produktywność, a także jakość naszych rozwiązań i przyjemność z pracy. Omawiane kategorie to:

  • Kontrola wersji
  • Jakość kodu
  • System buildowania
  • Dokumentacja
  • Komunikacja

Doskonale zdaję sobie sprawę, że nie każdy projekt embedded stosuje wszystkie opisane tutaj elementy. Istnieje pewnie nawet taka część, która nie stosuje żadnego z nich. Jeżeli wymienione techniki i narzędzia Ci się spodobają – pamiętaj, aby nie wprowadzać wszystkich naraz. Wybierz sobie jedną i daj sobie kilka tygodni na jej opanowanie.

Wybierając techniki do swojego projektu musisz też pamiętać o jego złożoności, wielkości, ilości osób, które będą nad nim pracować, czy czasu jaki masz na jego realizację. Jeżeli projekt jest prosty, albo pracujesz nad nim sam – wybierz tylko te elementy, które dadzą Ci największą wartość.

Kontrola wersji

Kontrola wersji zwykle kojarzy nam się z narzędziem do wersjonowania zmian w kodzie. Tutaj wybór jest prosty – najlepiej używać Gita. Jeżeli w swoim projekcie używasz dalej SVNa, zawsze możesz wykorzystywać gita lokalnie.

Jednak wybranie Gita jako narzędzia kontroli wersji nie wyczerpuje całego tematu. Trzeba go jeszcze odpowiednio używać:

Kontrola wersji to też nadawanie odpowiednich numerów oficjalnych wersji. W tym celu najlepiej użyć semver (Semantic Versioning). Sytuacja nieco się kompiluje jeżeli mamy więcej niż jedną binarkę w projekcie. W takim wypadku powinniśmy stosować oddzielną numerację zgodną z semver dla każdej binarki i dla każdego PCB. Numer wersji całego produktu powinien również być oddzielny i jasno definiować z których numerów wersji poszczególnych podzespołów się składa.

Mając prosty projekt z jedną binarką zaczynamy od jednego repozytorium na cały projekt. W miarę rozrastania się projektu możemy wydzielić dodatkowe repozytoria na:

  • Kod na kolejne procesory
  • Pliki o dużym rozmiarze

Narzędzie do kontroli wersji powinno być zintegrowane z narzędziami do zarządzania projektem i code review. Numery tasków z issue trackera powinny się linkować do commit msg i do merge requestów.

Najważniejsze gałęzie takie jak master i dev powinny być zablokowane na commity developerów. Jedynym sposobem dodania zmian jest przejście procedury code review. W ten sposób uniemożliwiamy dodanie zmiany, która się nie kompiluje.

Polecane materiały

Kurs online Git w Embedded

Narzędzia

Poniższe narzędzia łączą w sobie issue tracker, repo browser, wiki i narzędzie do code review.

Umożliwiają one postawienie narzędzia na lokalnym serwerze. Istnieją jeszcze inne podobne rozwiązania jak np. Gerrit, Redmine, ale są przestarzałe i brakuje im niektórych opcji.

Jakość kodu

Na zapewnienie odpowiedniej jakości kodu w projekcie składa się wiele czynników. Opiszę tutaj kilka najważniejszych

Przyjęte konwencje

Aby egzekwować na developerach dbałość o jakość kodu, najpierw musimy ją zdefiniować dla naszego projektu. Zwykle potrzebujemy do tego dwóch dokumentów:

  • Coding Standard – lista pożądanych i zabronionych praktyk i konstrukcji języka.
  • Style Guide – ujednolica subiektywne kwestie takie jak nawiasy, taby i spacje, sposób nazywania symboli i plików itp.

Coding Standard może opierać się na istniejących standardach takich jak:

Jak najwięcej punktów z Coding Standard i Style Guide powinno być sprawdzane przez automatyczne narzędzia, aby nie musiał się tym zajmować człowiek. Warningi kompilatora i z analizy statycznej powinny być dodawane automatycznie jako komentarze do review.

Analiza Statyczna

Analiza statyczna to proces sprawdzania jakości kodu bez jego uruchamiania. Możemy do tego wykorzystać kompilator i jego warningi albo zewnętrzne narzędzia. Code review również jest formą analizy statycznej, tylko że ręcznej i niezbyt powtarzalnej.

Analizę statyczną dobrze robić wykorzystując kilka narzędzi. Każde z nich lepiej specjalizuje się w wykrywaniu innych typów problemów. Poza tym niektóre bardziej skupiają się na pokazaniu wszystkich możliwych problemów produkując przy okazji więcej false positive’ów. Inne z kolei zgłaszają błąd tylko jeżeli są jego pewne umożliwiając z kolei zignorowane niektórych mniej oczywistych błędów.

Kilka polecanych narzędzi to:

Narzędzia do sprawdzania stylu:

Można również skorzystać z narzędzi do dynamicznej analizy kodu (wymagają odpalenia programu):

Dodatkowe materiały:

Testy

Przede wszystkim polecam kurs online Test Driven Development w Systemach Embedded, który uczy od zera jak korzystać z testów w embedded z perspektywy programisty. Do tego jest tam cała masa ćwiczeń praktycznych i przykładowa implementacja biblioteki Modbus zgodnie z TDD.

O testach (szczególnie unit testach) możesz więcej poczytać na dedykowanej podstronie:

Tutaj się skupię na polecanych narzędziach:

  • Catch2 – biblioteka do testów w C++. Można też używać do kodu produkcyjnego w C. Polecana biblioteka do mocków to Trompeloeil. Dzięki testom w sylu BDD z łatwym pisaniem scenariuszy Given-When-Then to jest aktualnie mój pierwszy wybór.
  • googletest + googlemock – również biblioteka w C++, którą jednak można wykorzystywać do kodu produkcyjnego w C.
  • Unity + FFF – rozwiązanie w całości w C. FFF to proste stuby stworzone na makrach bez zaawansowanych opcji. Jednak w 99% przypadków powinny być zupełnie wystarczające.

Materiały:

Metryki

Metryki to wartości liczbowe, które przekazują jakąś informację o naszym projekcie. Mogą być przydatne do śledzenia postępów albo monitorowania, czy przekraczamy gdzieś stan alarmowy. Musimy jednak pamiętać, że to tylko próba wyrażenia bardzo złożonego zagadnienia jakim jest stan projektu programistycznego za pomocą prostych liczb. Mogą one nie przekazywać pełnej informacji albo zostać błędnie zinterpretowane. Stosowane poprawnie mogą jednak dawać szybką informację o postępach. Jedną z często stosowanych metryk jest pokrycie kodu testami. Innymi przydatnymi wskaźnikami mogą być czas builda, czas wykonywania testów, czy złożoność cyklomatyczna.

Pożyteczne narzędzia to:

  • Lizard – do obliczania złożoności cyklomatycznej
  • gcov – narzędzie do badania pokrycia kodu testami wchodzące w skład gcc

O tym jak wykorzystywać metryki możesz posłuchać więcej tutaj:

Code review

Code review to proces sprawdzenia zmian w kodzie przed dodaniem ich do głównej gałęzi. Każda zmiana przed dołączeniem powinna być sprawdzona przez inną osobę. Dobrą praktyką jest sprawdzenie każdej zmiany przez dwóch recenzentów. Wtedy w przypadku sytuacji spornej między autorem a jednym z recenzentów mamy trzecią osobę, która może rozstrzygnąć spór.

Każde nowe review powinno zawierać checklistę z zadaniami do wykonania przez reviewerów. Powinna ona zawierać zadania do wykonania oraz najczęstsze błędy, na które uwagę musi zwrócić recenzent. Checklista powinna być uaktualniana w trakcie projektu.

Przed oddaniem review w ręce recenzentów, autor powinien najpierw sam przejrzeć wszystkie pliki i sprawdzić, czy nie ma błędów, czy nie zapomniał niczego scommitować itp. Może też dodać swoje komentarze ułatwiające review albo tłumaczące jego decyzje.

Sprawdzenie poprawności i jakości testów jest równoważną częścią code review. Recenzent musi się upewnić, że testy się kompilują, przechodzą, sprawdzają pożądane działanie, są napisane w czytelny sposób i zapewniają odpowiednie pokrycie.

Dokumentacja również podlega code review. Nie można akceptować zmiany, która nie została opisana w wymaganiach i w dokumentach opisujących projekt systemu. Sprawdzamy również przydatność dokumentacji, czy jest jednoznaczna i wystarczająco szczegółowa.

Nie można akceptować review jeżeli nie przejdzie kompilacja i testy na serwerze Continuous Integration.

Dodatkowe materiały

Build system

Na build system składają się narzędzia do budowania projektu lokalnie na maszynie developerskiej oraz serwer Continuous Integration.

Continuous Integration

Narzędzie do Continuous Integration powinno kompilować kod produkcyjny we wszystkich używanych wersjach. Mogą one różnić się kompilatorem, flagami kompilacji, definami docelowym sprzętem itp. Każda konfiguracja powinna być również sprawdzona przy pomocy narzędzi do analizy statycznej.

Kod w każdej konfiguracji powinien być sprawdzony za pomocą unit testów, do których CI generuje raporty i test coverage. Część testów automatycznych na docelowy sprzęt jest wykonywana w ramach smoke testów, z których również generowane są raporty. Pozostałe testy HW są wykonywane w ramach normalnego pakietu testów.

Podczas builda generowane są również metryki takie jak np. code complexity. Wynikiem buildu powinny być oficjalne binarki składowane na serwerze. To te binarki powinny być wgrywane na docelowe urządzenia, przekazywane klientom i wykorzystywane do testów.

Pełny pakiet testów może trwać kilka godzin i warto go wykonywać podczas nocnego buildu dla najważniejszych gałęzi takich jak master, dev czy release. Każdy merge request powinien być buildowany zaraz po wrzuceniu na serwer gita. Można w nich wykorzystać mniejszy pakiet testów – na przykład same unit testy i smoke testy.

Polecane narzędzia

  • CMake – build system ułatwiający portowalność i zarządzanie buildem. Za jego pomocą można generować pliki dla wielu systemów takich jak make, ninja, visual studio.
  • Ninja – alternatywa dla make charakteryzująca się dużo krótszym czasem buildowania.
  • Conan – package manager – rozwiązanie problemu zależności od zewnętrznych bibliotek, czy subrepozytoriów ze współdzielonym kodem.
  • Artifactory – narzędzie do składowania skompilowanych binarek w różnych wersjach. Płatne.
  • Jenkins – środowisko Continuous Integration do postawienia na firmowym serwerze.
  • Travis – środowisko Continuous Integration w chmurze, które można podpiąć do projektów na GitHubie, GitLabie, czy BitBucket. Przy projektach embedded może być trochę problemów.

Dodatkowe materiały

Prezentacja o Conan z Gdańsk Embedded Meetup.

Dokumentacja

Dokumentacji nie odkładamy na koniec! Opisujemy decyzje projektowe i działanie systemu w momencie pisania funkcjonalności. Wtedy mamy na ten temat największą wiedzę i pamiętamy najwięcej szczegółów.

Mamy trzy główne rodzaje dokumentacji:

  • użytkowa – w jaki sposób korzystać z naszego systemu. Jak aktywować poszczególne funkcje, jakich zachowań się spodziewamy.
  • techniczna – opis produktu, wymagania, architektura aplikacji i niskopoziomowy projekt poszczególnych elementów.
  • Tips & Tricks – dla osób pracujących nad projektem. Zawiera instrukcje obsługi wykorzystywanych narzędzi, jak konfigurować itp.

Poza tym mamy dokumentację zewnętrzną taką jak noty katalogowe wykorzystywanych układów, schematy elektryczne, mechaniczne, specyfikacje protokołów komunikacyjnych itp.

Najlepiej trzymać dokumentację w formie tekstowej w repozytorium. Można wykorzystać format Markdown i narzędzia do generowania diagramów z tekstu.

Część dokumentacji może również być trzymana na wiki projektu. Plusem wiki jest możliwość dostępu osób nietechnicznych, które boją się używać gita.

Techniki przydatne przy prowadzeniu dokumentacji:

  • ADR – Architecture Decision Record – dokumentowanie decyzji architektonicznych. Można ten sam format wykorzystać również do innych decyzji np. zarządzania ryzykiem.
  • Model C4 – służy do opisu architektury na wielu poziomach. Zaczynając od otoczenia w jakim nasz system pracuje i z kim wchodzi w interakcję, a kończąc na warstwach, modułach, klasach i diagramach aktywności.
  • Event Storming – technika służąca do odkrywania złożoności projektu i procesów występujących w naszej domenie.

Polecane materiały

Komunikacja

Nawet najlepsze narzędzia i techniki nic nie dadzą, jeżeli w naszym teamie komunikacja nie będzie przebiegać sprawnie. Musimy mówić do siebie w zrozumiały sposób, używać narzędzi, które nie podkopują naszej produktywności i nie skakać sobie do gardeł.

Wspólny język

Ubiquitous language, czyli język wszechobecny, albo po prostu wspólny język, to pojęcie używane w DDD (Domain Driven Design), ale można je zaadaptować do projektu nawet w oderwaniu od całej reszty praktyk DDD. Chodzi po prostu o to, żeby wszystkie osoby w zespole używały tego samego języka. Żeby developerzy byli w stanie porozumieć się z ekspertami domenowymi. Żeby nazwy używane w kodzie były takie same jak nazwy używane przez osoby nieznające się na programowaniu. Dzięki temu nie musimy tłumaczyć pojęć i łatwiej nam współpracować. Abyśmy byli w stanie to osiągnąć należy prowadzić słownik pojęć zawierający odpowiednie wytłumaczenia. Już samo to może być niesamowitą wartością.

Slack

Kolejną kwestią są narzędzia do codziennej komunikacji. Zwykle używamy maili, messengerów, robimy meetingi i rozmawiamy przy biurku w razie potrzeby. Jednak w związku z rosnącą popularnością pracy zdalnej w IT i podejścia remote asynch coraz większą popularność zdobywa Slack.

Jest to kolejna inkarnacja popularnego niegdyś IRCa. Mamy do dyspozycji kanały tematyczne, wiadomości bezpośrednie i integracje z różnymi serwisami. Dzięki Slackowi możemy informować zainteresowane osoby o postępach prac i prosić ich o pomoc. Inni mogą subskrybować tylko te kanały, które ich interesują i reagować na wiadomości wtedy kiedy mają czas – dzięki temu nie wychodzą ze stanu flow. Można w ten sposób też realizować również daily standupy.

Dodatkowe materiały

Do poczytania w temacie pracy asynchronicznej i zdalnej:

The art of async

Overcommunication is required for async

Komunikacja międzyludzka

Często porady dotyczące komunikacji są dla mnie odstraszające, bo skupiają się na tanich chwytach, manipulacji i udawaniu zainteresowania. Na szczęście nie musimy stosować tych wątpliwych moralnie sztuczek, aby dobrze dogadywać się z ludźmi. Wystarczy po prostu nie być chamem i traktować rozmówców z szacunkiem. Doskonałym wprowadzeniem do zasad komunikacji międzyludzkiej jest poniższa prezentacja.