Dokumentacja i systemy jakości Model jakości oprogramowania Iwona Kochańska Katedra Systemów Elektroniki Morskiej WETI PG November 25, 2018
Potrzeba standaryzacji Tworzenie kodu źródłowego (implementacja kodu) to nieodłaczny element procesu wytwarzania systemu wbudowanego Język C jest najczęściej używanym językiem programowania w systemach wbudowanych Jakość programów napisanych w C wyraźnie odzwierciedla różnicę w poziomie umiejętności kodowania między doświadczonymi i niedoświadczonymi programistami. Standaryzacja umożliwia minimalizowanie tej rożnicy! ESCR zgodne z ISO/IEC 25010:2011 Systems and software engineering Systems and software Quality Requirements and Evaluation (SQuaRE) System and software quality models 2/60
Standardy języka C dla systemów wbudowanych Język C C90: ISO/IEC 9899:1990 Programming Language C. C99: ISO/IEC 9899:1999 Programming Language C C11: ISO/IEC 9899:2011 Programming Language C Język C++: ISO/IEC 14882:2003 Programming language C++ MISRA C język C zdefiniowany przez The Motor Industry Software Reliability Association (MISRA) MISRA C:1998 MISRA C:2004 MISRA C:2012 3/60
Model jakości oprogramowania Poprawność (correctness) - stopień zgodności programu ze specyfikacja i zaspokojenia celów przedsięwzięcia Wiarygodność (reliability) - stopień, w jakim program wykonuje zamierzone funkcje z wymagana precyzja Wydajność (efficiency) - ilość przetwarzanych zasobów i kodu, które program potrzebuje do wykonywania swoich funkcji Integralność (integrity) - stopień odporności programu lub danych na nieuprawnione użycie lub modyfikację Użyteczność (usability) - wysiłek, który musi być włożony w nauczenie się programu, w jego użycie, przygotowanie danych wejściowych i interpretację danych wyjściowych Łatwość pielęgnacji (maintainability) - wysiłek, który musi być włożony w lokalizację błędów i ich naprawę 4/60
Model jakości oprogramowania Elastyczność (flexibility) - wysiłek wymagany dla modyfikacji działajacego programu Testowalność (testability) - wysiłek, który jest potrzebny dla upewnienia się, że program funkcjonuje poprawnie Przenośność (portability) - wysiłek, który może być wymagany dla przeniesienia oprogramowania z jednego systemu sprzętowego lub programowego do drugiego Możliwość powtórnego wykorzystania (reusability) - stopień, w jakim całość lub część oprogramowania może zostać powtórnie wykorzystana w innej aplikacji Łatwość współdziałania (interoperability) - wysiłek, który musi być włożony dla podłaczenia jednego systemu do drugiego 5/60
Model jakości oprogramowania (ISO/IEC 25010) 6/60
Embedded System development Coding Reference (ESCR) Publikowane przez IPA/SEC (Software Reliability Enhancement Center, Technology Headquarters, Information-technology Promotion Agency, Japan) Zalecenia dotyczace jakości kodu źródłowego w języku C Opracowane w celu ustanowienia pewnej konwencji i promowania standardów kodowania w języku C Zgodne ze standardem C99 i MISRA C Wersja 2.0, będaca rozwinięciem wersji 1.1 https://www.ipa.go.jp/files/000040508.pdf 7/60
Dobre praktyki 8/60
Niezawodność (Reliability) Inicjalizacja zmiennych Jeśli zmienne sterujace nie zostana zainicjowane, ich wartości staja się niezdefiniowane, a wyniki operacji moga się różnić od spodziewanych. Inicjalizacja musi mieć miejsce w momencie deklaracji lub wprost przed użyciem zmiennej. 9/60
Niezawodność (Reliability) Inicjalizacja stałych Stałe musza być zainicjalizowane w momencie deklaracji, ponieważ później nie można juz im przypisać wartości. W języku C brak inicjalizacji przy deklaracji stałej nie powoduje błędu kompilacji (w języku C++ już tak). 10/60
Niezawodność (Reliability) Inicjalizacja tablic Tablice o ustalonej liczbie elementów powinny być inicjalizowane taka liczba wartości, ile elementów ma tablica. Jeśli do tablicy o N elementach zostanie zapisanych N znaków, może to spowodować błędne działanie funkcji przetwarzajacej łańcuch znaków. Jeśli dane w tablicy maja być przetwarzane jako łańcuch znaków, należy pamiętać o znaku pustym jako jej ostatnim elemencie. 11/60
Niezawodność (Reliability) Liczba zmiennoprzecinkowa a instrukcja iteracyjna Liczba zmiennoprzecinkowa nie powinna być licznikiem (zmienna sterujac a) instrukcji iteracyjnej. Błędy zaokragleń moga się kumulować i doprowadzić do nieprzewidzianego zachowania instrukcji iteracyjnej. 12/60
Niezawodność (Reliability) Operator warunkowy (?:) Podczas użycia operatora warunkowego (?:) wyrażenie logiczne powinno być zamknięte w nawiasach (), a zwracane wartości powinny być tego samego typu. 13/60
Niezawodność (Reliability) Zgodność typów argumentów i wyniku operacji arytmetycznej W przypadku działań arytmetycznych typ zmiennej, do której zapisywany jest wynik, nie ma znaczenia podczas kompilacji. Precyzja wartości wynikowej zależy tylko od argumentów działania. W celu kontrolowania typu danych należy stosować rzutowanie typów PRZED operacja arytmetyczna. 14/60
Niezawodność (Reliability) Jednoargumentowy operator - a typ unsigned Nie należy używać jednoargumentowego operatora - przed argumentem typu bez znaku. Np.: instrukcja: if ( - ui < 0 ) nie zwróci wartości true. 15/60
Niezawodność (Reliability) Sekwencja bitów Sekwencja bitów powinna być definiowana jako liczba typu unsigned. Wynik działania operatorów bitowych na liczbie typu signed może być zależny od kompilatora. 16/60
Niezawodność (Reliability) Porównanie: wskaźnik < > 0 Nie ma sensu sprawdzać, czy wskaźnik jest mniejszy lub większy od zera. Jeśli jednym z argumentów porównania jest wskaźnik, kompilator zmieni wartość 0 na null pointer, wówczas wynik porównania może być inny od zakładanego. 17/60
Niezawodność (Reliability) Deklaracja funkcji bez parametrów Deklaracja function() może być interpretowana przez niektóre kompilatory jako funkcja o nieznanej liczbie parametrów. Dlatego w przypadku funkcji bez parametrów należy deklarować ja z 1 parametrem void. 18/60
Niezawodność (Reliability) Zawsze sprawdzaj, czy nie wychodzisz poza zakres adresów elementów tablicy W języku C tablica nie może mieć zmiennej wielkości 19/60
Niezawodność (Reliability) Unikaj konstrukcji, które w szczególnych okolicznościach moga spowodować runtime error 20/60
Niezawodność (Reliability) Nie używaj wywołań rekurencyjnych (MISRA C) Rozmiar stosu używany podczas wywołania rekurencyjnego jest nieprzewidywalny, więc istnieje ryzyko przepełnienia go. 21/60
Niezawodność (Reliability) W instrukcjach warunkowych zawsze obsługuj wszystkie możliwe przypadki jeśli brakuje else, nie wiadomo, czy jest to zamierzone, czy programista zapomniał czegoś dopisać nawet jeśli nie potrzebujesz obsługi else, warto wykorzystać ta część 22/60 instrukcji do obsługi wyjatków
Niezawodność (Reliability) W instrukcjach warunkowych zawsze obsługuj wszystkie możliwe przypadki 23/60
Niezawodność (Reliability) Nie używaj operatora porównania: == lub!= do sprawdzania wartości licznika instrukcji iteracyjnej 24/60
Niezawodność (Reliability) Nie łacz w jednej instrukcji przekazania parametru do funkcji i jego modyfikacji Kompilatory nie gwarantuj a kolejności wykonania operacji na parametrach wejściowych funkcji! (side effect problem) 25/60
Niezawodność (Reliability) Nie porównuj liczb zmiennoprzecinkowych za pomoca operatora == lub!=! dobrze: f l o a t a = 2. 0 ; f l o a t b = a * 3 ; f l o a t e p s i l o n = 0.0001; i f ( b a * 3 < e p s i l o n ) {... } źle: f l o a t a = 2.0 f l o a t b = a * 3 ; i f ( b == a * 3 ) { / / TRUE of FALSE?} 26/60
Łatwość pielęgnacji/konserwacji (Maintainability) Łatwość pielęgnacji (maintainability) - wysiłek, który musi być włożony w lokalizację błędów i ich naprawę Kiedy potrzebna jest zmiana programu, który został już dostarczony klientowi, np.: w wyniku znalezienia błędu gdy zaistnieje potrzeba dodania nowej funkcjonalności Dobre praktyki: pamiętaj, że inni będa czytać twój kod, pisz w taki sposób, by zmniejszyć prawdopodobieńtwo błędu podczas modyfikacji, pisz prosto, pisz w jednolitym stylu, pisz w taki sposób, by móc łatwo opracować testy. 27/60
Łatwość pielęgnacji/konserwacji (Maintainability) Deklaracje wielu zmiennych 1. W jednej instrukcji deklaruj tylko jedn a zmienn a 2. Możesz zadeklarować w jednej instrukcji zmienne tego samego typu i przeznaczenia, jednak nie mieszaj zmiennych zainicjalizowanych ze zmiennymi niezainicjalizowanymi 28/60
Łatwość pielęgnacji/konserwacji (Maintainability) Długi łańcuch znaków w wielu liniach kodu Długi łańcuch znaków podziel na wiele składowych łańcuchów. Nie umieszczaj jednego łańcucha znaków w wielu liniach kodu (taka konstrukcja jest bardziej czytelna). 29/60
Łatwość pielęgnacji/konserwacji (Maintainability) użycie instrukcji switch, gdy możliwe sa tylko 2 przypadki używaj instrukcji switch tylko wtedy, gdy liczba możliwych przypadków jest większa od 2 switch jest obarczony większym prawdopodobieństwem błędu, niż if (nieprawnie zdefiniowany przypadek default, brak polecenia break) 30/60
Łatwość pielęgnacji/konserwacji (Maintainability) Nawiasy w wyrażeniach logicznych Używaj nawiasów ( ) by zwiększyć czytelność wyrażeń logicznych 31/60
Łatwość pielęgnacji/konserwacji (Maintainability) Nawiasy a operatory dwuargumentowe Używaj nawiasów ( ) by jasno zaznaczyć, jaka jest kolejność wykonywania operacji 32/60
Łatwość pielęgnacji/konserwacji (Maintainability) Użycie nazwy funkcji bez nawiasów ( ) W języku C zawołanie nazwy funkcji bez nawiasów oznacza pobranie adresu (referencji) tej funkcji Taki kod trudno odróżnić od pomyłki polegajacej na pominięciu nawiasów podczas wywołania funkcji Używaj operatora & do sprawdzania referencji do funkcji! 33/60
Łatwość pielęgnacji/konserwacji (Maintainability) Jawne porównanie z wartościa 0 Jawnie porównuj wartość zmiennej z wartościa 0 (lub true/false) 34/60
Łatwość pielęgnacji/konserwacji (Maintainability) Jedna zmienna = jedno przeznaczenie Nie używaj tej samej zmiennej do różnych zadań 35/60
Łatwość pielęgnacji/konserwacji (Maintainability) Nie używaj nazw istniejacych w standardowych biblitekach 36/60
Łatwość pielęgnacji/konserwacji (Maintainability) Nie używaj nazw rozpoczynajacych się od znaku _ Nazwy rozpoczynajace się od _ i wielkiej litery zarezerwowane sa w języku C do operacji na plikach. 37/60
Łatwość pielęgnacji/konserwacji (Maintainability) 0 przed liczba całkowita Nie stawiaj 0 przed liczba całkowita w celu jej wyrównania. 0 przed liczba całkowita jest rozumiane jako poczatek liczby w systemie ósemkowym. 38/60
Łatwość pielęgnacji/konserwacji (Maintainability) Wyjaśnienie nietypowych konstrukcji Jeśli używasz nietypowych konstrukcji języka C/C++, zawsze opisuj je za pomoca komentarza lub makra Jeśli używasz nieskończnych instrukcji iteracyjnych, b adź konsekwentny: tylko for(;;) lub tylko while(1) 39/60
Łatwość pielęgnacji/konserwacji (Maintainability) Nawiasy { } a pojedyncza instrukcja W przypadku, gdy w instrukcji warunkowej lub iteracyjnej znajduje się tylko jedna instrukcja nie ma potrzeby używania nawiasów { }. Jednak jeśli kod będzie w przyszłości modyfikowany i lista instrukcji wydłuży się, istnieje prawodopodbieństwo, że nikt nie będzie pamiętał o dodaniu nawiasów { }. Dlatego warto ich używać w każdym przypadku. 40/60
Łatwość pielęgnacji/konserwacji (Maintainability) Break w instrukcji switch Każdy przypadek case, włacznie z default, powinien być zakończony intrukcja break. 41/60
Łatwość pielęgnacji/konserwacji (Maintainability) Definicja instrukcji for W definicji instrukcji for powinny się znaleźć tylko instrukcje dotyczace zmiennej sterujacej 42/60
Łatwość pielęgnacji/konserwacji (Maintainability) Używaj maksymalnie 2-krotnego wskaźnika Wskaźnik potrójny jest trudny do interpretacji 43/60
Łatwość pielęgnacji/konserwacji (Maintainability) Trzymaj się jednolitej konwencji zapisu: pozycji nawiasów { } wcięcia na poczatku linii kodu spacji. Dlaczego warto używać spacji? 44/60
Łatwość pielęgnacji/konserwacji (Maintainability) Trzymaj się jednolitej konwencji pisania komentarzy Używaj // dla komentarza na końcu linii kodu (tylko 1 linia tekstu!) Używaj /* */ dla bloku komentarzy Trzymaj się jednolitej konwencji nazw 45/60
Łatwość pielęgnacji/konserwacji (Maintainability) Nie używaj par nazw, które różnia się jedynie: obecnościa podkreślenia _ litera O i cyfra 0 litera I i litera l lub cyfra 1 litera S i cyfra 5 litera Z i cyfra 2 litera n i litera h 46/60
Łatwość pielęgnacji/konserwacji (Maintainability) Pamięć dynamiczna - jeśli alokujesz pamieć w sposób dynamiczny, musisz pamiętać o ryzyku zwiazanym z: odwołaniem do nieprawidłowej przestrzeni adresowej przecieków pamięci Problem zwiazane z dynamiczna alokacja pamięci Przepełnienie bufora (Buffer overflow) - kiedy odwołujesz się do pamięci spoza zaalokowanego. Problem ten ujawni się dopiero podczas odwołania do pamięci, która została zapisana przez pomyłkę Brak inicjalizacji (Forgetting to initialize) - kiedy pamięć alokowana jest za pomoca new, ale nie dla obiektu klasy C++, pamięć nie jest automatycznie inicjalizowana. 47/60
Łatwość pielęgnacji/konserwacji (Maintainability) Problem zwiazane z dynamiczna alokacja pamięci (c.d.) Przeciek pamięci (Memory leak) - kiedy zapominamy o zwalnianiu pamięci. Nie dotyczy to programów, ktore uruchamiane sa raz i kończa dziłałanie po jednokrotnym wykonaniu. Problem dotyczy programów, które działaja długo i iteracyjnie. Odwołanie po zwolnieniu pamięci (Use after return) - kiedy odwołujemy się do pamięci zwolnionej przez wcześniejsze wywołanie delete 48/60
Przenośność (Portability) Przenośność (portability) - wysiłek, który może być wymagany dla przeniesienia oprogramowania z jednego systemu sprzętowego lub programowego do drugiego Dobra praktyka: pisz w takim stylu, który jest niezależny od konkretnego kompilatora używaj tylko standardowych elementów języka używaj tylko standardowych znaków i znaków specjalnych upewniaj się co do reprezentacji typów danych w systemie i zapisuj to w dokumentacji pisz w stylu niezależnym od środowiska, w którym wytwarzasz oprogramowanie wszystkie elementy zależne od implementacji powinny być udokumentowane, np.: reprezentacja liczb zmiennoprzecinkowych jak traktowane sa znaki reszty z dzielenia liczb całkowitych (C90) kolejność szukania plików wymienionych przez dyrektywy #include użycie innych znaków niż standardowe (alfanumeryczne łacińskie, _ { } [ ] # ( ) < > % : ;.? * + / ^ & =,, spacja, znaki nowej linii i 49/60 powrotu karetki, tab)
Przenośność (Portability) Typ char a znak (signed/unsigned) w przeciwieństwie do typu int typ char nie ma domyślnie okreslonego znaku - to zależy od kompilatora. jesli chcesz wykorzytać char do przechowywania wartości liczbowej, określaj jawnie, czy jest to signed char czy unsigned char, najlepiej tworzac nowa definicję typu 50/60
Przenośność (Portability) Format #include używaj < > dla plików nagłówkowych dostarczanych wraz z kompilatorem używaj dla plików nagłówkowych innych niż dostarczanych wraz z kompilatorem 51/60
Przenośność (Portability) Format #include Standard języka C nie definiuje, jak interpretować znaki \ lub /* w łańcuchu znaków ograniczonym przez < > lub Zachowanie znaku : jest również specyficzne dla kompilatora 52/60
Przenośność (Portability) Format #include Nigdy nie podawaj bezwzględnej ścieżki do pliku nagłówkowego 53/60
Przenośność (Portability) Kod assemblera w kodzie C/C++ Nie wszystkie kompilatory obsługuja funkcję asm (string),która pozwala na dołaczenie do kodu instrukcji assemblera. Lepiej jest używać funkcji inline zdefiniowanej jako makro preprocesora 54/60
Przenośność (Portability) Typy proste Rozmiar i reprezentacja prostych typów (int i floating point) zależa od kompilatora Definiuj własne typy, nawet dla typów prostych Standard C99 proponuje następuj ace nazwy (jeśli używasz C90, użyj tych nazw do zdefiniowania typów prostych): 55/60
Dobre praktyki Wydajność (efficiency) - ilość przetwarzanych zasobów i kodu, które program potrzebuje do wykonywania swoich funkcji Pisz tak, by brać pod uwagę ilość zasobów i wymagania czasowe wobec działania progrmu 56/60
Wydajność Wywołanie funkcji a makro Częste wywołania funkcji moga spowalniać działanie systemu Zadania krytyczne czasowo moga być realizowane przez makra, ale z zachowaniem szczególnej ostrożności - działanie makr zależy od implementacji Liczne wywołania makr zwiększaja rozmiar pliku binarnego 57/60
Przenośność (Portability) Niezmienne elementy w instrukcjach iteracyjnych Nie umieszczaj w instrukcjach iteracyjnych elementów, które sa niezmienne Kompilator w procesie optymalizacji nie musi zoptymalizować wszystkiego. Wówczas taka stała instrukcja wołana iteracyjnie spowalnia działanie systemu. 58/60
Wydajność Wskaźniki do struktur danych Nie przekazuj do funkcji całych struktur danych, tylko wskaźniki do nich 59/60
Literatura 60/60