W systemach embedded zwykle skupiamy się na niskopoziomowych interakcjach ze sprzętem. Poznajemy nowe interfejsy, wykorzystujemy kolejne zewnętrzne układy i wykorzystujemy nowe rodziny procesorów. Jednak to z czym sobie naprawdę nie radzimy to poskromienie rosnącej złożoności tworzonych przez nas systemów. W większości systemów prawdziwym problemem jest architektura, czyli systematyczne podejście do radzenia sobie z tą złożonością. Jako branża embedded nie potrafiliśmy do tej pory stworzyć jasnych zasad tworzenia architektury i utrzymywania jej w projekcie. Zwykle dominują dwa podejścia – albo architektury nie ma wcale, albo tworzona jest na początku przed rozpoczęciem kodowania i okazuje się niepraktyczna. W tym wpisie dzielę się swoimi spostrzeżeniami na temat problemów z architekturą w embedded.
Hardware Driven Design
Pierwszą rzeczą na jakiej skupiamy się w projekcie embedded jest hardware. Mając jakiś problem do rozwiązania patrzymy na peryferia, które musimy do tego wykorzystać i od razu bierzemy się do ich oprogramowania. Nic dziwnego – część HW jest uważana za najtrudniejszą i najbardziej niewiadomą. Musimy sprawdzić, czy wymyślona przez nas koncepcja ma szansę się sprawdzić w praktyce. Informacje o potrzebnym hardware są potrzebne również, aby stworzyć projekt PCB. Cała reszta projektu jest uznawana za prostszą i niegodną spędzania nad nią czasu. Dlatego wyższe warstwy architektury często nie są zbyt dobrze zdefiniowane. Co więcej jest ona podrzędna w stosunku do części sterującej peryferiami, musi się dostosować do API driverów, co często skutkuje skomplikowanym kodem i trudnościami w dalszym utrzymaniu.
A przecież wcale nie musi tak być. Wystarczy potraktować początkowe eksperymenty z hardwarem jako Proof Of Concept, który nie musi od razu stawać się kodem produkcyjnym. Na początku naszym celem powinno być udowodnienie, iż jakieś konkretne działanie jest możliwe do uzyskania. A ostateczny kształt drivera powinien być skutkiem przemyślanej architektury całego systemu, w której w dodatku pełni zwykle funkcję szczegółu implementacyjnego.
Zależności od zewnętrznych bibliotek
W kodzie embedded bardzo często nie podejmujemy żadnych prób odseparowania logiki biznesowej od zewnętrznych bibliotek. Dobra architektura traktuje biblioteki jako szczegół implementacyjny ukryty za warstwą abstrakcji przy pomocy wzorców projektowych takich jak Adapter, Proxy, czy Bridge. W efekcie nasz kod bezpośrednio zależy bibliotek peryferiów (np. STM StdPeriph, Microchip Harmony), funkcji konkretnego RTOSa (np. FreeRTOSa), stosu TCP (np. lwIP), czy systemu plików (np. FatFS). Może nam się to wydawać niegroźne, ale tylko do momentu, kiedy będziemy chcieli taką bibliotekę zamienić na inną w połowie projektu.
Przyczyn takiego podejścia naley upatrywać w notach aplikacyjnych, przykładach kodu i różnych kursach. Tam biblioteki są wywoływane bezpośrednio, żeby nie zaciemniać obrazu dodatkowymi warstwami abstrakcji. W końcu te przykłady służą do celów edukacyjnych. Niestety są one później używane w ten sam sposób w kodzie produkcyjnym.
Ograniczenia języka C
Powszechne stosowanie języka C w embedded na pewno również nie ułatwia tworzenia przejrzystej architektury. Większość materiałów i technik tworzona jest z myślą o językach obiektowych. Osiągnięcie tego samego w C samo w sobie może być trudne i nieczytelne. W C i embedded brakuje jasno sprecyzowanych zasad pisania czystego kodu, czy SOLID.
Język C nie słynie również ze zbyt przyjaznej społeczności. Próg wejścia jest dość wysoki, zagadnienia trudne, a nauka wymaga czasu. Jednocześnie osoby, które już tę wiedzę posiadły, często niezbyt chętnie się nią dzielą. A czasem wręcz wyładowują się na nowicjuszach. Koronnym przykładem są tu listy mailowe Linuxa. W ten sposób nie dość, iż ograniczamy sobie dopływ świeżej krwi, to jeszcze każemy nowym samemu dochodzić do wszystkiego, często z pominięciem dobrych praktyk.
Brakuje źródeł rzetelnej wiedzy
Owe dobre praktyki nie mają się za bardzo gdzie kształtować i rozprzestrzeniać. Konferencji dotyczących C i Embedded w Polsce praktyczne nie ma. Na świecie też jest ich raczej mało. Sytuację trochę ratują producenci sprzętu i systemu organizujący warsztaty. Jednak zwykle służą one do reklamowania konkretnej rodziny procesorów, IDE, czy bibliotek. W Embedded brakuje nam wymiany wiedzy znanej z innych gałęzi oprogramowania. Mała jest również społeczność open source tworząca biblioteki i projekty niezależne od dużych producentów procesorów
Brakuje również fachowej literatury analizującej dokładnie podejście do architektury w systemach embedded. Mamy tutoriale pokazujące tworzenie mniejszych projektów, mamy opisy dobrych praktyk dotyczących przerwań, czy RTOSów jest choćby co nieco o wzorcach projektowych. Jednak brakuje zasad architektury całych dużych systemów łączących w sobie wiele różnych peryferiów i zaawansowanej logiki nad nimi.
Powolne przejmowanie technik z języków wyższego poziomu
Nie dość, iż jako społeczność embedded sami nie tworzymy dobrych praktyki i wzorców, to jeszcze dosyć powoli adaptujemy odkrycia z innych gałęzi programowania. Minęło dużo czasu zanim zaczęliśmy przyswajać techniki takie jak Test Driven Development, czy Continuous Integration. Na szczęście zdążyły już na dobre zagościć w wielu projektach embedded poprawiając ich jakość. Jest jednak wiele innych koncepcji czekających na przeniesienie na grunt systemów embedded. Aktualnie na topie jest Domain Driven Design adresujące wiele problemów dotyczących projektowania dużych systemów webowych w językach obiektowych. Może nie da się przełożyć jeden do jednego do embedded, ale są duże części DDD, które mogłyby mocno ułatwić development systemów embedded.
Branżowe mity
Kolejnym problemem są pewne mity rozprzestrzenione wśród programistów embedded. Powstały one w dawnych czasach, kiedy kompilatory były dużo gorsze w tworzeniu optymalnego kodu maszynowego. Dzisiaj te reguły są już po prostu nieaktualne. Zabobony dotyczą najczęściej wydajności np. C++ nie nadaje się do embedded bo produkuje dużo nadmiarowego kodu, należy ograniczyć ilość wywołań funkcji, bo instrukcje skoku zajmują zbyt dużo czasu. Te stwierdzenia kiedyś może miały rację bytu, ale dawno już nie są prawdziwe. Ich popularność pokazuje inną niepokojącą tendencję – uczymy się pewnych rzeczy jeden raz i potem zawsze bierzemy je jako pewnik. Branża IT zmienia się bardzo gwałtownie i po prostu musimy uczyć się nowych rzeczy żeby za nią nadążać.
Podsumowanie
Nie piszę tego wszystkiego tylko żeby sobie ulżyć i trochę ponarzekać. W najbliższym czasie mam zamiar podzielić się większą ilością przemyśleń dotyczących architektury systemów embedded. Chcę wskazać rozwiązania, które pomogą w efektywnym tworzeniu dużych systemów.