3 różnice w programowaniu: hobbystycznie vs. komercyjnie

3 różnice w programowaniu: hobbystycznie vs. komercyjnie

W pewnym momencie każdy programista musi przestawić się z hobbystycznego kodowania na bardziej profesjonalne podejście do tematu.

Czym różni się komercyjne pisanie programów na mikrokontrolery od podejścia stosowanego przez hobbystów i studentów? Jakich narzędzi wspomagających warto zacząć używać?

Na studiach lub po godzinach często programujemy coś na własny użytek. Używamy znanego zestawu narzędzi i skupiamy się na tym, aby "działało i przynosiło nam radość". Nie mamy wymagań co do jakości, niezawodności oraz czytelności kodu dla innych. Czego jednak powinniśmy się nauczyć, żeby bez problemu znaleźć pracę w branży i realizować projekty komercyjne?

W wielu komercyjnych projektach embedded wykorzystywane są te same praktyki, co w amatorskich projektach. Bywa to źródłem problemów, opóźnień, stresu i błędów. Dlatego powstało wiele dobrych praktyk, które mają nas uchronić przed tymi kłopotami.

Artykuł zawiera krótkie opisy wielu różnych technik i narzędzi. Tak naprawdę każda z nich mogłaby być osobnym artykułem. Dlatego zawarte tu informacje najlepiej traktować jako punkt startowy do dalszego zgłębiania wiedzy. W wielu miejscach zamieszczam linki, od których można zacząć dalsze poszukiwania. Część z przytoczonych technik poprawi jakość każdego projektu – nawet takiego, który rozwijamy sami po godzinach. Natomiast inne swoje walory pokazują jedynie w długim projekcie prowadzonym przez wiele osób.

Różnica 1: praca w (dużym) zespole

Jedną z kluczowych różnic między projektem studenckim i komercyjnym jest liczba osób zajmujących się projektem. Na studiach zwykle programujemy sami, czasem w dwie lub trzy osoby, maksymalnie w pięć. W pracy zespoły są najczęściej co najmniej kilkuosobowe, czasem przy projekcie pracuje nawet kilkadziesiąt osób podzielonych na mniejsze zespoły.

W takiej sytuacji sprawna komunikacja staje się prawdziwym wyzwaniem. Sytuację utrudnia fakt, że poszczególne osoby mogą pracować zdalnie z innych miast, państw a nawet kontynentów. Jeżeli pracujemy z ludźmi z USA lub Azji nasze godziny pracy się nie pokrywają. Musimy komunikować się pisemnie i rozwiązanie nawet najprostszego problemu może przerodzić się w emailowego ping-ponga.

W projekcie studenckim najczęściej każdy odpowiada za swój fragment kodu i nie jest robiona żadna dokumentacja w trakcie prac. Pisanie sprawozdania, czy pracy dyplomowej zostawiamy sobie na koniec. Czasem planując projekt zrobimy na początku jakąś szczątkową dokumentację, której i tak na bieżąco nie aktualizujemy.

Niestety w wielu komercyjnych projektach sytuacja wygląda podobnie. Zwykle jakaś dokumentacja papierowa jest wymagana i wtedy dodajemy ją po ukończeniu programu. Nie róbmy tak! Wtedy dokumentacja jest albo zbyt ogólnikowa, albo tłumaczy szczegóły implementacyjne kodu, zamiast wymagania i decyzje architektoniczne służące do ich realizacji.

Dokumentację mogą stanowić rysunki, historyjki opisujące podstawowe przypadki użycia (user stories). Informacje te mogą być umieszczone w kodzie (np. w formacie Doxygen), w formie plików tekstowych w repozytorium (ADL – Architecture Decisions Log), a także w wiki projektu albo issue trackerze, który jest dostępny na przykład w projektach na GitHubie.

Dlaczego dokumentacja jest taka ważna? Wiedza musi być przekazywana między członkami zespołu, a projekty komercyjne nieraz trwają całe lata. Poza wspomnieniami wcześniej barierami komunikacyjnymi, trzeba brać pod uwagę, że ludzie chorują, chodzą na urlopy i zmieniają pracę.

Powinniśmy dążyć do tego, aby nasz projekt miał jak najwyższy bus factor, czyli współczynnik, który określa ilu członków zespołu musiałby potrącić autobus, aby projekt nie mógł być kontynuowany ze względu na brak wiedzy o tworzonym systemie.

Różnica 2: sposób pracy z kodem

W amatorskich projektach poszczególne osoby zwykle piszą tak, jak im wygodniej. Każdy ma swoje preferencje nazewnictwa zmiennych, stawiania nawiasów, spacji itp. Kiedy stosujemy taką „wolną amerykankę” w większym projekcie trudno jest to czytać, szukać symboli i pisać nowy kod.

Dlatego w projektach profesjonalnych tworzymy tzw. Coding Standard czyli zbiór reguł dotyczących zarówno formatowania, jak i preferowanych oraz zakazanych konstrukcji języka. Dzięki temu nawet jeśli otwieramy jakiś plik po raz pierwszy, możemy się w nim łatwiej połapać. Najlepiej jeżeli zasady opisane w Coding Standard są dodatkowo możliwe do sprawdzenia (a nawet poprawienia) przez automatyczne narzędzia. Przykładami takich narzędzi do formatowania są clang-format oraz uncruscify. Natomiast narzędzia znajdujące możliwe błędy i niebezpieczne konstrukcje (czyli wykonujące analizę statyczną) to na przykład cppcheck oraz pc-lint.

Przykładowe standardy pisania kodu:

Mimo, że narzędzia do statycznej analizy są coraz lepsze, dalej nie są w stanie wyłapać wszystkich możliwych błędów. Poza tym istnieją pewne problemy na wyższym poziomie abstrakcji jak na przykład zastosowanie nieodpowiedniego algorytmu, czy mylących nazw zmiennych. Błędy tego typu najlepiej wyłapywać podczas Code Review - po napisaniu danej funkcjonalności kod musi zostać sprawdzony przez innego programistę. Dopiero po jego akceptacji może on zostać dodany repozytorium. Dzięki temu mamy możliwość wyłapania pewnych błędów. Poza tym taki mechanizm bardzo dobrze wpływa na szerzenie dobrych praktyk w zespole, wiedzy dotyczącej projektu, a także uczenia nowych osób. Więcej o tej technice można przeczytać w osobnym wpisie opublikowanym na moim blogu.

Jak unikać pułapek języka C?
Jak unikać pułapek języka C?

Dobry programista powinien wiedzieć jakie aspekty języka są niebezpieczne i… Czytaj dalej »

Jednym z kluczowych aspektów podczas pracy z kodem jest kontrola wersji. Istnieją w dalszym ciągu firmy, które korzystają z zipów, pendrive'ów, czy Dropboxa, ale na szczęście ich liczba systematycznie się zmniejsza. W profesjonalnych projektach standardem są narzędzia do kontroli wersji. Tutaj liderem jest zdecydowanie Git.

Sekrety profesjonalnego programowania
Sekrety profesjonalnego programowania

Niniejszy artykuł różni się od większości materiałów dotyczących programowania z… Czytaj dalej »

Jednak samo stosowanie takich narzędzi jeszcze nie jest wystarczające. Należy stosować odpowiednie praktyki umożliwiające łatwą współpracę z zespołem i z historią własnych zmian. O podstawowym zestawie praktyk można przeczytać np. na Git Tower.

W większych projektach standardem tutaj jest Gitflow. W skrócie chodzi o to, aby na głównej gałęzi repozytorium trzymać stabilną wersję systemu, a zmiany dokonywać na tzw. feature-branchach. Pracując na takiej gałęzi wykonujemy często niewielkie zmiany zgodnie z Single Responsibility Principle (tak, ta zasada odnosi się nie tylko do klas i metod w kodzie). Po zakończeniu prac nasz feature-branch musi przejść Code Review zanim zostanie dołączony do głównej gałęzi.

Gitflow opisuje również pracę nad oficjalnymi wersjami (które numerujemy za pomocą innego ogólnie przyjętego standardu np. Semantic Versioning) i dużo innych szczegółów. Więcej informacji na temat ten temat znaleźć można w tym artykule.

Systemy kontroli wersji – Mercurial
Systemy kontroli wersji – Mercurial

Pracowałeś nad większym projektem i bałeś się przystępować do większych… Czytaj dalej »

W projektach hobbystycznych zwykle korzystamy z projektów "wyklikanych" w IDE oraz kodu generowanego automatycznie przez frameworki takie jak np. Cube dla STM, czy Harmony dla układów Microchipa. Korzystamy również z bibliotek producentów takich jak HAL.

Każdy, kto trochę siedzi w temacie mikrokontrolerów na pewno trafił kiedyś na dyskusję czy korzystać z gotowych bibliotek, czy bezpośrednio z rejestrów. Prawda jest taka, że do zastosowań amatorskich biblioteki są całkowicie wystarczające, nie odciągają nas od głównej idei projektu i pozwalają na korzystanie z wielu gotowców. Jednak ich kod zawiera błędy, nie mamy kontroli co się dzieje pod spodem, możliwe są również problemy z wydajnością. Dlatego w niektórych (nie wszystkich!) zastosowaniach komercyjnych pisze się własne programy bezpośrednio na rejestrach lub używa się własnych bibliotek.

Skoro jesteśmy już przy IDE, to zwykle wygenerowany projekt ma domyślny sposób budowania np. ze skryptu Makefile wygenerowanego automatycznie. Czasem też kompilacja jest wykonywana bazując na konfiguracji projektu w xml. Przechowywanie takiej konfiguracji IDE, czy kreatora biblioteki w systemie kontroli wersji sprawia często problemy. Potrzebujemy tych plików, aby inni członkowie zespołu byli w stanie później wygenerować inną konfigurację, albo uruchomić nasz projekt u siebie. Szczególnie nieprzyjemne jest integrowanie konfiguracji zmienionej przez różne osoby na oddzielnych gałęziach.

Rozwiązaniem jest system budowania niezależny od IDE, a nawet systemu operacyjnego. Dzięki temu poszczególni programiści mogą używać swoich ulubionych narzędzi (chociaż częstą praktyką jest wymaganie od każdego pracy na tym samym IDE). Dodatkowo build można uruchomić na serwerze typu Continuous Integration.

Jako system budowania najlepiej używać CMake. Jest on dużo bardziej przyjazny niż czysty Makefile. Kiedy posiadamy konfigurację CMake możemy wygenerować potrzebne skrypty Makefile, albo dla innych systemów budowania np. Ninja (polecam wypróbować, czasy budowania Ninja są dużo krótsze w porównaniu z make).

Więcej informacji do poczytania o CMake:

Różnica 3: metody debugowania oprogramowania

Kolejnym ważnym aspektem pracy programisty jest debugowanie. Zdecydowanie najczęstszą praktyką jest testowanie bezpośrednio na docelowym sprzęcie. W tym celu uruchamiamy program i wywołujemy stany ręcznie, wykorzystujemy dodatkową aparaturę, przechodzimy stopniowo kod, wypisujemy dane na UART, świecimy testowymi LEDami i wykorzystujemy wiele podobnych technik.

W wielu profesjonalnych projektach te wszystkie rzeczy również są praktykowane, tylko przy użyciu dużo droższego sprzętu, co nieco usprawnia pracę. Prawda jest jednak brutalna – w zdecydowanej większości przypadków nie powinniśmy w ogóle tych problemów rozwiązywać na sprzęcie, bo jest to kompletnie nieefektywne!

Jesteśmy w stanie zaoszczędzić sporo czasu, jeśli maksymalnie odseparujemy kod bezpośrednio ingerujący w sprzęt i większość testów wykonamy na swoim PC. Większość popełnianych błędów to źle zrozumiane wymagania, błędy logiczne, literówki itp. Jeżeli korzystamy z Test Driven Development (TDD), spokojnie jesteśmy w stanie je wszystkie wyłapać za pomocą Unit Testów chwilę po tym jak te błędy popełnimy. O TDD w systemach wbudowanych napisałem już sporo na swoim blogu.

W kilkuosobowym projekcie studenckim często zdarzają się sytuację, że dzieli się projekt na dwie osoby, każdy pisze swój kod przez tydzień, a potem próbuje się to ze sobą zintegrować, co trwa co najmniej drugie tyle czasu. W większych projektach to zjawisko też występuje. Jednak jeśli wiele osób robiło swoje zmiany w odosobnieniu, to taka integracja może trwać wiele miesięcy.

Na sprzęcie powinno się debugować tylko te rzeczy, których nie da się inaczej sprawdzić, np. interakcja z sensorami i silnikami, komunikacja itd. Co ważne, testujemy tylko najniższą warstwę sprzętową. Filtrowanie sensorów albo obsługa retransmisji w komunikacji mogą być sprawdzone w testach na PC.

Od czego warto zacząć?

Jeżeli chcesz zacząć stosować opisane tutaj techniki w swoim istniejącym już projekcie, nie staraj się wprowadzać wszystkiego naraz. Ilość nowych rzeczy może być przytłaczająca. Poza tym każda z nich wymaga nieco wprawy i dyscypliny. Próbując robić wszystko naraz można łatwo się pogubić. Dlatego powinno się dodawać je stopniowo do swojego "arsenału" zaczynając od tych najprostszych i dających najwięcej wartości.

Jeżeli jeszcze nie używasz, powinieneś zacząć od kontroli wersji. Nie ma znaczenia, że piszesz projekt sam i nie trzymasz go na żadnym serwerze. Możliwość szybkiego powrotu do ostatniej działającej wersji sama w sobie jest warta zachodu, a kontrola wersji daje dużo więcej. Drugie w kolejności są narzędzia do statycznej analizy i formatowania kodu. Pozwolą Ci one w kilka sekund znaleźć wyjście poza rozmiar tablicy, czy używanie niezainicjalizowanych zmiennych.

Jeśli pracujesz nad projektem w zespole, kolejną techniką do wprowadzenia powinno być Code Review. Continuous Integration i Test Driven Development są trudniejsze do wprowadzenia, więc warto zostawić je na trochę później - jednak naprawdę są one warte zachodu!

O autorze

Autorem wpisu jest Maciek Gajdzica zawodowo zajmujący się programowaniem systemów ebedded, w tym systemów safety-critical. Maciek prowadzi swojego bloga ucgosu.pl, na którym publikuje teksty o programowaniu systemów wbudowanych i robotyce. Jego artykuły kierowanie są głównie do bardziej zaawansowanych czytelników, którzy mają już opanowane podstawy (np. z kursów Forbota). Jeśli taka tematyka jest dla Was ciekawa to zapiszcie się na newsletter ucgosu.pl, a w ramach bonusu otrzymacie wtedy poradnik „Jak zwiększyć jakość kodu w projektach embedded? – darmowe narzędzia”.

Zajmujecie się hobbystycznie programowaniem mikrokontrolerów i macie jakieś pytania? Jesteście związani z tą tematyką zawodowo i chcecie podzielić się swoją opinią? Dajcie znać w komentarzach!

Autor tekstu: Maciek (GAndaLF) Gajdzica
Redakcja: Damian Szymański

embedded, git, mikrokontrolery, programowanie