Jak wspomniałem w poprzednim wpisie, normy definiują różne poziomy bezpieczeństwa w zależności od możliwych skutków błędnego działania systemu. Tym poziomom odpowiadają konkretne wskaźniki statystyczne. O ile sprawdzają się one w przypadku układów psujących się w sposób łatwy do opisania prawdopodobieństwem – jak na przykład części elektroniczne – to soft zawiera częściej błędy systematyczne, czyli po prostu bugi. Poza tym wymaganych statystyk nie da się zmierzyć, ani przewidzieć na etapie projektowania. Zamiast tego tworząc software powielamy pewne rozwiązania, które sprawdziły się w istniejących systemach. Polecane rozwiązania są często opisane w normach. Przyjrzymy się więc dzisiaj praktykom zalecanym przez normę kolejową EN50128.
Projektowanie systemu
Według normy projektując system powinniśmy wykorzystywać „Structured Methodology”. Chodzi o to, żeby przed rozpoczęciem implementacji najpierw dobrze przemyśleć koncepcję systemu. Powinna ona zostać precyzyjnie opisana, ale można użyć w tym celu metody nieformalne i różne formy graficzne. Dzięki temu jest ona łatwiejsza do zrozumienia. Podczas fazy koncepcyjnej powinniśmy wykonać kroki takie jak:
- Określenie granic systemu i środowiska, w którym działa.
- Identyfikacja wymagań.
- Identyfikacja procesów i przepływu danych.
- Rozbijanie dużych problemów na mniejsze.
Podczas ich realizacji powinniśmy stosować techniki proste, intuicyjne i pragmatyczne. Niektóre z wymienionych technik to:
- Checklisty.
- Maszyny stanów.
- Diagramy sekwencji.
- Diagramy przepływu sterowania i danych.
- Diagramy sekwencji.
- Modelowanie wspomagane komputerowo.
Aby koncepcja systemu była zrozumiała dla wszystkich, zespół powinien wykonywać przeglądy i zgłaszać uwagi, czyli robić Design Review. Innym ważnym aspektem, o który trzeba zadbać już w fazie projektowania jest Traceability. Chodzi o to, aby dokumentacja systemu umożliwiała prześledzenie drogi od sformułowania wymagania stawianego systemowi, przez elementy architektury i design konkretnych komponentówu, które je implementują, dalej przez implementację w kodzie źródłowym aż do testów sprawdzających poprawność implementacji i spełnienie wymagań.
Norma wspomina o metodologiach z lat 80-tych takich jak SSADM, MASCOT, czy JSD. Jednak zapisy są ogólne, aby mogły pozostać prawdziwe również dla nowych technik. Na przykład popularne ostatnio Domain Driven Design świetnie wpisuje się w opisane wymagania. realizowane są już choćby pierwsze próby przeniesienia DDD na grunt systemów safety-critical. Odsyłam po więcej do artykułu opisującego próby z DDD przy pracach nad myśliwcami Gripen.
Oczywiście później, kiedy koncepcja systemu jest już określona należy ją odpowiednio udokumentować i na bieżąco wprowadzać zmiany wynikłe podczas developmentu. Tutaj rygor jest już bardzo duży i musimy wytworzyć sporą ilość papierów, o czym wspominałem przy okazji omawiania V-modelu.
Techniki specyficzne dla safety-critical
Istnieją również pewne szczegółowe techniki zwiększające bezpieczeństwo systemu. Część z nich spotyka się praktycznie tylko w safety-critical:
- Programowanie defensywne – zabezpieczenie się w kodzie przed potencjalnymi błędami. Najczęściej sprowadza się do sprawdzania poprawności argumentów wejściowych, zakresów zmiennych reprezentujących fizyczne wartości, konfiguracji, czy niemożliwych ścieżek w instrukcjach warunkowych.
- Asercje – wiąże się trochę z programowaniem defensywnym. Jest to wykorzystanie asercji, głównie do sprawdzenia stanu na wejściu lub wyjściu danej funkcji. W ten sposób realizujemy ideę Design by Contract.
- Graceful degradation – system jest w stanie dalej realizować krytyczne funkcje w przypadku błędów pewnych mniej ważnych komponentów.
- Diverse programming – w celu uniknięcia błędu systemu całego systemu spowodowanego jednym bugiem w sofcie (takich jak np. w rakiecie Ariane 5), tworzonych jest kilka różnych implementacji tej samej funkcjonalności. W celu dalszego zwiększenia dywersyfikacji mogą one być realizowane na różnych procesorach i przez niezależne zespoły developerskie.
- Safety-bag – dodatkowy moduł o uproszczonej logice kontrolujący funkcje safety systemu i mogący wywołać safe-state w przypadku wykrycia anomalii.
Niektóre techniki są również wyraźnie odradzane przez normę:
- Przywracanie systemu po błędzie do poprzedniego poprawnego stanu – w przypadku wykrycia błędu system zwykle nie powinien sam podejmować akcji wychodzenia z błędu. Dużo bezpieczniejszym rozwiązaniem jest przejście w safe-state i czekanie na decyzję operatora.
- Dynamiczna zmiana konfiguracji podczas działania – najlepiej jeżeli system wczytuje konfigurację tylko przy starcie. W przypadku zmiany konfiguracji niezbędny jest reset i powtórzenie procedury startowej. W ten sposób możemy uniknąć subtelnych błędów dotyczących zmiany konfiguracji w działających modułach.
Praktyki dotyczące kodu źródłowego
W normie możemy znaleźć również zalecane praktyki dotyczące kodu źródłowego:
- Stosowanie języków silnie typowanych – pozwalają one na znajdowanie większej ilości błędów przez kompilator.
- Coding Standard – opis niebezpiecznych konstrukcji językowych, które łatwo prowadzą do błędów i nie powinny być stosowane. Jest to szczególnie istotne w przypadku takich języków jak C i C++, gdzie czai się na nas wiele niebezpieczeństw.
- Coding Style Guide – jednolity styl pisania kodu wpływa pozytywnie na czytelność. Dlatego powinniśmy stosować dokument zawierający opis używanych konwencji nazw, białych znaków, czy nawiasów.
Norma EN50128 opisuje choćby kilka praktyk tworzenia czystego kodu:
- Podział na niewielkie moduły o pojedynczej odpowiedzialności.
- Moduły powinny być podzielone na warstwy abstrakcji.
- Ograniczone i jasno zdefiniowane zależności między modułami.
- Moduły komunikują się między sobą dzięki jasno zdefiniowanych interfejsów.
- Unikamy zmiennych globalnych.
- Funkcje mają ograniczoną ilość parametrów wejściowych.
- Funkcje mają ograniczoną długość i złożoność.
Jak widać są to ogólnie akceptowalne zasady jak powinien wyglądać dobrze napisany kod, które z powodzeniem możemy stosować również w standardowych aplikacjach. Istnieją jednak również pewne restrykcje specyficzne dla systemów safety-critical:
- Nie używamy dynamicznej alokacji pamięci – chodzi oczywiście o ogranczenie ryzyka wystąpienia wycieków pamięci oraz fragmentacji i możliwości odmówienia zaalakowania potrzebnego przez program obszaru. Systemy safety-critical potrafią działać po kilka lat bez przerwy i problemy tego typu mogą uniemożliwiać poprawne działanie.
- Ograniczone użycie rekurencji – tutaj problemem jest oczywiście możliwość przepełnienia stosu.
- Ograniczone użycie wskaźników – błędne użycie wskaźników lub przekłamanie wartości adresu (np. przez błąd hardware’owy) może prowadzić do nadpisania niewłaściwych obszarów pamięci. Poza tym w przypadku wskaźników na funkcje może się wykonać niewłaściwy fragment kodu.
Weryfikacja i testowanie
W poprzednich rozdziałach informacje umieszczone w tabelach i opisach zawartych w normie starałem się skondensować i opisać swoimi słowami. W przypadku weryfikacji i testowania pokażę jak wygląda taka tabelka.
1 | Formal Proof | - | R | R | HR | HR |
2 | Static Analysis | - | HR | HR | HR | HR |
3 | Dynamic Analysis and Testing | - | HR | HR | HR | HR |
4 | Metrics | - | R | R | R | R |
5 | Traceability | R | HR | HR | M | M |
6 | Software Error Effect Analysis | - | R | R | HR | HR |
7 | Test Coverage for Code | R | HR | HR | HR | HR |
8 | Functional/Black Box Testing | HR | HR | HR | M | M |
9 | Performance Testing | - | HR | HR | HR | HR |
10 | Interface Testing | HR | HR | HR | HR | HR |
HR oznacza highly recommended, R to recommended, M to mandatory, a – oznacza, iż norma nie definiuje czy technika jest zalecana, czy nie. W niektórych tabelach mamy również NR, czyli not recommended. Dla każdej z technik pozostało dokładny opis. Norma rekomenduje również wystarczające zestawy technik do wykorzystania dla konkretnych poziomów SIL. I tak dla powyższej tabeli na poziomie SIL3 i SIL4 powinniśmy użyć technik nr 3,5,7,8 oraz jedną z 1,2 lub 6. Natomiast na poziomie SIL1 i SIL2 wystarczą 5 oraz jedna z 2,3 lub 8.
Dalej norma precyzuje przedstawione w tej tabeli zapisy. Mówi jakie formy analizy statycznej, dynamicznej i testów powinniśmy wykorzystać, czy jaki użyć rodzaj test coverage.
Podsumowanie
Jak widać, zalecane praktyki nie biorą się z księżyca. Przemyślenie projektu przed implementacją i trzymanie się praktyk, które sprawdziły się w podobnych projektach to po prostu dobre inżynierskie podejście do projektu. Oczywiście niektóre zalecenia dla systemów safety-critical są bardziej restrykcyjne, jednak wiele z nich może być z powodzeniem stosowanych w każdym większym projekcie. Normy są często postrzegane jako coś złego, co ogranicza naszą programistyczną kreatywność. Jednak jak widzimy, często mogą być również naszym sprzymierzeńcem w walce o jakość projektu.