Thursday, June 16, 2011

Obiektowość dla embedded?

Często zdarza mi się wypełzać poza Javę, z której wyrosłem i ze zdziwieniem stwierdzam, że są inne obszary, w których ludzie mają podobne do naszych problemy i z utęsknieniem spoglądają w stronę gadżetów którymi dysponujemy.

Jakiś czas temu, konstruktywnie krytykując mój wpis, Sławek powiedział mniej więcej coś takiego: "Kto w obecnych czasach podaje w książkach przykłady w C++? Od razu widać, że old school"

Rzeczywiście, większość koncepcji związanych z: refaktoryzacją, TDD, clean code itd. nabrała obecnego kształtu w okół Javy i C# i ich community (tak, tak, pamiętam: Small Talk, C++, GoF - byli pierwsi :)). Jakby te języki były jedyne, dominujące, a inne to tylko marginalne ekstrawagancje. Zerknijmy na ten ranking. Jak widać Java i C# mają towarzystwo i to całkiem spore.

RTS, Entertainment
Mówię o systemach embedded, ale nie telefonach, grach i smartach, gdyż im (wg programistów embedded) bliżej raczej do PC-tów. Mówię o: systemach czasu rzeczywistego, systemach sterowania produkcją, oprogramowaniu obsługującym stacje bazowe w sieciach komórkowych, o komputerach pokładowych w samochodach, o sofcie który jest zainstalowany na naszych kartach kredytowych. Kto by pomyślał, że na takiej karcie jest procesor, a na nim system operacyjny, a na nim dziarsko działa maszyna wirtualna Javy? (@see Java Card)

C rządzi!
We wspomnianych wyżej domenach często króluje język C. Po pierwsze z powodów historycznych - ktoś zaczął pisać w C i tak już zostało.

W miarę rozrostu tego typu systemów, pojawiają poważne problemy z ich utrzymaniem. Miliony wierszy kodu, gęsto pociętych przez dyrektywy kompilatora, kompilacje warunkowe, ifdefy, makra tam, gdzie to tylko możliwe oraz specyficzny sposób oszczędnego nazywania zmiennych zdecydowanie utrudniają rozwijanie tego kodu. Te problemy powodują, że środowisko embedded coraz częściej rozgląda się za podejściem obiektowym, które pomoże tworzyć łatwiejszy w utrzymaniu kod. Napotykają jednak na pewne przeszkody, które budują przekonanie, że obiektowość nie jest dla embedded. Zebrałem nieco informacji nt. tych przeszkód. Niektóre z nich są rzeczywiscie zaskakujące, inne to raczej przekonania.

Brak kompilatorów
Do pewnego momentu nie było zwyczajnie technicznej możliwości skomplikowania kodu. Jeśli już jakieś istniały, to były tak zabugowane, że dyskwalifikowały się same przez się. Sytuację poprawił gcc, który popchnął sprawę nieco do przodu.

Koszt wejścia
O ile maszyna wirtualna Javy jest dla PC-tów darmowa, to za wersję na embedded trzeba płacić grube pieniądze. Doświadczenia zaprzyjaźnionych firm pokazują, że ta implementacja JVM jest koszmarnie wolna. Istnieją sprzętowe implementacje (tak! sprzętowe) maszyny wirtualnej, ale niewiele o nich wiem.

Niedoskonałość sprzętu
W porównaniu do procesorów z kategorii ix86, procesory dla systemów wbudowanych są dość proste (tylko w porównaniu :)). Pisząc usługę EJB nie zastanawiamy się, czy aby procesor działa poprawnie, czy nie będzie błędu w działaniu urządzenia. W aplikacjach embedded niedoskonałość sprzętu jest częstym kłopotem.

Trudności w poszukiwaniu błędów
Tak powszechne zadanie jak poszukiwanie błędów również może nastręczać trudności, gdyż odbywa się to poprzez debugowanie oraz analizę kodu asemblera. Taka analiza jest zdecydowanie łatwiejsza dla kodu napisanego w C niż w C++.

Koronny argument - wydajność
Hmmm, trochę prawda, trochę mit. Tak myślę. Przeanalizujmy.

Po pierwsze:
Jak wspomniałem JVM jest rzeczywiście wolna. Z drugiej strony istnieje przekonanie, że kod napisany w C++ będzie działa duuuuużo wolniej. Tylko czy ktoś to rzeczywiście zmierzył? Czy ktoś napisał dwie tożsame implementacje, jedną w C, drugą w C++ i stwierdził to jednoznacznie?

Po drugie:
Przyczyną mogą być ograniczenia programowe i sprzętowe. Sporo zależy od tego jak jest napisany sam kompilator, czy potrafi wykorzystywać automagiczne sztuczki optymalizacyjne. Innym i często skutecznym sposobem zwiększania wydajności jest zmiana sprzętu. Ten jednak przenika do branży dosyć wolno. Powód jest trywialny - koszty. Nowy sprzęt - większe pieniądze, kolejny kompilator, kolejne bugi itd.

Po trzecie:
W rozwiązaniach embedded rzadko kiedy brakuje np. 5%, 10%, 20% wydajności. Jeśli już trzeba zwiększyć wydajność, bo powiedzmy, nie nadążamy z pomiarem temperatury pieca i grozi wybuchem, to trzeba ją zwiększyć kilkukrotnie. W takim przypadku obniżenie wydajności o parę, paręnaście procent, w związku z konstrukcjami obiektowymi, jest do przyjęcia. Jeśli efektem tego narzutu będzie łatwość utrzymania i rozbudowywania dylemat jest wart uwagi. Tak doszliśmy to wysłużonego już hasła: czytelność ponad wydajność. Nie oznacza to bynajmniej nonszalancji w szafowaniu pamięcią, lecz to aby w pierwszej kolejności koncentrować się na tworzeniu czytelnego kodu, a dopiero w drugiej mierzyć, profajlować i optymalizować.

Co dalej?
Jakie wyzwania stoją przed technikami refaktoryzacji i wzorcami, które w swoim najbardziej światowym wydaniu, utknęły gdzieś pomiędzy Javą a C#?
  • Jak pracować z hybrydowym legacy code, pisanym trochę w C trochę w C++?
  • Jak bezpiecznie przepisywać kod proceduralny na obiektowy?
  • Jakie standardy kodowania przyjąć dla C++, aby uniknąć pułapek wynikających z bogactwa języka
  • Jak sensownie rozwinąć narzędzia wspierające refaktoryzację. Sorki, ale VC++ i wsparcie dla refaktoryzacji to porażka. Visual Assist X jest super, ale to jeszcze nie to. Eclipse C/C++ nie próbowałem, Wind River Workbench jest ponoć dobry, ale i kosztuje słono
  • i cała reszta bajerów, których się w okół Javy i C# dorobiliśmy

Za różnymi problemami oraz przekonaniami kryją się ludzie, którzy poszukują skutecznych rozwiązań swoich problemów. Na pewno coś da się zrobić.