R05-03.pdf
(
482 KB
)
Pobierz
Microsoft Word - R05-03.DOC
1
Rozdział 5.
Uruchamianie, śledzenie
przebiegu i testowanie
aplikacji
2
Po skompletowaniu kodu źródłowego projektu, jego skompilowaniu, skonsolidowaniu i uruchomieniu
przychodzi zazwyczaj czas pierwszych rozczarowań – stworzona z takim trudem aplikacja, o ile w
ogóle nie odmawia współpracy z użytkownikiem, zamiast oczekiwanych danych produkuje przeważnie
komunikaty o błędach. Scenariusz taki wpisany został już na dobre w realia życia zawodowego
programistów projektantów i pozostanie aktualny tak długo, dopóki w drastyczny sposób nie zmieni się
technologia tworzenia oprogramowania dla komputerów.
Etymologia angielskiej nazwy
debugging
, określającej całokształt postępowania zmierzającego do
zlokalizowania i poprawienia błędnych elementów aplikacji, nie doczekała się jednolitego wyjaśnienia.
Najczęściej powtarzająca się wersja, zgodnie z którą zabłąkana pomiędzy kable komputera p l u s k w a
(ang.
bug
) spowodowała jego błędne działanie, kwestionowana bywa przez autorytety powołujące się
na dokumentację ikonograficzną; wiele materiałów źródłowych przypisuje autorstwo samego terminu
bug
pani admirał Grace Hopper
1
, która 9 września 1945 r. odnalazła martwą ćmę (mola?) między
stykami jednego z tysięcy przekaźników ówczesnego komputera Mark II. Prawdopodobnie jednak dla
Czytelników książek takich jak niniejsza techniczne szczegóły „odpluskwiania” aplikacji są znacznie
bardziej interesujące od samej nazwy, swoją drogą dość trafnie odzwierciedlającej charakter zmagania
się programistów z oporną rzeczywistością.
Równie niejednoznaczną kwestią jest sama definicja „błędu” aplikacji – okazuje się, iż
zaklasyfikowanie takich czy innych aspektów funkcjonowania aplikacji jako „błędy” zależne jest od
punktu widzenia projektanta, użytkownika itp.
2
Abstrahując w tym miejscu od ścisłej, teoretycznej
definicji „błędu” ograniczymy się do stwierdzenia, iż najpowszechniej popełniane przez programistów
błędy, nie pozostawiające wątpliwości co do swojego charakteru, zaliczyć można do jednej z
poniższych kategorii:
•
syntaktyczne
– polegają na wprowadzeniu do kodu programu konstrukcji niezgodnych ze
składnią języka; są automatycznie wykrywane przez kompilator i dla programistów
wystarczająco znających tę składnię nie stanowią żadnego problemu;
•
konstrukcyjne
– są wynikiem nieprawidłowej procedury przekształcania kodu źródłowego w
postać binarną, na przykład wykonania kompilacji w trybie Make w sytuacji, gdy konieczny
jest tryb Build, bądź nieprawidłowego określenia ścieżek dostępu do bibliotek i plików
dołączanych. Do tej kategorii zalicza się także nieuwzględnienie przez kompilator zmian
wprowadzonych do kodu podczas kompilacji „w tle”;
•
semantyczne
– sprowadzają się do użycia poprawnych składniowo elementów języka o
znaczeniu innym niż zamierzone (na przykład operatora „
=
” zamiast „
==
”) oraz opuszczenia
pewnych niezbędnych konstrukcji (na przykład inicjalizacji zmiennych). Większość z nich nie
jest wykrywana w sposób automatyczny;
1
Grace Hopper
(1906-1992), komandor amerykańskiej marynarki, matematyk, absolwentka Vassar
College i Yale. Jedna z pierwszych programistek współczesnych komputerów (Mark I, II i III), w latach
sześćdziesiątych XX w. kierowała rządowym (USA) zespołem normalizacyjnym języka Cobol (przyp.
tłum. na podstawie Wielkiej Internetowej Encyklopedii Multimedialnej
http://wiem.onet.pl
).
2
Interesujące wywody na ten temat znaleźć można w książce G.J.Myersa „Projektowanie niezawodnego
oprogramowania” wyd. pol. WNT 1980, ISBN 83–204–0224–7 (przyp. tłum.)
3
•
algorytmiczne
– polegają najczęściej na właściwej realizacji niewłaściwej koncepcji, czego
przykładem może być posortowanie elementów listy w kolejności rosnącej długości, podczas
gdy wymagana jest kolejność malejąca. Błędy tej kategorii są najtrudniejsze do wykrycia;
•
kontekstowe
– powodują produkowanie błędnych wyników przez poprawne procedury, na
skutek niewłaściwych danych lub przekazania tych danych w niewłaściwy sposób;
•
bilansowania
– związane są z wykorzystaniem zasobów przez aplikację, a ściślej brakiem
komplementarności działań tej ostatniej, czego przykładem może być niezwolnienie
przydzielonej pamięci, niezamknięcie otwartego pliku czy też brak dekrementacji licznika
odwołań współdzielonego obiektu po jego wykorzystaniu;
•
interfejsu
– stanowią pogwałcenie zasad wynikających z założonych protokołów współpracy
pomiędzy aplikacjami, bądź częściami danej aplikacji;
•
efekty uboczne
– powodują niepożądane efekty w otoczeniu aplikacji, na przykład niszczenie
zawartości obszarów pamięci przynależnych do systemu albo innych aplikacji lub dostęp do
współużytkowanych plików bez przestrzegania założonych procedur synchronizacyjnych.
W uzupełnieniu wymienić można wiele innych, rzadziej spotykanych okoliczności, jak na przykład nie
nadający się do współpracy interfejs użytkownika (o czym pisaliśmy w rozdziale 3.) czy też niemożliwe
do zaakceptowania ślamazarne tempo pracy aplikacji.
Jak wynika z powyższego zestawienia, natura większości błędów wyklucza możliwość efektywnego
wykrywania ich w sposób automatyczny, należy więc walkę z błędami uczynić integralną częścią
działań projektowych, bez złudnych nadziei, iż wyręczy nas w tym kompilator czy konsolidator.
Szanse programistów na tworzenie bezbłędnego kodu zwiększają się dzięki oferowanym przez
C++Buildera narzędziom wspomagającym proces kodowania, opatrzonych wspólną nazwą
Code
Insight
. Ułatwiają one tworzenie bezbłędnych konstrukcji dzięki m.in. funkcjom automatycznego
uzupełniania kodu, podpowiedziom dotyczącym deklaracji używanej właśnie funkcji, wzorcom kodu
itp.
Projektowe uwarunkowania śledzenia aplikacji
Jak już wspomnieliśmy, wobec nieuchronności popełniania błędów programistycznych walkę z tymi
błędami należy uwzględnić już w początkowych stadiach tworzenia aplikacji. To enigmatyczne
stwierdzenie przekłada się w praktyce na działania dwojakiego rodzaju. Po pierwsze, uciekając się do
działań obarczonych znikomym ryzykiem błędu, zwiększamy szansę na bezbłędność produktu
końcowego – i tak na przykład wykorzystanie gotowych, gruntownie przetestowanych bibliotek,
udostępniających środki dla realizacji określonego aspektu funkcjonalnego aplikacji, jest posunięciem
zdecydowanie pewniejszym niż samodzielne tworzenie takowych bibliotek
ab ovo
. Bowiem jakby na
przekór znanemu przysłowiu, iż „toczący się kamień nie obrasta mchem”, intensywne prace projektowe
– niby przysłowiowe toczenie kamienia – niosą ze sobą groźbę równie intensywnego obrastania
„mchem” popełnianych błędów.
Po drugie, popełniane przez programistów błędy stają się mniej groźne, jeżeli manifestują się w
aplikacji w sposób wyraźny – na przykład w postaci komunikatu, iż wartość, stanowiąca (zgodnie z
algorytmem obliczeń) kwadrat liczby rzeczywistej jest ujemna, a wskaźnik do struktury zawierającej
dane wejściowe jest wskaźnikiem pustym. Należy również dążyć do takiego stylu kodowania, by jak
4
najwięcej pomyłek popełnianych przez programistów przekładało się na błędy syntaktyczne, jak
wiadomo wykrywalne w stu procentach. W charakterze przykładu rozpatrzmy częsty błąd, polegający
na gubieniu jednego znaku „=” w operatorze porównania. Konstrukcja:
if (x == 5)
...
zamienia się wówczas w instrukcję przypisania:
if (x=5)
...
zmieniającą niezauważalnie wartość zmiennej x, co może być naprawdę trudne do wykrycia, lecz już
równoważna konstrukcja:
if (5 == x)
w przypadku opuszczenia jednego ze znaków „
=
” zamienia się w konstrukcję błędną
syntaktycznie. Wybierając drugi ze wskazanych sposobów porównania zmiennej
x
z wartością 5,
zwiększamy w znacznym stopniu szansę na wykrycie ewentualnej pomyłki – nosi to znamiona swoistej
działalności „obronnej” przed skutkami ludzkiej niedoskonałości i z tego względu zyskało sobie
powszechnie miano programowania defensywnego.
Problematyce programowania defensywnego i ogólnie całokształtowi działań programistycznych,
przyczyniających się do tworzenia bezbłędnych aplikacji, poświęcona jest w całości książka Steve’a
Maguire’a
Writing solid code
, której polskie wydanie (pod tytułem
Niezawodne programowanie
)
przygotowywane jest właśnie w wydawnictwie Helion – przyp. tłum.
Możliwym źródłem błędu są również luki organizacyjne przy zespołowym tworzeniu projektu.
Programista, przystępując do wprowadzania zmian w istniejącej koncepcji, powinien poinformować o
tym swoich kolegów, którzy tym samym uniknąć mogą błędów, polegających na wykorzystywaniu
nowych konstrukcji na starą modłę. Może się wówczas także okazać, iż jakiś nowy mechanizm,
implementowany właśnie przez jednego z programistów, został już wcześniej zrealizowany przez kogoś
innego; utrzymywanie dwóch jego różnych implementacji nie sprzyja bynajmniej redukcji
popełnianych błędów (nie wspominając już o zwykłej stracie czasu programisty).
Naturalnym sojusznikiem programistów w walce z błędami jest przejrzysty i czytelny styl kodowania,
któremu poświęciliśmy rozdział 2. książki. Tworzony kod powinien być w miarę możności kodem
samodokumentującym, wszelkie „nieoczywiste” konstrukcje powinny być więc w sposób zrozumiały
skomentowane. Należy przy tym tak dobierać sposób kodowania i treść komentarzy, by były one
zrozumiałe przez innych programistów, a nie tylko przez programistę kodującego (który, w przeciwnym
razie, czytając swój własny produkt po upływie np. pół roku, mógłby sam mieć poważne kłopoty z jego
zrozumieniem).
Szczególnie istotnymi dla poprawności tworzonego kodu są te elementy, których w nim po prostu nie
ma – mowa tu o przyjętych milcząco specyficznych założeniach, przy których działać ma aplikacja, a
także o sytuacjach, które zdaniem programistów nie mają prawa wystąpić. Tak się jednak składa, iż
uwarunkowania zewnętrzne szybko się zmieniają (przekonali się o tym niedawno programiści Turbo
Pascala, gdy procesor Pentium II okazał się zbyt szybki dla funkcji skalującej czasomierz programowy,
5
powodując w efekcie błąd wykonania 200 – przyp. tłum.), zaś sytuacje uznane dotąd za niemożliwe
stają się całkiem realne. Programista utrzymujący aplikację powinien więc być świadom w k ażdym
czasie wspomnianych założeń, które tym samym powinny być wyraźnie udokumentowane w formie
komentarzy, a najlepiej dodatkowo weryfikowane za pomocą stosownych testów. Należy również
liczyć się z tym, iż domyślne ustawienia opcji, związanych z różnymi aspektami projektu (kompilacją,
konsolidacją itp.), mogą ulec zmianie w przyszłych wersjach wykorzystywanego języka
programowania.
Poprawiając dostrzeżone błędy, należy jednocześnie uważać, by przy tej okazji nie wprowadzić nowych
– wieszając kolejną bombkę na choince, należy uważać, by przy okazji nie stłuc pięciu już wiszących;
aktualna staje się w tym momencie zasada, zgodnie z którą „jeżeli coś działa, to nie należy tego
poprawiać”.
Programistyczne uwarunkowania śledzenia
aplikacji
Zgodnie z tym, co przed chwilą powiedzieliśmy, walka z błędami, które pojawią się w tworzonym
projekcie, powinna być organicznie wpisana w sam proces projektowania aplikacji; błędy
programistyczne są bowiem zjawiskiem na tyle powszechnym, iż niedopuszczalne jest traktowanie ich
jako jedynie swego rodzaju „wypadków przy pracy”, których skutki zniwelować można za pomocą li
tylko debuggera. Najdalej nawet posunięta staranność i „defensywność” przy tworzeniu projektów nie
jest jednak w stanie całkowicie zapobiec wszystkim błędom (ani nawet ich większości) – wtedy właśnie
debuggery stają się najważniejszymi narzędziami, ułatwiającymi zlokalizowanie błędu i jego usunięcie.
Podobnie, jak nie należy pozostawiać zagadnienia błędów programistycznych „na uboczu”
zasadniczych prac projektowych, podobnie nie należy odkładać „na później” poprawiania błędów już
znalezionych – świadomość, iż po ukończeniu tworzenia aplikacji pozostaje jeszcze usunięcie kilku
błędów, utrudnia określenie chociażby terminu ukończenia projektu, trudno bowiem a priori określić,
ile czasu zajmie walka z błędami.
Należy ponadto dokumentować wszelkie stwierdzone błędy, zarówno w formie odrębnej listy, jak i
innych środków bezpośrednio odnoszących się do kodu źródłowego, jak np. znaczników „To-Do”.
Lista zauważonych błędów nie zawsze musi być postrzegana jako „lista złych wiadomości”,
szczególnie jeżeli względnie szybko przekształca się ona w listę błędów poprawionych i jeżeli
(zgodnie z wytycznymi poprzedniego akapitu) nie będzie nigdy listą zbyt długą.
Przed przystąpieniem do modyfikacji kodu należy bezwzględnie sporządzić kopie zapasowe
modyfikowanych modułów – w przypadku totalnego zagubienia się w kodowaniu będziemy mieli
wówczas przynajmniej możliwość powrotu do stanu wyjściowego. Zadanie to ułatwiają różnorodne
systemy kontrolowania wersji.
I jeszcze jedno: człowiek z reguły uczy się na własnych błędach, należy więc starać się unikać
powtórnego popełniania tych samych błędów w tworzonych aplikacjach.
W tym miejscu Autor oryginału poleca ulubioną przez siebie książkę Steve’a McConnella
Code
Complete
wydaną przez w 1993 przez Microsoft Press (ISBN 1–55615–484–4), nazywając ją wprost
„biblią dla programistów”. Jego zdaniem książka ta porusza wszelkie aspekty konstruowania kodu
źródłowego i jest bardzo przyjemna w czytaniu.
Plik z chomika:
lightlady1
Inne pliki z tego folderu:
R01-03.pdf
(627 KB)
R04-03.pdf
(571 KB)
R02-03.pdf
(497 KB)
R06-03.pdf
(612 KB)
R05-03.pdf
(482 KB)
Inne foldery tego chomika:
apply
bench
Borland C++ - Lekcje
C++
c++ - b.sobczak
Zgłoś jeśli
naruszono regulamin