dr inż. Konrad Sobolewski Politechnika Warszawska Informatyka 1
Komputer jest bardzo sprawnym urządzeniem do liczenia i przetwarzania informacji. Wykorzystanie komputera zgodnie z naszymi potrzebami wymaga wstępnego przygotowania go do pracy. Przygotowanie to polega na nauczeniu komputera, jakie ma kolejno wykonywać czynności, aby wynik jego działania nas satysfakcjonował. Zanim zajmiemy się omawianiem zasad uczenia komputera, musimy zapamiętać następujące twierdzenie:
Komputer sam nie myśli. Efekt działania komputera jest wynikiem naszego myślenia przy formułowaniu dla niego sekwencji zadań do wykonania. Im dokładniej i lepiej przemyślimy sposób rozwiązania danego problemu, tym szybciej i efektywniej otrzymamy wyniki. Jeżeli my popełnimy błąd, to również błędne będzie działanie komputera, a uzyskane wyniki nieprawidłowe.
Język naturalny jest siłą rzeczy niejednoznaczny. Wiele pozostawia interpretacji, wyczuciu. Komunikacja z komputerem jest o tyle trudniejsza, że nie pozostawia miejsca na domysły czy interpretacje. Ponadto chodzi o to, żeby nasze programy były uniwersalne, a ich wykonanie na każdym komputerze wyglądało tak samo albo różniło się co najwyżej nieznaczącymi szczegółami, wynikającymi ze specyfiki architektury komputera, a nie z istoty zapisu algorytmu.
Różnica między językiem programowania a językiem naturalnym polega na tym, że język naturalny jest zastany, podczas gdy język programowania możemy stworzyć od zera według naszego pomysłu. Użyjemy do tego metody gramatyk formalnych, które zostały wynalezione przez znanego językoznawcę Noama Chomsky'ego, który przez większość swojego życia próbował usystematyzować gramatykę języka angielskiego.
Każda przyjęta (określona) gramatyka powinna charakteryzować się następującymi cechami: Gramatyka wyrażeń powinna być jednoznaczna. Wartości wyrażeń powinny być zgodne z tradycją matematyczną, w szczególności należy zachować priorytety operatorów oraz wykluczyć napisy typu 2 - - 3. Nawiasy powinny wymusić dowolną kolejność, na której nam zależy. Operacje niełączne, takie jak odejmowanie i dzielenie powinny być lewostronnie łączne, czyli ich wykonywanie musi być od lewej do prawej (w szczególności np. 5-3-1 powinno dać wynik 1, a nie 3).
Na początku mamy problem... Potem pojawia się rozwiązanie ogólne, opisane werbalnie bez wchodzenia w szczegóły... Analizując je dostrzegamy charakterystyczne części, logicznie od siebie zależne... Zaczynamy uzmysławiać sobie szczegóły realizacji poszczególnych czynności... Szczegółowość podziału całego zadania na czynności i stopień dokładności opisu zależy od umiejętności i możliwości danego wykonawcy.
Osobie dorosłej wystarczy powiedzieć Włącz radio. Małemu dziecku trzeba już szczegółowiej: Krzysiu, podejdź do stolika pod oknem Na stoliku stoi czarna skrzynka ze srebrnymi guziczkami Jeden z guziczków ma czerwoną obwódkę Połóż na tym guziczku paluszek i mocno naciśnij Nie bój się włączyłeś radio I to jest właśnie algorytm......ale to już Państwo wiedzą po ostatnim wykładzie.
Algorytm ma to do siebie, że kolejne czynności należy wykonywać w określonym porządku. Przykład? Próba zrobienia JAJECZNICY od końca... Zwracamy uwagę na sposób zapisu algorytmu i jego szczegółowość tu musi być wszystko jasne. Oczekując na jakieś dane wejściowe trzeba określić ich typ, zakres, źródło, etc. Wykonując obliczenia trzeba podać wzór i określić jak ma zostać zinterpretowany wynik.
Kolejnym krokiem przygotowania komputera do wykonania działań rozwiązujących zadany problem jest zakodowanie algorytmu w postaci programu. Dla skrócenia zapisu zaczynamy korzystać ze znanych z algebry zapisów formuł obliczeń, wprowadzając zmienne reprezentujące określone wartości. Dla komputera konieczne jest precyzyjne określenie kolejności obliczeń oraz sposobu rozstrzygnięć sytuacji alternatywnych i obliczeń wielokrotnie powtarzanych.
Do jednoznacznego wyrażania tych wszystkich elementów algorytmu będzie służył język programowania. Jest to język skonstruowany z jednej strony na podobieństwo języka używanego przez człowieka, a z drugiej strony przez dokładne i jednoznaczne określenie reguł zapisu i ich znaczenia jest zrozumiały dla komputera. Programem będzie tekst zapisany w języku programowania opisujący wykonanie sekwencji czynności według zadanego algorytmu. Proces tworzenia programu jest zwany kodowaniem algorytmu.
W każdym programie możemy wyróżnić kilka charakterystycznych elementów składowych. Postać ich zapisu jest uzależniona od przyjętych konwencji w języku programowania. Elementami składowymi programu są: Stałe wartości wpisane bezpośrednio w tekst programu. Zmienne reprezentujące pojedynczą wartość, zmienianą w wyniku wykonywanych obliczeń. Dane wartości wprowadzane z otoczenia programu. Wyniki wartości wyprowadzane ze zmiennych programu do jego otoczenia. Instrukcje operacje określające wykonanie lub decyzję o wykonaniu jednostkowego obliczenia.
Zapis algorytmu w postaci sieci działań prezentuje graficznie przyszłą strukturę programu. Omawiając więc podstawowe struktury programów będziemy posługiwać się sieciami działań. Funkcjonalnie program składa się z 4 głównych modułów: Deklaracji zmiennych z nadawaniem im wartości początkowych Wprowadzania wartości danych Wykonywania obliczeń zasadniczych Wyprowadzania wartości wyników
Moduły podziału funkcjonalnego oraz części tych modułów tworzą jedną z czterech podstawowych struktur programów: O strukturze liniowej kolejne instrukcje tworzą sekwencję obliczeń O strukturze rozgałęzionej (decyzyjnej) w każdej z części niektóre z obliczeń są wykonywane przy spełnieniu obliczonych wartości warunków, a inne w przypadku ich niespełnienia O strukturze cyklicznej (iteracyjnej) część instrukcji jest wielokrotnie wykonywana aż do spełnienia określonego warunku O strukturze proceduralnej wyróżnione części programu (procedury, funkcje, podprogramy) są logicznie włączone w wykonanie różnych innych części programu.
Praktycznie rzecz biorąc w każdym programie można znaleźć każdy z rodzajów struktur w zawężeniu do jego modułów. Wyróżnienie tych struktur ma głównie na celu wyjaśnienie pierwszych podstawowych pojęć oraz zwrócenie uwagi na konieczność zachowania ładu w projektowanym algorytmie, a potem w zakodowanym programie. Jeżeli bowiem na danym stopniu szczegółowości opisu całego lub części algorytmu możemy przyporządkować mu odpowiednią instrukcję, to mamy dużą gwarancję, że nasz program będzie dobry.
Programowanie komputera jest zespołem czynności umożliwiających rozwiązanie zadanego problemu i obejmuje: ustalenie algorytmu, określenie sposobu i kontroli wprowadzanych danych i wyprowadzenia wyników, zapisanie algorytmu w języku zrozumiałym dla komputera oraz weryfikację poprawności działania programu. Z technicznego punktu widzenia warto jest też mieć wiedzą choćby ogólną z zakresu działania komputera.
Definicja pojęcia programowania nie wyjaśnia nam, jakie są kryteria oceny efektu programowania i jak powinien prawidłowo przebiegać ten proces. Wynikiem programowania jest zapis pewnego algorytmu w pewnym języku programowania. Niestety sam proces programowania nie jest w pełni algorytmiczny. Oprócz wielu zasad opracowanych na podstawie doświadczeń tysięcy programistów, w procesie programowania niezbędne jest logiczne i analityczne myślenie programującego, a pożądanymi cechami są też talent i zdolności. Zacznijmy jednak od podstaw.
Istnieje kilka metod konstruowania algorytmu, a na jego podstawie programu. Od ogółu do szczegółu czyli zadany problem dzielimy na logicznie domknięte moduły posługując się ogólnym, werbalnym opisem ich funkcjonowania. Następnie każdą z tych części dzielimy na coraz mniejsze moduły, uzyskując w rezultacie opis pojedynczych czynności w instrukcjach języka. Od szczegółu do ogółu czyli rozpoczynamy rozwiązanie problemu od skonstruowania obliczeń dla jego najbardziej istotnych fragmentów, koncentrując się na uzyskaniu gotowego zapisu. Następnie fragment ten, lub też inne podobnie uzyskane fragmenty, łączymy ze sobą dołączając instrukcje wprowadzania danych, wyprowadzania wyników oraz inne instrukcje pomocnicze.
W samej nauce programowania wyróżniamy: Konstruowanie algorytmów polegające na logicznym podziale zadanego problemu na pojedyncze czynności; Poznawanie i rozumienie algorytmów już istniejących i ich wykorzystywanie; Weryfikowanie poprawności algorytmu i jego zgodności z zadanym problemem; Poznawanie instrukcji języka ich wariantów i ograniczeń; Stosowanie standardowych rozwiązań niektórych modułów programu;
Wykorzystywanie funkcji dostarczonych standardowo z danym językiem programowania; Adaptowanie rozwiązań już istniejących; Efektywne wykorzystywanie funkcji systemu cyfrowego, jakim jest komputer i jego systemowe oprogramowanie; Uruchamianie gotowego programu, polegające na umiejętności wykrywania i usuwania błędów; Ocenę efektywności działania programu; Weryfikowanie poprawności programu; Prawidłową eksploatację gotowego programu; Dokumentowanie oraz rzetelne i zrozumiałe opisywanie zasad korzystania z programu.
Komputer posługuje się informacjami zakodowanymi w postaci sekwencji dwóch wartości 0 i 1. Również rozkazy są w ten sposób kodowane, a więc program wykonywany w komputerze musi być zapisany w kodzie (języku) wewnętrznym (maszynowym). Zewnętrzna, z założenia czytelna dla programisty, postać kodu wewnętrznego jest nazywana językiem asemblerowym (niskiego poziomu). W języku tym każdy rozkaz jest opisany mnemonicznym hasłem instrukcji, a większość odwołań do argumentów (ich adresów) jest zadawana symbolicznie.
Zapisywanie treści programu w języku asemblerowym jest dość uciążliwe i czasochłonne. Z czasem powstały więc języki programowania wysokiego poziomu, bardziej zrozumiałe dla programującego. W przeciągu dość krótkiego czasu stworzono na świecie ponad 1000 różnych języków programowania. Cechami rozróżniającymi je są: Odmienność sposobu zapisu niektórych konstrukcji algorytmów Uproszczenia zapisu konstrukcji typowych Podział na moduły Różny stopień wspomagania programisty w jego działaniach
W opisie języka programowania wyróżniamy opis składni i semantyki. Składnia języka jest zbiorem reguł określających prawidłowe postacie zapisu treści programu. Inaczej mówiąc, są to reguły ortografii i gramatyki specyfikujące poprawne postacie deklaracji instrukcji i programu. Semantyka języka jest zestawem znaczeń określających jednoznacznie sposób interpretacji poszczególnych instrukcji języka. W przypadku języka programowania semantyka precyzuje sposób zrozumienia każdego z elementów programu przez komputer.
Wiemy już, że funkcjonowanie komputera jest wynikiem wykonywania przez procesor kolejnych rozkazów pobieranych z pamięci operacyjnej. Nasuwa się zatem pytanie w jaki sposób można uzyskać sekwencję rozkazów dla każdej z instrukcji języka wysokiego poziomu? Translator jest częścią oprogramowania komputera i służy do przekształcenia źródłowego tekstu programu, zapisanego w określonym języku programowania, w równoważny funkcjonalnie program wykonywany bezpośrednio przez procesor.
Wyróżniamy dwie podstawowe, odmienne zasady funkcjonowania translatora: Interpretator jest translatorem zapisującym źródłowy tekst programu bezpośrednio w pamięci operacyjnej, a następnie wykonującym program poprzez interpretację semantyczną każdej zapisanej instrukcji wywołując odpowiednią procedurę. Kompilator jest translatorem tłumaczącym postać źródłową programu w postać wynikową jako sekwencję rozkazów zgodnie z przeznaczeniem semantycznym. Po wykonaniu kompilacji, gotowy, sprawdzony pod kątem poprawności składni, program może być wielokrotnie wykonywany.
Różnica jest w praktyce taka, jak np. przy tłumaczeniu tekstu z języka obcego na nasz. Interpreter to by była taka osoba, która odczytuje i tłumaczy kolejne punkty instrukcji, które po kolei realizujemy. Istotne jest tutaj, że kolejność wybierania tych punktów jest uwarunkowana stanem urządzenia, a interpretowanie tych samych punktów przez tę osobę może odbywać się wielokrotnie. Kompilator to taka osoba, która przetłumaczy nam tekst, oryginał do szafy, a posługujemy się tłumaczeniem. Druga wersja jest atrakcyjniejsza, ale weźmy poprawkę na zmiany oryginału dopóki go nie dopracujemy, to jej pełne tłumaczenie nie bardzo ma sens.
Spróbujmy na tym etapie podsumować dotychczas poznane tajniki nauki programowania: Fazy życia programu: Idea rozwiązania problemu przy użyciu komputera. Zaprojektowanie algorytmu. Zapisanie algorytmu w języku programowania jako programu. Wprowadzenie treści programu do pamięci operacyjnej. Translacja programu. Wykonanie programu z wprowadzaniem danych i wyprowadzaniem wyników.
Styl programowania jest pojęciem abstrakcyjnym, określającym zespół warunków, które determinują otrzymanie programu nie tylko poprawnego, ale również ładnego i eleganckiego pod względem zasad metodyki programowania. Rozważając te zagadnienia będziemy zajmować się zawsze programem poprawnym, czyli takim który: Dla dobrze określonych zbiorów danych dokonuje ich przetworzenia w zbiór wyników zgodnie z założonym algorytmem, opracowanym na podstawie specyfikacji problemu przewidzianego do rozwiązania z wykorzystaniem komputera; Dla wszystkich innych zestawów danych, niezgodnych z założeniami, spowoduje wyprowadzenie komunikatu o braku możliwości uzyskania wyników.
Dobór identyfikatorów. Najlepszą metodą jest przyjęcie nazewnictwa związanego z rozwiązywanym problemem. Należy unikać nadawania nazw takich samych lub podobnie brzmiących jak instrukcje w danym języku programowania. Zestaw danych. Deklarowanie zmiennych czy stałych należy wykonywać w jednym miejscu zazwyczaj na początku programu (procedury). Rzetelność danych. Należy dobrać typy danych pod kątem zakresu tak, aby zapewniały poprawne wyniki w całym zakresie danych wejściowych.
Komentarze. Program bez dokumentacji w postaci komentarzy jest wart niewiele. Nawet krótka przerwa w jego edycji powoduje, że musimy dużo czasu poświęcić na przypomnienie sobie co dany fragment programu robi i w jaki sposób. A już nie mówiąc o kimś, kto ten program dopiero co zobaczył. Struktura programu. Tekst programu powinien być wyraźnie podzielony na moduły wykonujące logicznie odrębne fragmenty algorytmu. Można ten efekt uzyskać stosownymi komentarzami, albo zabiegami formatującymi treść. Zapis formuł w postaci wyrażeń. Dla czytelności formuł warto korzystać z nawiasów grupujących czynniki. Czasem jest to wręcz konieczne. A czasem przyspiesza wykonanie obliczeń.
Stwierdzenia niektórych programistów, że napisali program i od pierwszego uruchomienia działał jak trzeba można włożyć na półkę między książki z kategorii science-fiction. Nie jest to niemożliwe, ale bardzo rzadkie i dotyczy jedynie bardzo prostych programów bazujących na znanych algorytmach. Błędy w dużej mierze wynikają z potrzeby bardzo szczegółowego opisania komputerowi co ma robić. Człowiek często myśli na skróty i stąd omija niektóre szczegóły (błędy w trakcie działania programu). Druga część błędów wynika z braku w wiedzy w zakresie używanego języka programowania (błędy składniowe).
W językach programowania wykorzystywane są różnego rodzaju oznaczenia symboliczne. Dzielą się one na trzy kategorie: Operatory operacji arytmetycznych i relacji Separatory rozdzielające elementy składni języka Atrybuty elementów języka W praktyce oznaczenie danego działania może być opisywane dwoma symbolami (np. := ) lub słownie (np. mod). Dokładny opis znaczenia omówiony będzie w praktyce na laboratorium w przyszłym semestrze. Niektóre na następnym wykładzie.
W zależności od wybranego języka programowania możemy spotkać się z różnymi rodzajami typów danych, z jakich możemy korzystać w programie. W praktyce różnice między językami programowania sprowadzają się do przyjętego sposobu nazewnictwa, ale też zdefiniowanych zakresów znaczeniowych. Ogólnie typy te dzielą się na: Liczbowe Tekstowe Logiczne Tablicowe
Oprócz poświęcenia uwagi na opracowanie skutecznego algorytmu, a później na dokonanie jego konwersji do postaci programu trzeba poświęcić też trochę uwagi na jego uruchamianie. Naszym dotychczasowym dążeniem było uzyskanie poprawnego merytorycznie i technicznie tekstu programu, uwzględniając w miarę możliwości dobry styl programowania. Czas zatem poświęcić chwilę na proces wprowadzania programu do komputera, wykonania oraz przetestowania dla stwierdzenia jego poprawności i zgodności z założeniami algorytmu rozwiązywanego problemu.
Prawdopodobieństwo uzyskanie bezbłędnego programu natychmiast po jego wprowadzeniu do komputera jest praktycznie bliskie zeru. Z tego powodu, już na etapie konstruowania algorytmu oraz jego zapisu w języku programowania musimy się przygotować do metodycznego poszukiwania istniejących błędów. W większości przypadków do tego celu można będzie wykorzystać sam komputer.
W uruchamianym programie mogą wystąpić błędy należące do jednej z trzech klas: Błędy składniowe polegające na niezgodnym ze składnią języka zapisem instrukcji (popularnie są to błędy ortograficzne); Błędy wykonywania polegające na żądaniu wykonania niedozwolonej w danym kontekście operacji; Błędy logiczne polegające na poprawnym funkcjonowaniu programu, ale wykonującym przetwarzanie dla innego niż założono problemu.
Błąd składniowy znajduje się zawsze w linii, w której został on zasygnalizowany. Przeważnie jest to: Błędne zapisanie słowa kluczowego lub też pozostała część linii nie jest zgodna ze składnią instrukcji określonej pierwszym słowem kluczowym; Opuszczenie elementu składni instrukcji lub też błędne jego zapisanie nieprawidłowy identyfikator zmiennej, zła postać stałej typu liczbowego lub tekstowego, brak operatora w wyrażeniu lub separatora na liście zmiennych czy liście wartości, itp.
Błąd wykonywania jest wykrywany przez sprawdzenie zgodności argumentów aktualnie wykonywanej operacji z jej kontekstem w pozostałej części programu. Błędy te mogą pojawić się zarówno przy pierwszym, jak i kolejnych wywołaniach danej instrukcji. Błąd jest sygnalizowany dla linii, w której został on wykryty, niemniej często jest on powiązany z wcześniej wykonywanymi instrukcjami. Przeważnie jest to brak definicji funkcji, deklaracji tablicy, niepoprawna lub niezgodna z oczekiwaniami wartość zmiennej, wyczerpanie obszaru pamięci przeznaczonego na przechowanie danej wartości tekstowej.
Błąd logiki programu może mieć swoją przyczynę: W błędnie zaprojektowanym algorytmie rozwiązania problemu nie uwzględniającym wszystkich możliwych warunków decydujących o kolejności przetwarzania danych i interpretacji wyników pośrednich; W błędnie zakodowanym algorytmie w postaci programu pomijającym niektóre fragmenty algorytmu lub ograniczającym jego możliwości. Ujawnienie tego typu błędów może nastąpić w trakcie wykonywania programu w postaci błędów wykonywania, a jedyną metodą stwierdzenia braku takich błędów jest całkowite przetestowanie programu z możliwymi danymi wejściowymi.
Zatem można podsumować, że brak błędów składniowych i wykonywania nie świadczy o braku błędów logiki. Dopiero przeprowadzenie testów i uzyskane w nich wyniki mogą doprowadzić do stwierdzenia o poprawności działania programu. Bywa to czasem znacznie bardziej czasochłonne i kosztowne. Do wykrywania błędów logiki algorytmu i programu używa się różnych metod opartych o tablice decyzyjne i definicje niezmienników dla wykonywanych obliczeń. Jedno i drugie wykracza sporo poza ramy podstaw, więc nie będziemy tego rozwijać teraz.
Testowanie programu polega na jego wykonywaniu dla specjalnie opracowanych danych testujących umożliwiających zweryfikowanie poprawności poprzez: Sprawdzenie działania programu w warunkach granicznych lub dla osobliwych wartości danych Oszacowanie dokładności obliczeń Sprawdzenie odporności programu na błędy Sprawdzenie wszystkich dróg sterowania Wymaga to opracowania kompleksowego zestawu testów, których dobór i opracowanie nie jest zagadnieniem uniwersalnym.
Podstawowymi zasadami przy generowaniu testów są: Test powinien sprawdzać działanie programu w warunkach granicznych czyli wybieramy wartości ekstremalne oraz zero. Kolejne testy w zestawie powinny się różnić tylko w niektórych wartościach dla sprawdzenia różnic w wartościach wyników. Na tej podstawie możemy oszacować dokładność, z jaką będzie trzeba zadawać wartości danych dla uzyskania pożądanej dokładności wyników. Pożądane jest, aby dla każdego zestawu danych testujących zostały oszacowane prawdopodobne wartości wyników, jakie powinny być uzyskane. Dobrze też jest znać wartości wyników pośrednich w określonych punktach programu.
Należy ograniczać czas wykonywania programu dla danych testujących poprzez minimalizowanie iteracji, liczby przetwarzanych wartości, wielkości rozmiarów analizowanych tablic czy długości przetwarzanych napisów. Część testów powinna zawierać wartości danych niezgodne z założeniami rozwiązywanego problemu. W tym przypadku wykonanie programu powinno spowodować wyprowadzenie odpowiednich komunikatów sygnalizujących odporność programu na błędy. Pełny zestaw testów powinien weryfikować wszystkie istniejące drogi sterowania w programie. Stąd też przygotowując testy należy przeanalizować sieć działań zaznaczając w niej, który zestaw testów weryfikuje daną drogę w sieci.
W procesie uruchamiania programu możemy wyróżnić dwa etapy: Usuwanie z programu błędów składniowych i błędów wykonywania. Testowanie programu z usuwaniem błędów logicznych programu. Usuwanie błędów z programu polega na uruchomieniu programu z bardzo prostymi zestawami danych w celu sprawdzenia poprawnego zapisania tekstu programu. Wykorzystywane zestawy danych powinny umożliwiać sprawdzenie praktycznie każdej drogi sterowania w programie. Uzyskiwane wyniki programu tymczasem nas nie interesują.
Dla pierwszego etapu uruchamiania programu warto jest przygotować testujący zestaw danych wewnętrznych, aby nie trzeba było go wielokrotnie wprowadzać dla kolejnych prób uruchomienia programu. Jednocześnie staramy się minimalizować ilość tych danych oraz czas działania programu. I w tym momencie z nawiązką zwraca się używanie dobrego stylu w programowania oraz parametryzacji programu. Z drugiej strony czytelność i łatwość modyfikacji tekstu programu daje nam szansę prostego przystosowania programu do częstych i krótkich w działaniu wykonań.
Po wstępnym przystosowaniu programu do akceptacji prostych zestawów danych przystępujemy do uruchamiania programu. W większości języków programowania wystąpienie błędu przerywa dalsze jego wykonywanie. Po poprawieniu błędu składniowego możemy wznowić jego działanie zazwyczaj jest to uruchomienie od początku. Przy okazji zatrzymania można (i czasem warto) przyjrzeć się zawartości niektórych zmiennych: Przechowujących dane programu Sterujących w instrukcji cyklu Ostatnio obliczanych w poprzednich instrukcjach
Po usunięciu błędów składniowych i wykonywania (tutaj procedura jest identyczna) przechodzimy do drugiego etapu uruchomienia programu testowania w celu weryfikacji jego poprawności. Przed rozpoczęciem tego etapu powinniśmy dokonać tzw. instrumentacji programu, która polega na świadomym wprowadzeniu do tekstu programu instrukcji umożliwiających stałe kontrolowanie jego działania. Elementami tymi są: Wydruki wartości pośrednich Instrukcje wstrzymywania (break point) Instrukcje śledzenia działania programu
Wykorzystując wszystkie trzy, lub tylko niektóre, metody instrumentacji programu odpowiednio przygotowane zestawy danych testujących kolejno weryfikujemy poprawność fragmentów programu. Po wykonaniu testów i stwierdzeniu, że program na 99% jest poprawny powinniśmy usunąć elementy jego instrumentacji i można taką wersję programu udostępniać dalej. Niemniej warto zachować wersję zinstrumentalizowaną, ponieważ zawsze zostaje ten 1% prawdopodobieństwa, że w trakcie regularnego korzystania pojawi się sytuacja awaryjna i wtedy mając taką wersję łatwiej i szybciej będziemy mogli załatać dziurę.
A co zrobić, jeśli program działa błędnie, mimo naszych usilnych starań? Propozycje są następujące kolejno: Uzbroić się w cierpliwość i raz jeszcze spokojnie przejrzeć tekst programu, najlepiej z drugim programistą, wyjaśniając mu przeznaczenie każdej instrukcji przez nas napisanej; często mamy przeświadczenie o poprawności działania fragmentu programu, a niestety rzeczywistość jest inna. Na podstawie tekstu programu należy odtworzyć sieć działań porównując ją następnie z wersją przez nas na początku skonstruowaną dla danego algorytmu; często wychodzą różnice. Nie wstydzić się i poprosić o pomoc bardziej doświadczonych programistów; niektórzy mogli mieć podobny problem i znają remedium.
Napisać jeszcze raz porządnie program od początku, korzystając ze zdobytych już doświadczeń i stosując wszelkie kanony dobrego stylu programowania; ładne i eleganckie programy to podstawa. Jeżeli żadna z powyższych rad nie przyniesie pozytywnego rezultatu, można zacząć podejrzewać używany przez nas komputer oraz wykorzystywane oprogramowanie o błędne funkcjonowanie; warto zatem spróbować uruchomić program na innej maszynie, w innym środowisku tego samego języka programowania.