Błędy, testowanie i weryfikacja programów Jak napisać bezbłędny program? 1 Proces a program Program jest obiektem statycznym to formalny opis tego, co ma być wykonane. Proces jest obiektem dynamicznym to ciąg sekwencyjnie wykonywanych instrukcji programu. KaŜdy program jest skończony trudno napisać nieskończony program Procesy mogą być: skończone (te z reguły nas interesują) nieskończone (kończone mniej lub bardziej brutalnie) 2/26 Poprawność programów Poprawność częściowa - po zakończeniu: wygenerowane dane wyjściowe są poprawne (zgodne ze specyfikacją dla przyjętych danych wejściowych). Poprawność całkowita dla wszystkich dopuszczalnych zestawów danych wejściowych proces kończy swe działanie wygenerowane dane wyjściowe są poprawne (zgodne ze specyfikacja dla przyjętych danych wejściowych). 3/26 1
Dowodzenie poprawności programów Formuły częściowej poprawności programu Anthony Hoare, połowa lat 60. Metoda dowodzenia warunku stopu (60) R. Floyd; korzysta z zasady indukcji 4/26 Przykład Zadanie: czy w A[1..n] of Integer jest x? jest:=false; for k:=1 to n do if A[k]=x then jest:=true Zacznijmy od zapisania go przy uŝyciu pętli while. jest := false; k:=1; while k<=n do begin if A[k]=x then jest:=true; k:=k+1 end Sprawdźmy, Ŝe podany niezmiennik pętli spełnia wszystkie warunki. Oznaczmy przez wartość formuły logicznej. ZauwaŜmy po pierwsze, Ŝe w trywialny sposób inicjalizuje się. Warunek jest zawsze prawdziwy: nie ma bowiem Ŝadnej wartości większej lub równej 1, a jednocześnie mniejszej od 1, wskutek czego pierwszy czynnik koniunkcji jest trywialnie prawdziwy. Wszelkie stwierdzenia poprzedzone kwantyfikatorem ogólnym dla elementów zbioru pustego są prawdziwe. Oczywiście dla oraz mamy teŝ, więc drugi czynnik teŝ jest prawdziwy. Po drugie wykonanie jednego obrotu pętli nie zmienia prawdziwości niezmiennika. Jeśli bowiem na początku pętli dla pewnej wartości k wartość zmiennej jest równa się false, to na mocy załoŝenia indukcyjnego zachodzi. Teraz mamy dwa przypadki. Jeśli dodatkowo, to moŝemy powiedzieć równowaŝnie, Ŝe, czyli, a to jest równowaŝne. Warunek ten obrazuje stan obliczeń po wykonaniu badanej instrukcji warunkowej, gdyŝ wartość zmiennej {\tt jest} pozostanie równa false. W jednym kroku pętli wykonuje się jeszcze tylko instrukcja k:=k+1. Po tym przypisaniu warunek nasz będzie spełniony dla nowego k, czyli niezmiennik zostaje odtworzony. Jeśli natomiast, to wykona się instrukcja jest := true i wtedy, dla nowej wartości, formuła będzie fałszywa, czyli równowaŝna wartości. Dowód: Podobnie gdy zmienna jest ma wartość true, na mocy załoŝenia istnieje takie, Ŝe. NiezaleŜnie od tego, czy przypisanie jest :=true zostanie wykonane w ramach instrukcji warunkowej, po wykonaniu instrukcji k:=k+1 niezmiennik się równieŝ odtworzy. To stwierdzenie kończy dowód niezmienniczości formuły. Zostało nam w końcu do udowodnienia, Ŝe po wyjściu z pętli, gdy mamy oraz zachodzi, otrzymamy tezę. Zatem ze względu na to, Ŝe dochodzimy do wniosku, Ŝe, co łącznie z pierwszym czynnikiem niezmiennika daje nam (po zastosowanu reguły osłabienia tezy) końcowy warunek. W naszym przypadku niezmiennik opisywał ogólną sytuację po kilku obrotach pętli: zmienna jest ma wartość true wtedy i tylko wtedy gdy na lewo od znajduje się wartość. Kiedy szukamy niezmiennika, staramy się moŝliwie dokładnie wyobrazić sobie jak moŝe wyglądać ytuacja, gdy juŝ pętla wykona parę obrotów i zapisujemy to w postaci formuły logicznej. Jeśli nie mamy wystarczająco duŝo wyobraźni, to niezmiennik wyjdzie za słaby i trudno będzie korzystając tylko z niego i z dozoru pętli jego niezmienniczość udowodnić (choć moŝe być prawdziwy). RównieŜ za słaby niezmiennik moŝe być powodem trudności pokazania trzeciej z implikacji, kończącej wywód. W naszym przypadku na przykład nieuwzględnienie w niezmienniku warunku spowodowałoby kłopoty z przekształceniem niezmiennika do postaci, z czego wynikała od razu teza. Z kolei za mocny niezmiennik oznacza kłopoty z jego inicjalizacją. Niezmiennik powienien być w sam raz. 5/26 Dowody poprawności, ze względu na ich nietrywialność, stosuje się dla prostych algorytmów. W praktyce nie dowodzi się poprawności większych programów. 6/26 2
Co z tym bezbłędnym programowaniem programowanie jest dziedziną wielce zróŝnicowaną, często wymagającą złoŝonej działalności intelektualnej. Przypuszczenie, Ŝe kiedykolwiek moŝna by jego naukę skondensować w postaci ścisłych recept wydaje się niesłuszne. Wirth, 1989 Nie istnieją Ŝadne absolutne reguły stylu programowania tak samo jak stylu pisania, poniewaŝ programowanie jest częściowo sztuką, a częściowo nauką. McCracken, Salmon, 1987 7/26 Etapy rozwoju programu Rozwiązanie zadania: 1. dokładne zdefiniowanie zadania 2. wyjaśnianie i usunięcie nieokreśloności w sformułowaniu problemu 3. wybór sposobu rozwiązania problemu 4. naszkicowanie rozwiązania w zrozumiałym zapisie - schemat rozwiązania 5. kodowanie (proces, tak naprawdę, mało twórczy ), czyli mechaniczny przekład rozwiązania problemu na poprawne gramatycznie zdania pewnego szczególnego języka (np. Pascala). 6. wyłapanie i usunięcie błędów z napisanego programu - odpluskwianie 7. napisanie i uzupełnienie dokumentacji programu 8. dokładne sprawdzenie (testowanie) programu 9. wdroŝenie i utrzymanie (konserwacja) programu 8/26 Definiowanie i szkic rozwiązania Faza definiowania problemu - częsty błąd => wpływ na cały proces!: niepełne zdefiniowanie zadania programistyczne bo nie do końca rozumiem (albo potrafię dogadać się ze zleceniodawcą) pozostawienie niejasności, np.: jest jeszcze jeden przypadek, ale rzadko występuje, potem ustalimy to ustalimy na końcu (postać raportu, eksportowanych danych, ) Szkic rozwiązania: program (prawdziwy, nie programik ): wiele oddzielonych, ale powiązanych ze sobą zadań, np.: System Magazynowy: rejestracja dokumentów przychód: rejestracja dokumentu (obrót materiałowy), zgłoszenie materiałowe zmiana stanu (kartoteka materiałowa) rozchód, zgłoszenie WaŜne: precyzyjnie określić zadania dla kaŝdej z części programu podać związki między tymi częściami co na wejściu, co na wyjściu Efekt: wykonanie kaŝdej z tych części z osobna =>> prawidłowa całość. 9/26 3
Algorytm i kodowanie Wybór i reprezentacja algorytmu jak program ma rozwiązać zadanie? algorytm: szczególny sposób rozwiązania problemu: dostępny w literaturze, bibliotekach algorytmów lub algorytm opracowany przez programistę reprezentacja: zapis schematyczny Kodowanie wybór języka (zaleŝnie od problemu) 10/26 Testowanie, dokumentacja Faza sprawdzania - testowanie wybrać takie dane, dla których potrafimy przewidzieć wyniki dokonać wszelkich moŝliwych testów programu. Dokumentacja wykonywana na bieŝąco (jest zajęciem ciągłym!), dokumentację tworzą wszystkie materiały z etapów poprzednich: szczegółowy opis problemu przedstawienie algorytmiczne zadania program jako taki. Dokumentacja informacje dla uŝytkownika dla programistów (ewentualne zmiany w przyszłości). 11/26 Testowanie wskazuje na obecność błędów, nie zaś na ich brak E. Dijkstra _ 12/26 4
Konserwacja i dostosowywanie Program to narzędzie, które naleŝy poprawiać: błędy w róŝnych fazach jego powstawania dostosowywać do aktualnej sytuacji: zmiany w przepisach, róŝnice w konfiguracji sprzętu komputerowego, róŝne urządzenia wejścia-wyjścia, róŝnice w realizacji języków programowania dla róŝnych maszyn, itp. Aktualizacja dokumentacji! kaŝda modyfikacja programu => dokładna informacja. Tylko dobrze zorganizowane programy, z przejrzystą dokumentacją, mają szanse na przetrwanie. 13/26 Czas (w przybliŝeniu) Definiowanie, analiza wymagań uŝytkownika: 10% Określenie funkcjonalności: 30% analiza zasobów systemowych, szkic dokumentacji, przewodnik uŝytkownika (pomaga w całym procesie pisania programu i jego konserwacji), analiza moŝliwych błędów i opis reakcji itp. Projektowanie programu: 20% wybór typów danych, algorytmu, podział programu na fragmenty, itd. Kodowanie: 15% zapis projektu w języku wyŝszego poziomu (Pascal, C++, itd.) Sprawdzanie poprawności: 15% kompilacja, usuwanie błędów, wykonanie obliczeń z danymi, które prowadzą do znanych wyników, z danymi błędnymi, przypadkowymi i rzeczywistymi Instalacja: 10% umieszczenie programu na komputerze klienta, innym niŝ ten, na którym został przygotowany, szkolenie personelu obsługującego program itd.): 10% Konserwacja programu: dalsze 100%... usługi gwarancyjne, inne błędy, usuwanie problemów związanych z nowym sprzętem itd. McCrackena i Salmon, 1987 14/26 Klasyfikacja błędów Błąd składniowy (błąd kompilacji) pomyłka (np. nieprawidłowo zapisaną nazwą, brak nawiasu itp. Rozwiązanie: wykrywa kompilator, przerywa kompilację, wskazuje miejsce, poprawia programista. Błąd czasu wykonywania (runtime error) nieoczekiwane przerwanie działającego programu, zdarzenie zewnętrzne zmusza program do zatrzymania, np. dane nieprawidłowego typu, niewykonalna operacja (/0), błąd zapisu Rozwiązanie: przewidzieć!, takŝe testować sprawdzać dane, oprogramować lub przejąć obsługę błędu (wyłączyć obsługę) Błąd logiczny błąd algorytmu (działa, ale źle) Rozwiązanie testować najlepiej usunąć przed oddaniem programu po przekazaniu: powtarzalność błędu określić sekwencję operacji poprawić algorytm lub wykluczyć przypadek dziedzinę) (ograniczyć algorytm zliczania zdań: sekwencja znaków. (kropka-spacja); Na rys. 2 pokazano... 15/26 5
Metody testowania Śledzenie pracy programu: WriteLine, msgbox 16/26 Narzędzia środowiska Debug: śledzenie pracy programu praca krokowa Step Into, Step Over punkty zatrzymania breakpoint wykonanie instrukcji i modyfikacja Immediate 17/26 Punkty kontrolne breakpoint Ustawienie F9 Wartości zmiennych Locals 18/26 6
Zatrzymanie okno Locals Wartości zmiennych okno Immediate 19/26 Praca krokowa punkt zatrzymania instrukcja do wykonania wskazanie zmiennej 20/26 Styl programowania McCracken, Salmon, 1987 Ogólne reguły: nazwy zmiennych w programie, wielkości stałe, liczba zmiennych globalnych, deklaracje typów zmiennych, odstępy, puste linie, akapity, inteligentne komentarze, podział na sekcje, podprogramy. 21/26 7
Nazwy zmiennych w programie ZaleŜnie od przeznaczenia: rzeczowniki dla oznaczania nazw wszelkich danych, czasowniki dla funkcji i procedur (rozkazy, np. dziel, dodaj itp), przymiotniki dla zmiennych logicznych. Mnemoniczne nazwy nazwy jednolite - mało czytelne l1, l2, l3, l55, l78, lepiej - nazwy, które coś oznaczają, powiadamiają programistę o moŝliwościach modułu wskazują sens stałych, zmiennych, typów itd. Bardzo długie nazwy mało praktyczne, nieczytelne (dodajdwieliczby) stosować wyróŝnienia! DodajDwieLiczby, dodaj_dwie_liczby. 22/26 Wielkości stałe Problemy: wielkości niecharakterystyczne stała pi, charakterystyczna zapisana z 9 miejscami zamienić na liczbę o 12 miejscach znaczących. rozmiar tablicy w programie (w modułach) [1..100]. automatyczna zamiana (edytor) 100 200, przy okazji, np. temperatura procesu 100 200? Oprócz 0, 1 i moŝe jeszcze kilku innych nie uŝywać wprost stałych w treści programu. zadeklarować (zgłosić) je w specjalnej części! 23/26 Zalecenia Minimalizować liczbę zmiennych globalnych zmiana globalna, łatwa pomyłka Deklarować typy zmiennych (using) podzakresy, definicje tablic, rekordów, plików itp. Stosować odstępy, wcięcia puste linie (jedna, dwie) POWINNY! oddzielać róŝne fragmenty programu i podprogramy wcięcia w instrukcjach złoŝonych. zdecydować się na jakiś styl przestrzegać go (jednolita postać programu). 24/26 8
Modularyzacja, komentarze Podział na moduły (podprogramy) podział zadania na części oprogramowanie i sprawdzenie części połączenie części (wg zasady dziel i rządź) spójność modułów jeśli uda się znaleźć słowo określające to co robi dany moduł, to oznacza, Ŝe funkcja jest spójna. przejrzystość komunikacji modułów Inteligentne komentarze x = 7 {Tutaj pod zmienną x podstawiamy 7 }??? 25/26 Im większy program im większy zespół tym waŝniejsze, by stosować ww. uwagi 26/26 9