...są tylko programy, w których na razie jeszcze nie znaleziono błędu. Takie powiedzenie dość często można usłyszeć wśród ludzi zajmujących się oprogramowaniem komputerów. Spróbujmy zastanowić się, co to , na pozór dziwne stwierdzenie może oznaczać dla programistów, a co dla użytkowników maszyn cyfrowych.
Zacznijmy tworzyć duży program. Początkiem takiego działania musi być zadanie, które ten program ma rozwiązywać. Jeśli jestem informatykiem hobbystą, to zwykle zadanie stawiam sobie sam.
Jeśli mam programowaniem zarobić na życie, to muszę rozwiązywać zadania postawione przez innych - przyszłych użytkowników mojego oprogramowania. Analizuję zadanie, tworzę koncepcję rozwiązania, przyglądam się jej, jeśli nie jest zadowalająca szukam lepszej.
Ostatecznie powstaje jakiś, mniej lub bardziej szczegółowy projekt, według którego następnie można pisać program. Potem przychodzi radosny moment napisania ostatniej instrukcji. Jako człowiek rozsądny, przed dalszymi eksperymentami zapisuję program w bezpiecznym miejscu, np. na dyskietce, jako człowiek doświadczony przez życie robię na drugiej dyskietce zapasową kopię tego zapisu i mogę powiedzieć: gotowe. Zwykle jednak radość jest krótkotrwała.
Mało prawdopodobne jest, że pisząc kilkaset linii programu nie pomylimy się ani razu. Toteż zwykle pierwsze próby uruchomienia naszego dzieła kończą się wypisaniem przez maszynę komunikatu o znalezieniu błędnych' konstrukcji języka programowania. A to przecinek zamiast kropki, a to znowu BGIN zamiast BEGIN. Zdarzają się również dużo bardziej wyrafinowane, a co za tym idzie trudniejsze do zauważenia, błędy składniowe, ale to trochę inny temat. Dla naszych dzisiejszych rozważań istotne jest, że wszystkie te błędy wykryje translator języka programowania i tym samym wymusi na nas ich usunięcie - bez tego nie da się wykonać programu. Zresztą dla osoby dobrze znającej język, w którym programuje, poprawienie tych błędów nie jest dużym problemem.
PROGRAM ZACZYNA DZIAŁAĆ
Ostatecznie, po kilku próbach i poprawkach nasz program wykonuje się od początku do końca i produkuje jakieś wyniki. Podkreślam JAKIEŚ, bo często zdarza się, że nie są to wcale takie wyniki, o jakich marzyliśmy. Może np. zdarzyć się, że odległość z miasta A do miasta B, obliczona przez program wynosi - 5 km, liczba murarzy potrzebna do zbudowania domu przez tydzień wyszła równa 1,48, a program, który miał być super wróżką, przewidział koniec świata na rok 1823 Wszystkie te wyniki mają jedną wspólną cechę: są mianowicie, delikatnie mówiąc, mało wiarygodne. Cecha ta skłania zwykle autora do wnikliwego przyjrzenia się programowi i wprowadzenia w nim pewnych zmian (najbardziej skrajnym przypadkiem takich zmian jest wyrzucenie całości i rozpoczęcie prac od punktu wyjścia).
Zwykle jednak i ten etap zostaje szczęśliwie pokonany. Program daje wyniki, które wyglądają przyzwoicie i chyba są dobre. Owszem, mogą być dobre, ale wcale nie muszą. W programie dalej mogą być błędy! Choćby tak! zamiast A=1000 jest A=10000, głupi drobny błąd, a wynik jest dziesięć razy za duży! Wniosek jest jeden: nawet wyników, które "na oko" są porządne, nie możemy od razu zaakceptować, trzeba je sprawdzić. Jak? Musimy wykonać program dla takich danych, dla których znamy poprawne wyniki i porównać rezultaty pracy programu z tym, o czym na pewno wiemy, że jest dobre. Często oznacza to, że nasze zadanie musimy sami rozwiązać "ręcznie", aby uzyskać odpowiedni materiał do prowadzenia testów programu. I tutaj zaczyna się równocześnie kilka wątków. Zajmijmy się nimi kolejno.
Jeśli okaże się, że program działa źle, zwykle stajemy przed poważnym problemem: dlaczego? Lokalizacja błędów tego typu jest często bardzo trudna, ta problematyka odbiega jednak od naszych dzisiejszych rozważań, zajmiemy się nią kiedy indziej.
Jeśli program jest pisany na zlecenie, to pomoc zleceniodawcy może być niezwykle przydatna przy sprawdzaniu poprawności. Np. może on dostarczyć danych do testów, z gotowymi poprawnymi wynikami. Zauważmy również, że jako przyszły użytkownik programu, zleceniodawca powinien (w swoim własnym interesie) dołożyć wszelkich starań aby upewnić się, że program który ma otrzymać działa poprawnie. Oczywiście szanujący się informatyk będzie starał się sam wykryć jak najwięcej błędów, żeby potem nie najeść się wstydu.
TRZY RAZY NIE
No i wreszcie rzecz najważniejsza: powiedzmy, że przetestowaliśmy nasz program na dziesięciu różnych zestawach danych i zawsze otrzymaliśmy właściwy wynik. Czy wiemy już, że nasz program jest w pełni poprawny? NIE, NIE i jeszcze raz NIE. Podchodząc do problemu ściśle (a takie podejście jest tu niezbędne) możemy stwierdzić tylko jedno: program działa poprawnie dla tych dziesięciu zestawów danych. Jakie będą wyniki dla innych danych - tego nie możemy być pewni.
Oczywiście, wiemy że program zbudowany jest zgodnie z logiką, więc jeśli liczy dobrze dla jednych danych, to tak powinno być dla innych. Na tej podstawie domyślamy się, wnioskujemy, mamy nadzieję, jesteśmy głęboko przekonani, że cały program jest poprawny, że dla wszystkich danych da dobre wyniki. Ale pewności mieć nie możemy, chyba że przetestujemy program na wszystkich możliwych zestawach danych, co jest zwykle nie do wykonania w praktyce, albo udowodnimy, że program jest zawsze poprawny, tak jak w szkole dowodziliśmy np. twierdzenia Pitagorasa. Tego zaś zwykle nie jesteśmy w stanie zrobić, bo mimo ogromnych wysiłków badaczy skierowanych na opracowanie skutecznych metod dowodzenia poprawności programów, praktyczne efekty są jak dotąd niewielkie.
Czy jednak cały ten problem nie jest trochę wydumany? Napisałem poprzednio "ściśle biorąc nie mamy zupełnej pewności". Takie sformułowania na milę pachną teoretyzowaniem. Na pewno nie, problemu poprawności, czy też niezawodności oprogramowania nie wymyślili teoretycy, choć, jak już wspomniałem, wielu z nich biedzi się teraz nad jego rozwiązaniem. Problem ten zauważyli praktycy, a może raczej odczuli go na własnej skórze i to często bardzo boleśnie. Oczywiście nie dotyczy on na ogół programów małych, mieszczących się w kilku liniach, lecz większych, liczących setki czy tysiące instrukcji. Jednak prosty, przykład, który zaraz podam może się przemknąć także w niedużym programie:
readln (a,b); {czyta wartości zmiennych a i b}
writeln (1/(a-b));
te instrukcje mogą się wykonywać poprawnie nawet całe lata, dopóty liczby wczytywane jako dane nie będą sobie równe. Wtedy koniec - próba dzielenia przez zero, program pada. Życie dostarcza zwykle dużo bardziej skomplikowanych przykładów konstrukcji, które są poprawne prawie zawsze, tylko w pewnych, szczególnych przypadkach dają bezsensowne wyniki. Co gorsza, często wcale nie powodują awarii programu - liczy się on do końca, tylko wynik jest bez sensu.
GDZIE SZUKAĆ RATUNKU?
Programy trzeba testować, testować wszechstronnie, metodycznie i oczywiście inteligentnie. Starać się rozpatrzeć wszystkie możliwe przypadki, szczególnie te złośliwe (jak w naszym przykładzie - gdy obie liczby równe), szczególnie te graniczne. Np. co będzie jak podam długość równą zero, albo baza danych - co się stanie jak usunę wszystkie rekordy - czy nie ulegnie dezorganizacji, itd. Należy też dokładnie zbadać odporność programu na błędy w danych - czy je wykrywa i właściwie sygnalizuje. Bardzo dobre efekty daje zwykle przetestowanie programu nie przez twórcą, lecz przez kogoś innego.
Testowanie musi być tym dokładniejsze i wszechstronne im więcej zależy od poprawnego działania programu. To nie żarty - np. od programu sterującego ruchem pociągów zależy życie setek ludzi.
Powiecie, że takie dokładne i wszechstronne testowanie pochłonie więcej czasu niż samo programowanie. I oczywiście macie rację! Według danych pochodzących z firm produkujących oprogramowanie, więcej niż połowa kosztów tegoż oprogramowania, to właśnie koszt testowania i poprawiania gotowych programów.
Ustaliliśmy, że programista powinien zrobić wszystko, aby jego programy byty wolne od błędów i że może się zdarzyć, że to nie wystarczy i błędy jednak wystąpią. Co w tej sytuacji powinien robić użytkownik oprogramowania?
Musi upewnić się, że rzeczywiście program był testowany, warto także przetestować go samodzielnie jeszcze raz. Choćby dlatego, że programista mógł pewne szczegóły zadania zrozumieć nieco inaczej niż zleceniodawca i program działa bezbłędnie, tylko... rozwiązuje nieco inne zadanie niż miał rozwiązywać. Nigdy i nikomu nie wolno bezkrytycznie akceptować danych tylko dlatego, że wyliczył je komputer.
I wreszcie, każdy kto korzysta z komputera musi pamiętać, że nie ma programów dobrych, za dobre uważa się programy, w których na razie nie znaleziono błędów.