TABLE OF CONTENTS:
Autor: Łukasz Sojka (Projektant-Programista)
Czego nauczył mnie development w projekcie safety-critical? Na to pytanie spróbuję dziś odpowiedzieć z punktu widzenia developera z ponad pięcioletnim doświadczeniem. O tym, z czym wiąże się proces wytwarzania oprogramowania safety-critical pisaliśmy już jakiś czas temu w artykule: Kilka wskazówek dotyczących rozwoju systemów krytycznych, od których zależy ludzkie życie.
W skrócie, oprogramowanie, które ma spełniać wysokie standardy bezpieczeństwa (safety-critical) podlega szeregowi norm opisujących, w jaki sposób ma ono powstawać. Cały proces wytwarzania oprogramowania musi być szczegółowo przemyślany. Udokumentowany od ogółu, czyli podstawowych wymagań systemowych, do szczegółu, czyli dokładnego projektu implementacji. Do każdego etapu dokumentacji projektowej, równolegle musi powstać plan jego weryfikacji.
Sam etap implementacji w tym procesie, jest tutaj przedstawiony jako jeden z wielu elementów. Oczywiście, również na tym etapie normy odgrywają ważną rolę. Narzucane są sposoby pisania kodu, reguły, standardy – np. MISRA C.
Jak więc przebiega praca dewelopera w takim projekcie? Czy te wszystkie dokumenty i reguły pomagają, czy przeszkadzają deweloperowi? Zacznijmy od dokumentacji projektowej. To, że powinna ona być obecna podczas wytwarzania oprogramowania dowolnego typu, jest niewątpliwe. W projektach typu safety-critical jest jednak często znacznie więcej dokumentacji projektowej i jest ona bardziej szczegółowa. Poza podstawowym opisem podziału na bloki, klasy, rozpisaniem interfejsów czy struktur danych, mamy tutaj również szczegóły mocno implementacyjne. Projekt implementacji oprogramowania safety-critical musi opisywać zachowania oprogramowania w każdym, nawet mało prawdopodobnym przypadku. Nie ma tutaj miejsca na niedopowiedzenia czy radosną twórczość developera.
Początkowo może się wydawać, że jest to ogromne ograniczenie. Nic bardziej mylnego. Fakt, czasami odgórnie narzucona forma implementacji bywa problematyczna. Zdarzają się sytuacje, kiedy deweloperowi znacznie łatwiej byłoby do danej funkcji przekazać jakiś argument w innej formie, ale to już wiąże się z koniecznością zmiany projektu implementacji, a być może i architektury, no a zmiana tych dokumentów, to również zmiana planów ich weryfikacji i pewnie będzie miało to też wpływ na środowisko testowe.
Są to jednak sytuacje sporadyczne, które występują tym rzadziej, im bardziej przemyślane są wcześniejsze etapy projektu, a jeśli występują, obligują dewelopera do szerszego spojrzenia na wpływ ewentualnych zmian na cały projekt.
Tutaj płynnie przechodzimy do korzyści, jakie daje szczegółowe tworzenie dokumentacji projektowej. Aby mogła ona powstać, cały zespół złożony z projektantów, architektów oprogramowania, ale często również deweloperów i testerów musiał szczegółowo przemyśleć każdy detal, każdą sytuację, jaka może się zdarzyć, rozplanować rozmieszczenie wszelkich komponentów czy mechanizmów wymiany danych.
Kiedy więc dochodzi do momentu implementacji, wspomniany brak miejsca na wolną inwencję dewelopera nie jest ograniczeniem, a zapewnieniem, że to, co deweloper tworzy, będzie działać zgodnie z oczekiwaniami, a ryzyko błędów jest minimalizowane już na samym początku.
Mnie osobiście, daje to ogromną satysfakcję kiedy napisane przeze mnie oprogramowanie działa poprawnie od pierwszego uruchomienia i nie muszę poprawiać dziesiątek bugów wynikających z niedopowiedzeń.
Oczywiście nie oznacza to, że błędów w takim oprogramowaniu nigdy nie ma. Jak to mówią, nie myli się tylko ten, kto nic nie robi. Zatem, mimo przygotowania nawet najbardziej szczegółowej dokumentacji projektowej, zdarza się, że w pewnych sytuacjach nasz kod zachowuje się inaczej, niż powinien. Tutaj istotne jest, aby wykryć to najszybciej jak to możliwe.
Dlatego też w projektach safety-critical każdemu etapowi projektu odpowiada etap testów. Począwszy od testów jednostkowych sprawdzających pojedyncze funkcje, przez testy komponentów aż po funkcjonalne testy całego produktu. I tutaj znów mogłoby się wydawać, że tak skrupulatne testowanie oprogramowania z punktu widzenia dewelopera jest zbędne i przysparza jedynie niepotrzebnej pracy, bo przecież uruchomiłem program samodzielnie i u mnie działa, a ci testerzy wymyślają jakieś mało prawdopodobne przypadki, które na pewno nigdy się nie wydarzą. Znów, nic bardziej mylnego.
Praca w projekcie typu safety-critical niejednokrotnie pokazała mi, jak bardzo potrzebne są testy oprogramowania i jak bardzo złożone błędy niekiedy odnajdują. Manualne uruchomienie programu – co często jest jedynym testem w wielu projektach non-safety – nie jest w stanie sprawdzić zależności czasowych. Nie pokaże, czy np. jakiś stan wyjściowy nie jest w specyficznych warunkach niestabilny.
Wszystko to natomiast widać bardzo wyraźnie w dobrze zaplanowanych testach, które mogą wymusić mało prawdopodobne, ale nie niemożliwe warunki. Sama poprawa błędów wykrytych przez takie testy dla dewelopera nie jest uciążliwa, ponieważ dobrze opisany test zawiera szczegółowe warunki na reprodukcję błędu, co często znacznie przyspiesza jego lokalizację i ułatwia poprawę.
Skąd jednak takie błędy się biorą skoro mamy tak dokładnie opisany projekt? Pomijając implementację niezgodną z opisanymi założeniami, jedną z przyczyn defektów w programowaniu jest niewłaściwy sposób pisania kodu. Powiedzmy sobie w takim razie o samym przebiegu implementacji. Jak wspomniałem, w projektach safety-critical normy nakładają spore wymagania, np. stosowania się do reguł standardu MISRA C, stosowania narzędzi do statycznej analizy, zapewnienie czytelności kodu czy prowadzenia review kodu. Po raz kolejny wydawać by się mogło, że to zbędne zabiegi, które tylko spowalniają proces implementacji. Zwłaszcza reguły MISRA, w większości mogą wydawać się niezrozumiałe i niepotrzebne.
Weźmy np. bezwzględny nakaz jawnego rzutowania zmiennych różnych typów przed operacją arytmetyczną. Wiadomo przecież, że kompilator poradzi sobie sam i dobierze odpowiedni typ zmiennej. Ale co jeśli podczas takiego niejawnego rzutowania utracimy istotne informacje w wyniku zaokrąglenia liczb?
Stosowanie takiej reguły wymusza od dewelopera, aby ten zastanowił się, czy na pewno uzyska poprawny efekt oraz pomaga zrozumieć, w jaki sposób napisany kod będzie interpretowany przez kompilator.
Zapewnianie czytelności kodu i prowadzenie review to kolejne niezmiernie istotne kwestie. Wiadomo, kod napisany jednym ciągiem ze zmiennymi o nazwach typu ‘a’, ‘b’, ‘c’ prawdopodobnie również będzie działać. Często jednak będzie stwarzać problemy przy próbie jakiejkolwiek modyfikacji nie tylko przez innych członków zespołu, ale nawet i przez pierwotnego autora takiego kodu. Takich problemów możemy uniknąć stosując się do jasno określonych reguł opisujących jak dzielić kod na podfunkcje, jak nazywać zmienne itp. Nawet kiedy nam się wydaje, że kod jest czytelny i zrozumiały, weryfikacja przez innego członka zespołu podczas review często może pokazać, że jednak można napisać coś jeszcze bardziej klarownie.
Odpowiadając zatem na pytanie postawione na początku, czego nauczył mnie dewelopment projektu safety-critical? Dzięki stosowaniu reguł MISRA oraz analizowaniu błędów, jakie mi również zdarzało się popełniać, z pewnością znacznie poszerzyłem swoje umiejętności programistyczne oraz nauczyłem się szerzej patrzeć na to, jaki efekt uzyskam i jaki wpływ na całość projektu ma moja praca. Przekonałem się, również że normy nie są takie straszne, jak by się mogło wydawać, a skrupulatnie zaplanowane procesy, dokumentacja projektowa i szczegółowe testy oprogramowania powinny być nieodłączną częścią każdego projektu, nie tylko safety-critical. Dzięki temu praca w takim projekcie, choć mogłaby się wydawać nudna i mocno formalna, jest naprawdę przyjemna.