The Power of 10/pl
│ English (en) │ polski (pl) │
Nieskończone pętle lub wycieki pamięci w oprogramowaniu rakiety startowej lub bezzałogowej sondy kosmicznej mogą spowodować utratę misji. Przyjęcie przez sterownik respiratora wartości domyślnej dla braku odczytu pomiaru może doprowadzić do śmierci ciężko chorego pacjenta. Dwa wypadki Boeinga 737 MAX z dużą liczbą ofiar śmiertelnych były wynikiem działania oprogramowania ślepo ufającego wynikom pojedynczego czujnika, który dostarczał nieprawidłowe dane.
Bezpieczne programowanie ma kluczowe znaczenie dla każdego projektu. Dotyczy to zwłaszcza zastosowań w niektórych dziedzinach, takich jak medycyna, wojsko, przemysł i lotnictwo. Z tego powodu Narodowa Agencja Aeronautyki i Przestrzeni Kosmicznej (NASA) zdefiniowała zbiór zasad o nazwie The Power of 10 (Potęga 10) podsumowujących podstawowe zasady poprawy bezpieczeństwa kodu.
Język Pascal wprowadza już pewne podstawowe środki poprawy bezpieczeństwa od podstaw, w tym silne typowanie i zachęcanie do bezpiecznych struktur kontrolnych. Warto jednak pamiętać o kilku dodatkowych zasadach, zwłaszcza na początku nowego projektu. Oczywiście możliwe jest również wdrożenie tych zasad na późniejszych etapach.
Zasady The Power of 10 (Zasady Potęgi 10) zostały zdefiniowane w 2006 roku przez Gerarda J. Holzmanna w Laboratorium Niezawodnego Oprogramowania JPL|NASA/JPL. Ich główną intencją jest wyeliminowanie pewnych praktyk kodowania C, które utrudniają przeglądanie lub analizę statyczną kodu. Zasady te zostały włączone do standardów kodowania wielu instytucji.
Zasady
Dziesięć zasad to:
- Unikaj złożonych konstrukcji przepływu, takich jak goto, bezpośrednich lub pośrednich, jak rekurencja (rekursja).
- Wszystkie pętle muszą mieć ustaloną górną granicę, aby zapobiec niekontrolowanemu kodowi.
- Unikaj alokacji pamięci sterty po inicjalizacji.
- Ogranicz wielkość procedury, funkcji i metody do jednej drukowanej strony. Zazwyczaj oznacza to nie więcej niż około 60 linii kodu na blok.
- Użyj co najmniej dwóch asercji w czasie wykonywania na funkcję.
- Ogranicz zakres danych do najmniejszego możliwego. Dlatego też, lokalne zmienne powinny być preferowane nad zmiennymi globalnymi.
- Sprawdź zwracaną wartość wszystkich funkcji lub zwróć nil lub NaN, aby wskazać, że zwracana wartość jest bezużyteczna. Dodatkowo każda wywoływana funkcja musi sprawdzać poprawność wszystkich rzeczywistych parametrów dostarczonych przez kod ją wywołujący.
- Używaj dyrektyw kompilatora lub dyrektyw IDE oszczędnie. Jeśli to możliwe, powinny być ograniczone do sekcji interface w module.
- Ogranicz użycie wskaźników do pojedynczego pobrania danych i unikaj używania wskaźników do funkcji.
- Kompiluj z aktywnymi wszystkimi możliwymi ostrzeżeniami; wszystkie ostrzeżenia należy następnie rozwiązać przed wydaniem oprogramowania.
Unikaj złożonych konstrukcji przepływu, takich jak goto i bezpośrednia lub pośrednia rekurencja
Chociaż konstrukcja goto jest dostępna w Pascalu, nie zaleca się jej używania, ponieważ utrudnia ona odczytanie kodu, a jeszcze trudniej jest przewidzieć jego zachowanie. Podobne rozważania dotyczą exit.
Jeśli to możliwe, należy unikać nawet konstrukcji z rekurencją. W wielu przypadkach pętle for, while i repeat... until wykonują tę samą pracę całkiem dobrze.
Wszystkie pętle muszą mieć ustaloną górną granicę, aby zapobiec niekontrolowanej ucieczce kodu
Ta zasada jest bardzo podobna do pierwszej. Stałe granice pomagają uniknąć używania struktur kontrolnych, takich jak goto lub exit.
Unikaj alokacji pamięci sterty po inicjalizacji
Niedopasowane pary alokacji i usuwanie bloków pamięci w stercie są częstym źródłem awarii lub wycieków zasobów. Dodatkowo, ta technika zwykle wymaga użycia wskaźników lub uchwytów i dlatego powinna być unikana w miarę możliwości (patrz reguła #9).
Ograniczenie procedur, funkcji i metod do pojedynczej drukowanej strony. Zazwyczaj oznacza to nie więcej niż około 60 linii kodu na blok
Krótsze bloki kodu mają trzy zalety:
- Łatwiej je zrozumieć.
- Są mniej podatne na przeładowanie.
- W wielu przypadkach skracanie bloków zapobiega duplikowaniu kodu.
Użyj co najmniej dwóch asercji środowiska wykonawczego na funkcję
Asercje są rodzajem automatycznego pasa bezpieczeństwa dla twojego kodu. To rodzaj testu, który zwraca prawdę lub fałsz w wyniku wykonania bloku kodu programu. Ich efekt jest podobny do przestrzegania zasady #7.
Ogranicz zakres danych do najmniejszego możliwego. Dlatego zmienne lokalne powinny być preferowane nad zmiennymi globalnymi
Zmienne globalne mogą zostać przypadkowo zmienione przez błędy w dowolnym miejscu kodu. To niebezpieczeństwo można znacznie zmniejszyć, ograniczając ich zakres do kontekstu lokalnego. Podobnie deklarowanie pól w klasach jako chronionych lub prywatnych może pomóc w poprawie bezpieczeństwa.
Sprawdź zwracaną wartość wszystkich funkcji lub zwracaj nil lub NaN, aby wskazać, że zwracana wartość jest bezużyteczna. Dodatkowo każda wywoływana funkcja musi sprawdzać poprawność wszystkich rzeczywistych parametrów dostarczonych przez kod ją wywołujący
Nie ufaj ślepo istniejącemu kodowi, nawet jeśli został napisany przez Ciebie. Zawsze dobrze jest sprawdzić wartości pod kątem wiarygodności. Odwrotnie, sprawdzanie w witrynie funkcji wywołującej może być obsługiwane przez jawne wskazywanie nieprawidłowych wyników.
Używaj dyrektyw kompilatora lub dyrektyw IDE oszczędnie. Jeśli to możliwe, powinny być ograniczone do sekcji interface modułu
Dyrektywy i warunki dodają osobną warstwę do twojego kodu, która jest zgodna z rodzajem logiki, która różni się od głównego algorytmu. Może to być głównym źródłem nieprzewidywalności. Podobne rozważania mogą dotyczyć również używania makr.
Ogranicz użycie wskaźników do pojedynczego pobrania danych i unikaj używania wskaźników do funkcji
Arytmetyka wskaźników jest złożona i często myląca. Brak równowagi między przydzielaniem a zwalnianiem pamięci może powodować awarie i wycieki. Dodatkowo, podobnie jak zmienne globalne, bloki pamięci, do których odwołuje się wskaźnik, są podatne na przypadkowe uszkodzenie przez błędy w dowolnym miejscu kodu.
Kompiluj z aktywnymi wszystkimi możliwymi ostrzeżeniami; wszystkie ostrzeżenia należy następnie rozwiązać przed wydaniem oprogramowania
Jeśli używasz FPC z wiersza poleceń, to w generowaniu cennych informacji pomocne mogą być szczególnie opcje -B, -C i -g. W Lazarusie może to być określone w oknie dialogowym opcji na poziomie globalnym. Dodatkowo istnieje możliwość kontrolowania tego zachowania za pomocą makr IDE na poziomie lokalnym. To oczywiście wyjątek od reguły nr 8.
Zobacz także
Przykłady
- 4 czerwca 1996 r. rakieta startowa o dużym udźwigu Ariane 5 uległa awarii podczas swojego dziewiczego lotu z powodu przepełnienia liczby całkowitej i nieobsłużonego wyjątku. Spowodowało to fiasko misji badawczej w zakresie magnetosfery Cluster i stratę ponad 370 milionów dolarów. Można było tego uniknąć, przestrzegając zasady nr 7 (i prawdopodobnie nr 10).
- W latach 1985-1987 trzech pacjentów zostało zabitych, a trzech dodatkowych osób zostało poważnie rannych, kiedy sterowana komputerowo maszyna do radioterapii Therac-25 dostarczyła ogromne dawki promieniowania. Przyczyny obejmowały ponowne użycie słabo poznanego kodu dla starszego modelu maszyny oraz arytmetyczne przepełnienie zmiennej flagi. Reguły #4, #5 i #7 zapobiegłyby temu.
Referencje
- GJ Holzmann: Potęga 10: Zasady opracowywania kodeksu krytycznego dla bezpieczeństwa DOI: 10.1002/9781119174240.ch10
- R Giorato: Potęga 10 — zasady kodowania NASA. Medium, 2 października 2019 r.