Rozdział 10.
Wykrywanie błędów w aplikacjach
Jedną z ważnych cech zintegrowanego środowiska Delphi jest jego debugger. Narzędzie to umożliwia ustawianie punktów przerwań, podgląd stanu zmiennych, kontrolowanie obiektów, a także przeprowadzanie wielu innych operacji, dzięki którym można (względnie) szybko zorientować się, co dzieje się (lub nie dzieje) z badanym programem. Dobry debugger ma niebagatelne znaczenie dla efektywnego tworzenia aplikacji.
Tak się niestety składa, że sama czynność śledzenia programu i wyszukiwania błędów jest przez wielu programistów traktowana marginalnie. Ja sam, kiedy rozpocząłem programowanie dla Windows, przez długi czas ignorowałem debugger, gdyż byłem pochłonięty nauką samego programowania. Kiedy jednak odkryłem, jak cennym narzędziem jest dobry program wykrywania błędów, poczułem się trochę głupio z powodu unikania go przez tak długi czas. Czytając tę książkę dysponujesz luksusem uczenia się na moich błędach. Z tego rozdziału dowiesz się, jakie możliwości daje debugger.
Debugger wchodzący w skład środowiska IDE udostępnia kilka cech i narzędzi, które pomagają w pracy przy wykrywaniu błędów. W dalszej części omówione zostaną następujące zagadnienia:
Elementy menu debuggera
Punkty przerwań
Nadzorowanie zmiennych przy pomocy listy wyrażeń testowych (Watch List)
Nadzorowanie obiektów przy pomocy Inspektora Śledzenia (Debug Inspector)
Inne narzędzia wykrywania błędów
Praca krokowa
Techniki wykrywania błędów
Po co stosować debugger?
Najprościej mówiąc - po to, aby pomóc sobie przy wykrywaniu błędów w pracy programu. Niemniej jednak proces śledzenia służy nie tylko do odnajdywania i korygowania błędów - jest to również narzędzie wspomagające rozwój oprogramowania. Mimo, iż proces wykrywania błędów ma duże znaczenie, wielu programistów nie poświęca wystarczająco dużo czasu na przyswojenie sobie zasad korzystania z wszystkich cech zintegrowanego debuggera; w efekcie przysparzają oni sobie wielu kosztów i straty czasu, nie mówiąc już o frustracji wynikającej z trudnych do znalezienia błędów.
Generalnie, uruchomienie programu pod kontrolą debuggera rozpoczyna sesję wykrywania błędów; w środowisku z debuggerem zintegrowanym (jak np. Delphi) debugger ten aktywizowany jest już w momencie uruchomienia programu (co, jak wiadomo, następuje w wyniku naciśnięcia klawisza F9, lub wybrania opcji Run|Run menu głównego, bądź też kliknięcia w przycisk Run na pasku narzędzi.)
Elementy menu związane
z procesem śledzenia
Przed przystąpieniem do omawiania szczegółów procesu wykrywania błędów przyjrzyjmy się poleceniom menu głównego, związanym z debuggerem. Niektóre z tych poleceń należą do kategorii Run głównego menu, pozostałe znaleźć można w menu kontekstowym Edytora Kodu. Elementy menu kontekstowego Edytora Kodu (specyficzne dla debuggera) zestawione zostały w tabeli 10.1.
Tabela 10.1. Elementy menu kontekstowego Edytora Kodu związane ze śledzeniem programu
Element |
Klawisz skrótu |
Opis |
Toggle Breakpoint |
F5 |
Ustawia lub likwiduje punkt przerwania w bieżącej linii Edytora Kodu. |
Run to Curso |
F4 |
Uruchamia program i wykonuje go aż do |
Inspect |
Alt+F5 |
Otwiera okno Inspektora Śledzenia |
Goto Address |
Ctrl+Alt+G |
Umożliwia określenie adresu w programie, od którego wznowiony zostanie proces |
Evaluate/Modify |
Ctrl+F5 |
Umożliwia podgląd i/lub modyfikację zmiennej w trakcie pracy programu. |
Add Watch at Cursor |
Ctrl+F7 |
Dodaje zmienną wskazywaną przez kursor do listy wyrażeń testowych. |
View CPU |
Ctrl+Alt+C |
Wyświetla okno kodu w postaci asemblera. |
Menu Run zawiera kilka poleceń odnoszących się do programów pracujących pod kontrolą debuggera. Polecenia te pozwalają, między innymi, uruchomić program pod kontrolą debuggera, przerwać wykonanie programu pracującego pod debuggerem --> [Author:J] , a także określić parametry przekazywane do programu z poziomu wiersza poleceń. Niektóre polecenia tego menu są zdublowane w menu kontekstowym Edytora Kodu. Polecenia menu Run, kontrolujące operacje wykrywania błędów, zostały zestawione w tabeli 10.2.
Tabela 10.2. Elementy menu Run, kontrolujące operacje wykrywania błędów
Element |
Skrót |
Opis |
Run |
F9 |
Kompiluje program (jeżeli zachodzi taka potrzeba) i uruchamia go pod kontrolą |
Parameters |
(brak) |
Umożliwia wpisanie parametrów wiersza poleceń dla aplikacji i przypisania aplikacji nadrzędnej podczas śledzenia biblioteki DLL. |
Step Over |
F8 |
Wykonuje linię kodu źródłowego, wskazywaną przez punkt wykonania, po czym zatrzymuje się w następnej linii kodu. |
Trace Into |
F7 |
Wchodzi do wnętrza procedury/funkcji napotkanej w punkcie wykonania. |
Trace to Next Source Line |
Shift+F7 |
Powoduje przejście punktu wykonania do następnej linii w kodzie źródłowym programu. |
Run to Cursor |
F4 |
Uruchamia program i wykonuje go aż do osiągnięcia linii kodu (w oknie edytora), w której znajduje się kursor. |
Show Execution Point |
(brak) |
Wyświetla punkt wykonania programu w Edytorze Kodu, przewijając kod w oknie jeżeli jest to niezbędne. Działa tylko w stanie zatrzymania programu. |
Program Pause |
(brak) |
Zatrzymuje wykonanie programu, kiedy tylko sterowanie osiągnie kod posiadający swą reprezentację w kodzie źródłowym. |
Program Reset |
Ctrl+F2 |
Bezwarunkowo zatrzymuje program i zwraca sterowanie do środowiska IDE Delphi. |
Inspect |
(brak) |
Wyświetla okno dialogowe inspekcji, |
Evaluate/Modify |
Ctrl+F7 |
Wyświetla okno dialogowe |
Add Watch |
Ctrl+F5 |
Wyświetla okno właściwości podglądu. |
Add Breakpoint |
(brak) |
Wyświetla podmenu zawierające polecenia dodania punktu przerwania do kodu źródłowego, adresu, danej lub modułu. |
W trakcie testowania swojej aplikacji będziesz często korzystał z tych poleceń, powinieneś więc również zaznajomić się z klawiszami skrótu operacji wykrywania błędów. Przyjrzyjmy się teraz punktom przerwania i możliwościom wykorzystania ich w programie.
Punkty przerwań
Kiedy uruchomisz swój program w zintegrowanym środowisku Delphi, zostanie on wykonany z pełną szybkością, zatrzymując się jedynie w miejscach, w których ustawione zostały punkty przerwań.
|
Punkt przerwania jest to znacznik nakazujący debuggerowi zatrzymanie procesu wykonania programu w chwili osiągnięcia tego znacznika. |
Ustawianie i usuwanie punktów przerwań
Ustawienie punktu przerwania wymaga kliknięcia myszą na obszarze guttera na wysokości linii, w której chcesz zatrzymać wykonanie programu (gutterem nazywany jest szary pasek należący do lewej krawędzi okna Edytora Kodu). Ustawienie punktu przerwania jest sygnalizowane ikoną (czerwonym kołem) na pasku guttera oraz podświetleniem linii na czerwono. Ponowne kliknięcie na ikonie punktu przerwania spowoduje jego usunięcie. Punkt przerwania może być również ustawiony lub usunięty z poziomu klawiatury (klawisz F5) lub z poziomu menu kontekstowego Edytora Kodu.
|
Punkt przerwania może być ustawiony jedynie w linii, która posiada swe odzwierciedlenie w skompilowanym kodzie. Do nieprawidłowych operacji należy ustawianie punktów przerwań w liniach pustych, zawierających komentarz lub deklarację. Próby ustawienia punktu przerwania w jednej z takich linii nie są blokowane, ale powodują wystosowanie ostrzeżenia przez debugger. Ustawienie punktu przerwania w jednej z poniższych linii kodu, spowoduje wygenerowanie ostrzeżenia nieprawidłowego punktu przerwania: {To jest komentarz, po którym następuje pusta linia.} X : Integer; {Deklaracja} Punkty przerwań mogą być ustawiane na dyrektywie end kończącej procedurę lub funkcję. |
Jeżeli ustawisz punkt przerwania w nieprawidłowej linii, Edytor Kodu wyświetli ją na zielono (zakładając domyślne ustawienia kolorów), a ikona punktu na gutterze będzie zaciemniona.
Program pracujący pod kontrolą debuggera zachowuje się tak, jakby pracował całkowicie samodzielnie - aż do momentu, kiedy osiągnięty zostanie punkt przerwania. Kiedy to nastąpi, sterowanie wraca do środowiska IDE, a linia kodu źródłowego, zawierająca punkt przerwania, zostaje podświetlona. Jeżeli stosujesz domyślny ustawienia kolorów linia, na której zatrzymany został program, jest podświetlona na czerwono, ponieważ ten właśnie kolor wskazuje linię z ustawionym punktem przerwania.
|
Punkt wykonania wskazuje linię kodu źródłowego, która będzie wykonana w następnej kolejności |
W miarę krokowego wykonywania programu, punkt wykonania jest podświetlany na niebiesko, a na gutterze okna edytora wyświetlana jest zielona strzałka. Linia podświetlona na niebiesko jest linią, która zostanie wykonana w następnej kolejności, kiedy tylko praca programu zostanie wznowiona.
|
Punkt wykonania programu jest podświetlony na niebiesko, o ile w linii tej nie został ustawiony punkt przerwania, w przeciwnym przypadku linia jest podświetlana na czerwono. Najbardziej precyzyjnym wskaźnikiem punktu wykonania jest zielona strzałka widoczna na gutterze, ponieważ jej obecność jest niezależna od koloru podświetlenia linii. |
Po zatrzymaniu programu na punkcie przerwania możesz przyjrzeć się wartościom zmiennych, stosowi wywołań lub rozpocząć pracę krokową. Po dokonaniu inspekcji zmiennych i obiektów, możesz wznowić pracę programu, klikając na przycisku Run. Aplikacja rozpocznie normalną pracę aż do osiągnięcia kolejnego punktu przerwania.
|
Błędy popełnione w procesie kodowania często wykrywane są w wyniku zatrzymywania programu w punktach przerwania. Jeżeli w trakcie sesji wykrywania błędów dokonasz zmian w kodzie źródłowym, a następnie wybierzesz polecenie wznowienia pracy programu, środowisko IDE wyświetli okno dialogowe z zapytaniem, czy chcesz ponownie skompilować program. Jeżeli odpowiesz twierdząco, nastąpi przerwanie bieżącego procesu, kod źródłowy zostanie skompilowany, po czym nastąpi ponowne uruchomienie programu. W wyniku takiego podejścia program nie ma szansy na normalne zakończenie swojej pracy, a w związku z tym - wykorzystywane przez niego zasoby w danym momencie mogą nie zostać zwolnione w sposób prawidłowy. Scenariusz taki niemal na pewno zaowocuje zagubieniem pewnej ilości pamięci. I chociaż programy Windows 95 i Windows NT radzą sobie znacznie lepiej z „gubieniem” zasobów, niż 16-bitowa wersja Windows, nadal wskazane jest zakończenie programu w sposób naturalny, a następnie zrekompilowanie go. |
Okno listy punktów przerwań
Zintegrowane środowisko Delphi sprawuje kontrolę nad wszystkimi ustawianymi przez Ciebie punktami przerwań. Ich podgląd umożliwia okno listy punktów przerwań; aby wyświetlić tę listę, wybierz polecenie menu ViewDebug WindowsBreakpoints - otwarte zostanie okno, widoczne na rysunku 10.1.
Rysunek 10.1. Okno listy punktów przerwań |
|
Okno listy punktów przerwań składa się z czterech kolumn:
Filename/Address - pokazuje nazwę pliku kodu źródłowego, w którym ustawiony został punkt przerwania.
Line/Length - wskazuje numer linii, w której ustawiony został punkt przerwania.
Condition - wyświetla wyrażenie warunkowe, jakie zdefiniowane zostało dla warunkowego punktu przerwania.
Pass - przedstawia warunkowy licznik przejść ustawiony dla punktu przerwania. (Wyrażenia warunkowe oraz warunki ilości przejść dla punktów przerwań omawiane są w sekcji „Warunkowe punkty przerwań”.)
Rozmiar kolumn może być modyfikowany przez przeciąganie linii podziału między nagłówkami.
|
Kolumna Pass nie pokazuje liczby dokonanych już przejść przez punkt przerwania; jest to tylko warunek ilości przejść, jaki ustawiłeś dla punktu przerwania. |
Menu kontekstowe listy punktów przerwań
Okno listy punktów przerwań posiada dwa menu kontekstowe. W tabeli 10.3 zestawione zostały elementy menu widoczne po kliknięciu prawym przyciskiem myszy na punkcie przerwania - w dalszej części rozdziału menu to będzie nazywane podstawowym menu kontekstowym; drugim ze wspomnianych menu zajmiemy się za chwilę.
|
Aby szybko przejść do edycji linii kodu, w której ustawiony jest punkt przerwania, kliknij dwukrotnie na danym punkcie przerwania w kolumnie nazwy pliku (Filename) okna listy punktów przerwań. Operacja taka jest równoważna wybraniu polecenia Edit Source z menu kontekstowego okna listy. |
Drugorzędne menu kontekstowe wyświetlane jest w wyniku kliknięcia prawym przyciskiem myszy na obszarze okna listy w miejscu, w którym nie ma punktu przerwania. W skład tego menu wchodzą polecenia o nazwach: Add (Dodaj), Delete All (Usuń wszystkie), Disable All (Dezaktywuj wszystkie), Enable All (Udostępnij wszystkie) i Dockable (Włącz/wyłącz dokowanie). Nazwy elementów wskazują jednoznacznie na ich przeznaczenie, dlatego nie będą tutaj dokładniej omawiane.
Tabela 10.3. Podstawowe menu kontekstowe okna listy punków przerwań
Element |
Opis |
Enable |
Uaktywnia lub dezaktywuje punkt przerwania. Kiedy punkt przerwania jest nieaktywny, jego ikona w oknie listy punktów przerwań, jest przyciemniona. Ikona punktu przerwania w kodzie źródłowym jest również przyciemniona, a linia kodu jest podświetlona na zielono, co wskazuje na nieaktywny stan punktu przerwania. |
Delete |
Usuwa punkt przerwania. |
View Source |
Powoduje wyświetlenie w Edytorze Kodu linii zawierającej wybrany punkt przerwania. (Okno listy pozostaje jednak nadal oknem pierwszoplanowym). |
Edit Source |
Umieszcza kursor w tej linii kodu źródłowego, w której ustawiony jest wybrany punkt przerwania; okno Edytora Kodu staje się oknem pierwszoplanowym. |
Properties |
Wyświetla okno dialogowe właściwości punktu przerwania. |
Dockable |
Określa, czy okno listy punktów przerwań jest dokowalne. |
Uaktywnianie i dezaktywowanie punktów przerwań
Dezaktywacja punktu przerwania ma na celu jego tymczasowe anulowanie - punkt przerwania zachowywany jest wraz ze wszystkimi elementami swej definicji, podczas wykonywania programu jest on jednak ignorowany przez debugger. Ponowne aktywowanie punktu przerwania przywraca jego normalne funkcjonowanie. Jest to z pewnością znacznie wygodniejsze, niż usuwanie punktu przerwania i jego ponowne definiowanie.
Aby uaktywnić lub dezaktywować punkt przerwania, kliknij na nim prawym przyciskiem myszy w oknie listy i zaznacz opcję Enable menu kontekstowego.
Modyfikowanie punktów przerwań
Jeżeli chcesz zmodyfikować definicję punktu przerwania, wybierz z podstawowego menu kontekstowego polecenie Properties. Kiedy to zrobisz, otwarte zostanie okno właściwości punktu przerwania (Source Breakpoint Properties - rysunek 10.2).
Rysunek 10.2. Okno właściwości punktu przerwania |
|
Najczęstszym powodem modyfikacji właściwości punktu przerwania jest zdefiniowanie/ przedefiniowanie warunku - zagadnienie to jest omawiane w sekcji „Warunkowe punkty przerwań”.
Aby usunąć punkt przerwania, wybierz go w oknie listy punktów przerwań, a następni naciśnij przycisk Delete na klawiaturze. Jeżeli chcesz usunąć wszystkie punkty przerwań, kliknij prawym przyciskiem myszy i wybierz polecenie Delete All.
Zwykłe punkty przerwań
Zwykły punkt przerwania powoduje zawieszenie procesu wykonania programu przy każdorazowym osiągnięciu takiego punktu. Wszystkie punkty przerwań, jakie ustawiasz początkowo, domyślnie traktowane są jako zwykłe. Zwykły punkt przerwania nie wymaga szczególnego objaśnienia - po jego napotkaniu wykonanie programu zostaje wstrzymane, a debugger oczekuje na polecenia. W większości przypadków będziesz stosował właśnie zwykłe punkty przerwań. Warunkowe punkty przerwań przewidziane są dla sytuacji specjalnych, w przypadku których będziesz potrzebował większej kontroli nad procesem wykrywania błędów.
Warunkowe punkty przerwań dzielą się na dwie kategorie. Pierwsza z nich to tzw. punkty przerwań z wyrażeniem warunkowym. Wpisz wyrażenie warunkowe w pole Condition okna właściwości punktu przerwania (rysunek 10.2); wyrażenie to będzie wartościowane każdorazowo, gdy osiągnięty zostanie punkt przerwania - jeżeli jego wartością będzie prawda (True), wykonanie program zostanie zatrzymane, w przeciwnym wypadku punkt przerwania będzie ignorowany. Dla przykładu, spójrz na ostatni punkt przerwania w oknie listy, przedstawionym na rysunku 10.1. Punkt ten posiada wyrażenie warunkowe X > 20. Jeżeli zostanie on osiągnięty w pewnym momencie wykonania programu, zatrzymanie nastąpi tylko wówczas, gdy zmienna X będzie w tym momencie posiadać wartość większą od 20.
Innym typem punktu przerwania jest punkt przerwania z warunkiem ilości przejść. W tym przypadku wykonanie programu jest zatrzymywane jedynie wtedy, gdy punkt przerwania zostanie osiągnięty określoną ilość razy. Aby określić liczbę przejść punktu przerwania, otwórz jego okno właściwości i wpisz odpowiednią wartość w pole Pass Count. Jeżeli, przykładowo, ustawisz ilość przejść na wartość 3, wykonanie programu zostanie zatrzymane w punkcie przerwania przy jego trzecim napotkaniu.
|
Ilość przejść jest liczona względem jedynki, nie zera. Jak wskazuje poprzedni przykład, ilość przejść równa 3 oznacza, że punkt przerwania stanie się aktywny przy jego trzecim napotkaniu przez program. |
Stosuj punkty przerwań z warunkiem ilości przejść wtedy, kiedy będziesz wymagał kilkakrotnego wykonania programu, przed jego zatrzymaniem i przejrzeniem zmiennych, przejściem do pracy krokowej, lub innej pracy związanej z wykrywaniem błędów.
|
Warunkowe punkty przerwań spowalniają normalny proces wykonania programu, ponieważ przy każdym napotkaniu tego typu punktu musi nastąpić sprawdzenie warunku. Jeżeli w trakcie procesu wykrywania błędów Twój program działa ociężale, przejrzyj listę punktów przerwań i sprawdź czy nie ma na niej warunkowych punktów przerwań, o których istnieniu zapomniałeś. |
|
Niekiedy jednak fakt spowalniania wykonania programu przez warunkowe punkty przerwań może działać na Twoją korzyść. Jeżeli chcesz przyjrzeć się jakiemuś procesowi w zwolnionym tempie, umieść w jego kodzie jeden lub więcej punktów przerwań. Ustaw takie warunki, które nigdy nie zostaną spełnione, przez co program ulegnie spowolnieniu, ale nigdy nie zostanie zatrzymany. |
Polecenie Run to Cursor
Polecenie to, dostępne w menu Run lub w menu kontekstowym Edytora Kodu, wykonuje program aż do momentu, kiedy osiągnięta zostanie linia kodu zawierająca kursor. W tym miejscu program zostaje zatrzymany tak, jak gdyby osiągnięty został punkt przerwania.
Polecenie to można traktować jako tymczasowy punkt przerwania. Korzystaj z tej komendy zamiast używania punktu przerwania, jeżeli chcesz bezwarunkowo zatrzymać program w danym miejscu. Wystarczy, że umieścisz kursor w linii, w której ma nastąpić przerwanie programu, i wybierzesz polecenie Run to Cursor (lub naciśniesz klawisz F4), a debugger zachowa się tak, jakbyś w tym miejscu umieścił punkt przerwania. Dzięki zastosowaniu tego polecenia jesteś zwolniony z obowiązku usuwania punktu przerwania po zakończeniu testowania określonej części kodu.
Podgląd zmiennych
Co więc należy zrobić kiedy program zatrzyma się na punkcie przerwania? Zazwyczaj celem zatrzymania programu jest chęć przyjrzenia się zawartości jednej lub kilku zmiennych. Możesz chcieć upewnić się, że określona zmienna zawiera oczekiwaną wartość lub po prostu sprawdzić wartość pewnej zmiennej, jeżeli wyleciała Ci ona z głowy.
Funkcja listy wyrażeń testowych (Watch List) jest prosta: pozwala ona śledzić wartości zmiennych. Programiści zwykle pomijają to proste, lecz istotne narzędzie, ponieważ nie poświęcają dostatecznej ilości czasu na pełne zrozumienie debuggera. Lista wyrażeń testowych nie narzuca ograniczenia na liczbę zmiennych, jakie można przeglądać. Rysunek 10.3 przedstawia okno listy wyrażeń testowych w trakcie sesji debuggera.
Rysunek 10.3. Lista wyrażeń testowych w działaniu |
|
Elementami listy są nazwy zmiennych, po których następuje ich bieżąca wartość. Sposób wyświetlania zmiennych zależy od typu danych jakie przechowują oraz bieżących ustawień wyświetlania danego elementu. Do listy wyrażeń testowych wrócę za chwilę, przedtem jednak umówię cechę, która znacznie ułatwia proces podglądania zmiennych.
Wartościowanie wyrażenia w formie podpowiedzi
Zarówno debugger, jak i Edytor Kodu posiadają cechę, która znacznie ułatwia sprawdzanie wartości zmiennej. Cecha ta - wartościowanie wyrażeń przez podpowiedź (Tooltip expression evaluation) - jest standardowo włączona, jej użycie nie wymaga więc żadnych dodatkowych czynności z Twojej strony. Wyłączenie tego mechanizmu jest możliwe poprzez stronę Code Insight opcji środowiska (Environment Options), o której mowa była w poprzednim rozdziale.
Czym właściwie jest wartościowanie wyrażeń przez podpowiedź? W przypadku zwykłych pól danych (Integer, Char, Byte, String, itd.) wyświetlana jest ich rzeczywista wartość. Jeśli chodzi o obiekty tworzone dynamicznie (np. egzemplarze klas), okno podpowiedzi wyświetla adres obiektu w pamięci. W przypadku rekordu wyświetlane są wszystkie jego elementy (pola). Przykład wartościowania pól rekordu przedstawiony został na rysunku 10.4.
Rysunek 10.4. Wartościowanie pól rekordu przez podpowiedź |
|
|
Czasami działanie wartościowania może być odbierane jako nieprawidłowe. Jeżeli, dla przykładu, umieścisz kursor nad zmienną, która występuje poza obszarem widoczności swej definicji, podpowiedź nie pojawi się. Ponadto, zmienne optymalizowane przez kompilator mogą nie wskazywać prawidłowych wartości. Optymalizacja była omawiana w poprzednim rozdziale i będzie o niej jeszcze mowa w dalszej części rozdziału. Innym przypadkiem, kiedy wartościowanie przez podpowiedź nie działa, jest wnętrze bloku with. Dla przykładu rozważmy następujący fragment kodu: with Point do begin X:= 20; Y:= 50; Label1.Caption := IntToStr(X); end; Po umieszczeniu kursora nad zmienną X, podpowiedź nie wyświetliłaby żadnej wartości, ponieważ X należy do zmiennej występującej w wyrażeniu with (Point). Jeżeli jednak umieścisz kursor nad zmienną Point, wyświetlona zostanie jego wartość (łącznie z polem X). |
Menu kontekstowe listy wyrażeń testowych
Jak wszystkie omawiane do tej pory okna Delphi, również i okno listy wyrażeń testowych posiada własne menu kontekstowe. Jego polecenia zostały zestawione w tabeli 10.4.
Tabela 10.4. Polecenia menu kontekstowego dla listy wyrażeń testowych
Polecenie |
Opis |
Edit Watch |
Umożliwia edycję elementu listy poprzez okno właściwości wyrażeń testowych (Watch Properties). |
Add Watch |
Dodaje nowy element do listy wyrażeń testowych. |
Enable Watch |
Uaktywnia element listy. |
Disable Watch |
Dezaktywuje element listy. |
Delete Watch |
Usuwa element listy. |
Enable All Watches |
Uaktywnia wszystkie elementy na liście. |
Disable All Watches |
Dezaktywuje wszystkie elementy na liście. |
Delete All Watches |
Usuwa wszystkie elementy z listy. |
Stay on Top |
Wymusza pozostawanie okna listy wyrażeń na pierwszym planie środowiska IDE. |
Break When Changes |
Kiedy wartość zmiennej umieszczonej na liście ulegnie zmianie, debugger przerwie program. Aktywność tej opcji w stosunku do danej zmiennej jest sygnalizowana podświetleniem jej na czerwono. |
Dockable |
Określa, czy okno listy wyrażeń testowych jest dokowalne. |
Polecenia Edit Watch i Add Watch wywołują okno właściwości podglądu. Przyjrzyjmy się z bliska temu oknu.
Okno właściwości podglądu
Celem tego okna jest dodanie lub edycja podglądanego elementu. Wygląd okna właściwości, podczas edycji zmiennej Buff, został przedstawiony na rysunku 10.5.
Rysunek 10.5. Okno właściwości podglądu |
|
Pole Expression, widoczne w górnej części okna, służy do wprowadzenia nazwy zmiennej przeznaczonej do edycji lub dodania do listy. Pole to jest listą rozwijalną umożliwiającą również wybór zmiennych podglądanych już wcześniej.
Pole Repeat count znajduje zastosowanie w przypadku podglądania tablic. Dla przykładu, załóżmy że posiadasz tablicę 20 elementową. Aby śledzić zawartość pierwszych dziesięciu elementów tej tablicy, powinieneś wpisać w pole Expression pierwszy element tablicy (np. Tablica[0]), zaś w pole Repeat Count - wartość 10; efektem tego byłoby umieszczenie pierwszych dziesięciu elementów tablicy na liście wyrażeń testowych.
|
Jeżeli do listy wyrażeń testowych dodasz tylko nazwę tablicy, wyświetlane zostaną wszystkie jej elementy. Skorzystaj z pola Repeat Count, aby ograniczyć liczbę jej wyświetlanych elementów. |
Pole Digits znajduje zastosowanie jedynie w przypadku obserwowania liczb zmiennoprzecinkowych. Dzięki niemu jesteś w stanie określić liczbę cyfr znaczących wartości wyświetlanej na liście wyrażeń testowych. Wyświetlana wartość nie jest obcinana, lecz zaokrąglana. Kolejne pole tego okna - Enabled - decyduje, czy modyfikowany element listy ma być aktywny.
Pozostała część okna właściwości podglądu składa się z różnorodnych opcji wyświetlania. Każdy typ danych cechuje się domyślnym sposobem wyświetlania, stosowanym w przypadku wybrania opcji Default. Wybierz inną opcję, aby wyświetlić wartość w innej formie. Na rysunku 10.6 w oknie listy wyrażeń testowych widoczne są dwie zmienne wyświetlane przy zastosowaniu różnych opcji reprezentacji na ekranie: Buff jest tablicą typu znakowego, natomiast I jest zmienną całkowitą (integer).
Rysunek 10.6. Okno wyrażeń testowych z wyświetlające dane przy różnych opcjach reprezentacji |
|
Aby zmodyfikować element listy, kliknij na nim w obszarze okna listy i wybierz polecenie menu kontekstowego Edit Watch. Edycji elementu można również dokonać klikając na nim dwukrotnie. Efektem obu tych czynności jest otwarcie okna właściwości wyrażeń testowych (Watch Properties), które umożliwia wprowadzenie dowolnych modyfikacji.
|
Najszybszy sposób przejścia do edycji elementu to dwukrotne kliknięcie jego nazwy w obszarze okna listy wyrażeń testowych. |
Uaktywnianie i dezaktywowanie
elementów listy wyrażeń testowych
Podobnie jak w przypadku punktów przerwań, również indywidualne elementy listy wyrażeń testowych mogą być uaktywniane lub dezaktywowane. Element nieaktywny jest przyciemniony, a jego wartość wskazuje łańcuch <disabled>.
Aby uczynić element nieaktywnym, kliknij na nim w obszarze okna listy wyrażeń i wybierz polecenie Disable Watch. Ponowne uaktywnienie elementu następuje w wyniku wybrania polecenia Enable Watch.
|
Powodem do dezaktywacji elementów listy wyrażeń testowych może być chwilowy brak potrzeby ich obserwowania. Przy dużej liczbie aktywnych elementów listy wyrażeń testowych następuje zauważalne spowolnienie pracy programu; dzieje się tak dlatego, że zmienne na tej liście muszą być uaktualniane przy każdorazowym wykonaniu linii programu. |
Dodawanie zmiennych do listy wyrażeń testowych
Istnieje kilka sposobów na dodanie elementu do listy wyrażeń testowych. Najszybszy z nich to kliknięcie na nazwie zmiennej w oknie edytora i wybranie polecenia Add Watch at Cursor z menu kontekstowego Edytora Kodu lub wciśnięcie kombinacji klawiszy Ctrl+F5. W wyniku tego element zostanie dodany do listy wyrażeń testowych; w miarę potrzeby można dokonać edycji jego opcji wyświetlania.
Poprzednia metoda wymagała zlokalizowania zmiennej w kodzie, czego można uniknąć wybierając polecenie menu RunAdd Watch. Kiedy wyświetlone zostanie okno właściwości wyrażenia testowego (Watch Properties), wpisz nazwę zmiennej, którą chcesz dodać do listy i kliknij na przycisku OK.
|
Mimo, że do listy wyrażeń testowych można dodać zmienną reprezentującą obiekt dowolnej klasy, informacja wyświetlana w takim przypadku okaże się mało użyteczna. Do przejrzenia wszystkich pól klasy powinieneś wykorzystać Inspektor Obiektów, o którym za chwilę. |
Użytkowanie listy wyrażeń testowych
Kiedy program zostanie zatrzymany w punkcie przerwania, lista wyrażeń testowych wyświetla bieżące wartości wszystkich zmiennych w niej występujących. Jeżeli w danej chwili lista jest niewidoczna, można otworzyć ją poleceniem menu ViewDebug Windows Watches.
|
Zadokuj listę wyrażeń testowych w dolnej części okna Edytora Kodu, dzięki czemu będzie ona zawsze widoczna podczas pracy krokowej. |
W określonych warunkach zamiast wartości zmiennej wyświetlony zostanie komunikat. Przykładowo, jeżeli zmienna znajdzie się poza zasięgiem definicji lub nie zostanie znaleziona, obok jej nazwy na liście wyrażeń testowych wyświetlony zostanie komunikat Undeclared identifier: `X'. Jeżeli program nie jest uruchomiony, bądź jego zatrzymanie nie wynika z punktu przerwania, obok wszystkich elementów listy pojawi się komunikat: [process not accessible]. Element nieaktywny zostanie oznaczony komunikatem <disabled>. W zależności od bieżącego stanu aplikacji lub określonej zmiennej mogą pojawić się również inne komunikaty.
Jak wspomniałem w poprzednim rozdziale, niekiedy dostęp do zmiennej X może okazać się niemożliwy ze względu na optymalizację kodu przeprowadzaną przez kompilator - co jest przejawem znanej zasady, że w procesie śledzenia kodu optymalizacja może programiście co najwyżej przeszkadzać. Rozsądnym rozwiązaniem jest więc rezygnacja z optymalizacji na tym etapie (osiąga się to, wyłączając opcję Optimization na stronie Compiler okna właściwości projektu).
Pamiętaj również, że zmienne, którym nie zostały przypisane wartości początkowe, będą posiadały wartości przypadkowe aż do momentu ich inicjalizacji.
|
Niejako ubocznym zastosowaniem listy wyrażeń testowych może służyć wykorzystanie jej w roli szybkiego konwertera liczb dziesiętnych na szesnastkowe i odwrotnie. Aby przekonwertować liczbę szesnastkową na dziesiętną, wybierz polecenie menu RunAdd Watch, wpisz wartość szesnastkową w pole Expression i kliknij na przycisku OK; w oknie listy wyrażeń testowych wyświetlone zostaną obydwie wartości - szesnastkowa i dziesiętna. Konwersja liczby dziesiętnej na szesnastkową przebiega w identyczny sposób, z jednym wyjątkiem: kliknij na przycisku opcji Hexadecimal, aby zmienić typ wyświetlanej wartości. Ponieważ pole Expression akceptuje dowolne wyrażenia matematyczne, możesz również użyć listy wyrażeń testowych jako kalkulatora liczącego w systemie szesnastkowym. Nie ma żadnych przeszkód w mieszaniu wartości szesnastkowych i dziesiętnych. Jedyną wadą takiego rozwiązania jest konieczność zatrzymania aplikacji w punkcie przerwania, aby można było w ogóle skorzystać z listy wyrażeń testowych. |
W celu lepszego zrozumienia roli listy wyrażeń testowych w procesie śledzenia programu wykonaj następujące ćwiczenie:
Stwórz nową aplikację i umieść przycisk w formularzu. Zmień właściwość Name przycisku na WatchBtn, a jego właściwość Caption na wartość Test Podglądu. Zmień właściwość Name formularza na DebugMain, a właściwość Caption na - cokolwiek sobie zażyczysz.
Kliknij podwójnie na przycisku, aby wyświetlić jego procedurę obsługującą zdarzenie OnClick w Edytorze Kodu. Zmodyfikuj tę funkcję zgodnie z kodem przedstawionym poniżej:
procedure TForm1.Button1Click(Sender: TObject);
var
S : string;
X, Y : Integer;
begin
X := Width;
S := IntToStr(X);
Y := Height;
X := X * Y;
S := IntToStr(X);
X := X div Y;
S := 'X = ' + IntToStr(X);
Width := X;
Height := Y;
end;
Zapisz projekt. Modułowi nadaj nazwę DbgMain, a projektowi - DebugTst.
Ustaw punkt przerwania w pierwszej linii za dyrektywą begin we wnętrzu procedury obsługującej zdarzenie OnClick. Uruchom program.
Kliknij na przycisku Test Podglądu. Debugger zatrzyma się w punkcie przerwania, a na pierwszym planie pojawi się środowisko IDE wraz z Edytorem Kodu.
Do listy wyrażeń testowych dodaj zmienne S, X i Y. (Początkowo zmienne X i Y będą niedostępne ze względu na optymalizację, ale nie przejmuj się tym.)
Przywołaj na ekran listę wyrażeń testowych i Edytor Kodu, rozmieść ich okna tak, aby oba były widoczne (Możesz zadokować okno listy wyrażeń testowych u dołu Edytora Kodu.)
Uczyń okno edytora oknem aktywnym, naciśnij klawisz F8 powodując w ten sposób wykonanie linii kodu. Linia kodu zostaje wykonana, a punkt wykonania zostaje przeniesiony do linii następnej. W tej chwili zmienna X wykazuje konkretną wartość.
Przejdź krokowo przez program naciskając kolejno klawisz F8. Obserwuj wartości zmiennych raportowane przez okno wyrażeń testowych.
Kiedy punkt wykonania osiągnie ostatnią linię w metodzie, kliknij na przycisku Run paska narzędzi, aby wznowić pracę programu.
Kliknij dowolną ilość razy na przycisku Test Podglądu aby poczuć, w jaki sposób działa lista wyrażeń testowych. Poeksperymentuj z innymi ustawieniami podglądu.
|
Kod w powyższym przykładzie pobiera wartości dwóch właściwości formularza: Width i Height, dokonuje pewnych obliczeń, a następnie przypisuje właściwościom Width i Height wartości, jakie miały one na początku. W efekcie nic się nie zmienia, ale istnieje dobry powód aby przypisać wartości właściwościom Width i Height na końcu metody. Jeżeli nie zrobisz czegoś ze zmiennymi X i Y, nie będziesz mógł dokonać ich inspekcji, ponieważ ze względu na optymalizację ich podgląd stanie się niemożliwy. Rzecz w tym, iż kompilator jest w stanie spojrzeć w przód i przekonać się, które ze zmiennych nie są nigdy używane, a więc można się ich pozbyć. Wykorzystanie wartości zmiennych X i Y na końcu metody zapobiega wyeliminowaniu ich przez kompilator w procesie optymalizacji. Podnosiłem tę kwestię już kilkakrotnie, ale chcę się upewnić że będziesz w stanie zrozumieć w jaki sposób działa kompilator optymalizujący. Kiedy rozpoczniesz poszukiwanie błędów w swoich aplikacjach, wiedza ta pomoże Ci uniknąć frustracji związanej z pojawianiem się komunikatów typu Variable `X' inaccessible here do to optimization („zmienna X niedostępna ze względu na optymalizację”) w oknie listy wyrażeń testowych. |
Inspektor Śledzenia
Inspektor Śledzenia jest nową cechą Delphi 4. Mówiąc wprost, Inspektor Śledzenia umożliwia przeglądanie złożonych struktur takich, jak klasy czy rekordy. Istnieje również możliwość przeglądania danych typu prostego, (np. całkowitego, tablicy znakowej itp.), chociaż w ich przypadku lepiej jest skorzystać z okna listy wyrażeń testowych. Inspektor Śledzenia staje się najbardziej użyteczny w przypadku obiektów i rekordów.
|
Wykorzystanie Inspektora Śledzenia jest możliwe tylko w trakcie zatrzymania programu pod kontrolą debuggera. |
Aby dokonać inspekcji obiektu, kliknij na jego nazwie w pliku kodu źródłowego i wybierz polecenie Inspect z menu kontekstowego Edytora Kodu (lub naciśnij kombinację klawiszy Alt+F5). Ewentualnie możesz również wybrać polecenie menu RunInspect.
Okno Inspektora Śledzenia zawiera szczegółowe informacje na temat wyświetlanego obiektu. Jeżeli obiekt jest typu prostego, wyświetlana jest jego bieżąca wartość (szesnastkowo i dziesiętnie w przypadku typu numerycznego), a na pasku statusu okna umieszczany jest typ zmiennej. Jeżeli przykładowo śledzisz wartość typu całkowitego, na pasku statusu pojawi się napis Integer. U szczytu okna znajduje się lista rozwijalna, zawierająca początkowo opis podglądanego obiektu.
Jeżeli przeglądany obiekt jest klasą, Inspektor Śledzenia będzie wyglądał mniej więcej tak, jak przedstawia to rysunek 10.7.
Rysunek 10.7. Inspektor Śledzenia kontroluje klasę |
|
Aby lepiej zrozumieć ideę Inspektora Śledzenia, wykonaj poniższe ćwiczenie:
Załaduj program DebugTst, który stworzyłeś wcześniej (o ile nie jest on już załadowany).
Umieść punkt przerwania gdzieś we wnętrzu metody WatchBtnClick.
Uruchom program i kliknij na przycisku Test Podglądu; program zatrzyma się w miejscu, w którym umieściłeś punkt przerwania.
Wybierz polecenie menu RunInspect. Wyświetlone zostanie okno dialogowe inspekcji.
W pole Expression wpisz wartość Self i naciśnij przycisk OK.
Wyświetlone zostaje okno dialogowe Inspektora Śledzenia, umożliwiając przejrzenie pól danych formularza.
|
Korzystanie ze wskaźnika Self jest możliwe jedynie we wnętrzu metody danej klasy. Jeżeli ustawisz punkt przerwania w zwykłej funkcji, a następnie spróbujesz przejrzeć obiekt Self, otrzymasz komunikat błędu twierdzący, iż Self jest nieprawidłowym symbolem. W powyższym przykładzie Self odnosi się do głównego formularza aplikacji. |
Strony Inspektora Śledzenia
Podczas inspekcji klas okno Inspektora Śledzenia składa się z trzech stron. Na początku znajdują się elementy należące do klasy przodka. Pola należące do przeglądanej klasy znajdują się u dołu listy. Istnieje możliwość wyłączenia informacji dotyczącej klasy przodka. W tym celu kliknij prawym przyciskiem myszy i wybierz polecenie Show Inherited z otwartego menu kontekstowego Inspektora Śledzenia.
Korzystając z klawiszy kursora klawiatury możesz poruszać się w górę i w dół po polach klasy, jednym spojrzeniem możesz określić typ każdego pola (spoglądaj na pasek statusu Inspektora Śledzenia). Żeby móc w przyszłości nadzorować określone pole danych, wystarczy dwukrotnie kliknąć na jego wartości. W wyniku tej operacji otwarte zostanie drugie okno Inspektora Śledzenia zawierające wybrane pole danych. Można otworzyć wiele okien Inspektora Śledzenia jednocześnie.
Okno o nazwie Methods wyświetla metody należące do klasy. W niektórych przypadkach okno to nie jest wyświetlane (np. podczas przeglądania prostych typów danych). Na pasku statusu wyświetlana jest deklaracja wybranej metody.
Na stronie Properties wyświetlane są właściwości przeglądanej klasy. Przeglądanie właściwości klasy ma słabe uzasadnienie (informacje tam zawarte nie są zbyt użyteczne). W większości przypadków będziesz w stanie znaleźć to, czego szukasz przeglądając pola danych związanych z określoną właściwością na stronie Data.
|
Strony Methods i Properties Inspektora Śledzenia są dostępne jedynie podczas przeglądania klasy. W przypadku prostych typów danych wyświetlana jest jedynie strona danych (Data). |
|
Jeżeli chcesz, aby okno Inspektora Śledzenia znajdowało się zawsze na wierzchu - przed Edytorem Kodu - zaznacz pole wyboru Inspectors stay on top na stronie Debugger okna ustawień środowiska (Environment Options). |
Menu kontekstowe Inspektora Śledzenia
Menu kontekstowe Inspektora Śledzenia zawiera kilka poleceń, które umożliwiają pracę zarówno z Inspektorem Śledzenia, jak i indywidualnymi zmiennymi. Na przykład, zamiast otwierać nowe okno dla kolejnych obiektów, można kliknąć prawym przyciskiem myszy i wybrać polecenie Descent w wyniku którego bieżący obiekt w Inspektorze Śledzenia zostanie zastąpiony wybranym obiektem. Jeżeli na przykład śledzisz zachowanie się formularza z przyciskiem o nazwie Button1, możesz wybrać ten przycisk w oknie Inspektora Śledzenia, a następnie użyć polecenia Descent. W tym momencie Inspektor rozpocznie nadzorowanie obiektu Button1. Metoda ta ma dodatkową zaletę: środowisko IDE utrzymuje listę obiektów, które były przeglądane. Aby cofnąć się do obiektu, który był już przeglądany wcześniej, wystarczy wybrać go z listy w górnej części okna Inspektora Śledzenia, co spowoduje uaktywnienie go w oknie Inspektora.
Polecenie Change menu kontekstowego Inspektora Śledzenia pozwala na modyfikację wartości zmiennej.
|
Zachowaj szczególną ostrożność podczas modyfikacji zmiennych za pomocą Inspektora Śledzenia. Modyfikacja niektórych pól danych lub wprowadzenie nieprawidłowej wartości do danej zmiennej może sprawić, że program „wysypie się”. |
Polecenie Inspect umożliwia otwarcie drugiego okna Inspektora Śledzenia dla elementu wskazanego przez kursor - służy do tego polecenie New Expression menu kontekstowego.
Element menu kontekstowego o nazwie Show Inherited jest przełącznikiem określającym, jak wiele informacji powinno być wyświetlanych w oknie Inspektora Śledzenia. Kiedy pole to jest aktywne, Inspektor Śledzenia pokazuje wszystkie elementy klasy będącej przedmiotem inspekcji oraz klasy będącej jej bezpośrednim przodkiem. Przy wyłączonej opcji Show Inherited wyświetlane są tylko elementy charakterystyczne dla samej klasy przeglądanej, co powoduje przyspieszenie pracy Inspektora Śledzenia, ponieważ ma on mniej informacji do wyświetlania.
|
Jeżeli nie pamiętasz typu elementu, który chcesz obejrzeć, kliknij na nim w chwili zatrzymania programu w punkcie przerwania i wciśnij kombinację klawiszy Alt+F5, powodując wyświetlenie okna Inspektora Śledzenia; typ pola możesz przy okazji odczytać z paska statusu okna Inspektora. |
Inne narzędzia procesu wykrywania błędów
Oprócz narzędzi przedstawionych do tej pory, Delphi posiada również inne przeznaczone do tropienia błędów. Niektóre z nich są z natury są bardzo zaawansowane i stosowane rzadziej niż pozostałe, niemniej jednak w rękach doświadczonego programisty stają się niezwykle użyteczne.
Okno Evaluate/Modify
Okno dialogowe Evaluate/Modify umożliwia śledzenie bieżącej wartości zmiennej oraz jej modyfikację (w miarę potrzeby). Umożliwia to testowanie zachowania się programu przy różnych parametrach, bez potrzeby rekompilowania go za każdym razem. Rysunek 10.8 przedstawia okno Evaluate/Modify w trakcie inspekcji przykładowej zmiennej.
Rysunek 10.8. Okno dialogowe Evaluate/Modify |
|
|
Pasek narzędzi okna Evaluate/Modify może wyświetlać duże lub małe ikony przycisków. Domyślnie wyświetlane są małe przyciski. Nie posiadają one podpisów, więc aby przekonać się o przeznaczeniu którejś z nich, trzeba naprowadzić na nią kursor myszy i przeczytać zawartość podpowiedzi. Aby wyświetlić duże ikony (z podpisami - jak na rysunku 10.8), przeciągnij pasek rozmiaru w dół, bezpośrednio poniżej paska narzędzi. |
Okno Evaluate/Modify działa podobnie do listy wyrażeń testowych i Inspektora Śledzenia. Aby określić wartość zmiennej, wystarczy kliknąć na niej prawym przyciskiem myszy w kodzie źródłowym i wybrać polecenie Evaluate/Modify z menu kontekstowego Edytora Kodu. Jeżeli chcesz wpisać zmienną nie występującą w aktualnie dostępnym kodzie, wybierz polecenie menu RunEvaluate/Modify, a następnie wpisz nazwę zmiennej, która Cię interesuje.
Miejscem, w które należy wpisać nazwę zmiennej lub wyrażenie do wartościowania jest pole Expression. Wartość wyrażenia zostanie obliczona po kliknięciu przycisku Evaluate (lub naciśnięciu klawisza Enter), a wynik pojawi się w polu Result.
|
Okno dialogowe Evaluate/Modify może służyć jako szybki kalkulator. Akceptowane są wyrażenia w formie matematycznej składające się zarówno z liczb dziesiętnych, jak i szesnastkowych (a także ich kombinacje). Np. jeżeli w pole Evaluate wpiszesz wyrażenie $400 - 256 i naciśniesz klawisz Enter, w polu Result pojawi się wynik 768. W pole Evaluate można również wpisać wyrażenie typu logicznego i sprawdzić jego prawdziwość w polu Result. Jeżeli np. wpiszesz wyrażenie 20 * 20 = 400 w polu Result pojawi się wartość True (prawda). Warunkiem działania okna Evaluate/Modify jest zatrzymanie programu w punkcie przerwania. |
Jeżeli chcesz zmienić wartość zmiennej, wpisz nową wartość w pole New Value i kliknij na przycisku Modify. Kiedy klikniesz na przycisku Run, aby wznowić pracę programu (lub będziesz kontynuował pracę krokową), zmienna będzie zawierać tę nową wartość.
|
W przeciwieństwie do okna wyrażeń testowych i Inspektora Śledzenia okno Evaluate/Modify nie jest automatycznie aktualizowane w trakcie pracy krokowej. Jeżeli zmienna w oknie Evaluate/Modify zostanie zmodyfikowana w kodzie programu, to aby zobaczyć rezultat tej modyfikacji będziesz musiał ponownie kliknąć na przycisku Evaluate. Takie rozwiązanie ma jedną podstawową zaletę: mechanizm pracy krokowej działa szybciej, ponieważ debugger nie musi wyznaczać śledzonego wyrażenia po każdym kroku programu (tak jak ma to miejsce w przypadku Inspektora Śledzenia i okna wyrażeń testowych). Typowy sposób wykorzystania okna Evaluate/Modify to wartościowanie wyrażenia i natychmiastowe zamknięcie okna. |
Okno Call Stack
Okno Call Stack umożliwia śledzenie stosu wywołań procedur i funkcji. W celu jego otwarcia wybierz polecenie menu ViewDebug WindowsCall Stack. W oknie tym wyświetlana jest lista funkcji i procedur, jakie wywołane zostały przez program - w porządku wynikającym z kolejności ich wywoływania. Procedura lub funkcja wywołana jako ostatnia znajduje się na szczycie listy.
Podwójne kliknięcie na nazwie metody w oknie Call Stack spowoduje przejście do linii kodu źródłowego tej metody, o ile metoda ta znajduje się w Twoim programie. W przypadku funkcji i procedur których, kod źródłowy jest niedostępny (np. metody VCL), okno Call Stack zawiera jedynie adres i nazwę modułu, w którym zlokalizowana jest dana procedura. Podwójne kliknięcie funkcji lub procedury, dla której nie istnieje kod źródłowy, spowoduje otwarcie okna deasemblacji omawianego poniżej.
Przeglądanie kolejności wywołań jest najbardziej pomocne po wystąpieniu błędu naruszenia ochrony dostępu (Access Violation). Przejrzenie stosu wywołań umożliwia określenie miejsca, do którego doszedł program przed wystąpieniem błędu. Wiedza na temat tego miejsca jest często pierwszym krokiem w analizie przyczyny nieprawidłowego zachowania się programu.
|
Jeżeli lista kolejności wywołań zawiera pozornie bezsensowne informacje, może być to efekt uszkodzenia stosu wywołań. Uszkodzony stos wywołań zwykle wskazuje na przepełnienie stosu lub pamięci. W aplikacjach 32-bitowych przepełnienie stosu nie jest tak często spotykane, jak w aplikacjach 16-bitowych, niemniej jednak może się zdarzyć. |
Okno deasemblacji
Okno deasemblacji zostało oficjalnie udokumentowane w Delphi 4 - w poprzednich wersjach Delphi można je było uzyskać poprzez odpowiednie modyfikacje w Rejestrze. Obecnie okno to jest oficjalną częścią Delphi i może zostać otwarte poleceniem menu ViewDebug WindowsCPU (lub z klawiatury Ctrl+Alt+C).
Okno deasemblacji umożliwia przeglądanie programu na poziomie instrukcji asemblera. Korzystając z tego widoku można wykonywać pracę krokową po jednej instrukcji procesora.
Można również wykonać program w normalnym tempie do określonej instrukcji asemblera - podobnie, tak jak w przypadku „zwykłego kodu” program wykonywany był do określonej linii programu pascalowego. Okno deasemblera składa się z pięciu zakładek: disassembly, register, flags, raw stack i dump; każda zakładka posiada własne menu kontekstowe. Efektywne wykorzystanie okna procesora wymaga dobrej znajomości asemblera.
Polecenie Go to Address
Jest to kolejne zaawansowane narzędzie procesu wykrywania błędów. W przypadku nieoczekiwanego zakończenia programu Windows wyświetla komunikat błędu wskazując adres miejsca, w którym wystąpiła nieprawidłowa sytuacja. Dzięki poleceniu Go to Address możesz spróbować dotrzeć do miejsca w programie, gdzie nastąpiło jego załamanie. W przypadku gdy nastąpi błąd naruszenia ochrony dostępu, Windows wygeneruje okno komunikatu zbliżone do tego z rysunku 10.9.
Rysunek 10.9. Okno Windows z komunikatem naruszenia ochrony dostępu |
|
Kiedy ujrzysz taki komunikat, zapisz adres, w którym wystąpił błąd ochrony, a następnie wybierz polecenie menu kontekstowego Edytora Kodu - DebugGo to Address. Wyświetlone zostanie okno dialogowe przejścia do wskazanego adresu; wpisz w pole Address zapisany wcześniej adres wystąpienia błędu.
Kiedy klikniesz na przycisku OK, debugger spróbuje znaleźć miejsce w kodzie źródłowym, w którym wystąpił błąd. Jeżeli uda mu się powiązać to miejsce z konkretna linią programu źródłowego, w linii tej zostanie umieszczony kursor; w przeciwnym wypadku debugger poinformuje, iż nie jest w stanie zlokalizować odnośnej instrukcji.
Jak wspomniałem wcześniej, jest to narzędzie zaawansowane, którego być może nigdy nie będziesz używał.
Praca krokowa
Praca krokowa jest jedną z najbardziej podstawowych operacji w procesie wykrywania błędów, ale mimo to należy o niej tutaj wspomnieć. Podobnie jak czasami widzi się drzewa, nie widząc lasu, tak też czasami autorzy książek o programowaniu zapominają o przedstawianiu rzeczy oczywistych. Powtarzanie od czasu do czasu wiadomości podstawowych może czasem odsłonić czasem coś, czego przedtem nie wiedziałeś.
Symbole procesu śledzenia umieszczane na gutterze
Przed przystąpieniem do omawiania tematu tej sekcji, opowiem pokrótce o symbolach, które pojawiają się na gutterze w czasie sesji debuggera. W sekcji „Ustawianie i usuwanie punktów przerwań” powiedziałem, że czerwone kropki pojawiają się na gutterze po ustawieniu punktu przerwania w linii kodu; wspomniałem również, że w czasie pracy krokowej zielona strzałka wskazuje bieżący punkt wykonania.
Jedna rzecz, o której dotychczas nie wspominałem, to małe niebieskie kropki pojawiające się na gutterze obok niektórych linii kodu. Kropki te wskazują na linie kodu, które w rzeczywistości zostały przekształcone w kod wynikowy. Na rysunku 10.10 przedstawiony został Edytor Kodu w trakcie zatrzymania debuggera w punkcie przerwania; widoczne są małe kropki wskazujące wygenerowany kod, ikona strzałki wskazująca punkt wykonania, a także ikona punktu przerwania. Znak zaznaczenia na ikonie punktu przerwania oznacza, że sam punkt został sprawdzony i uznany za poprawny.
Przyjrzyj się bliżej rysunkowi 10.10. Zauważ, że małe kropki pojawiają się tylko przy niektórych liniach kodu. Linie bez kropek nie generują żadnego skompilowanego kodu. Weźmy dla przykładu następujące linie:
S : string;
X : Integer;
Dlaczego linie te nie są źródłem kodu? Ponieważ są to deklaracje zmiennych. A co z tą linią:
X:=20;
Rysunek 10.10. Okno Edytora Kodu z symbolami umieszczonymi na gutterze |
|
Dlaczego ta linia nie jest źródłem kodu? Wytłumaczenie zawiera się w jednym słowie: optymalizacja. Kompilator patrzy na dalszą część kodu i nigdzie nie widzi wykorzystania zmiennej X, więc całkowicie ignoruje wszelkie do niej odwołania. W końcu, zwróć uwagę na następujące linie:
{$IFNDEF WIN32}
S:= `Coś jest tutaj nie tak...';
{$ENDIF}
Kompilator nie generuje kodu dla linii zawartej między dyrektywami kompilatora, ponieważ symbol WIN32 jest zdefiniowany w programie Delphi 4. Dyrektywa {$IFNDEF WIN32} jest interpretowana przez kompilator następująco „Skompiluj poniższą linię kodu, jeżeli docelową platformą nie jest 32-bitowa wersja Windows”. Ponieważ Delphi 4 jest kompilatorem 32-bitowym, linia ta nie jest kompilowana (byłaby skompilowana w środowisku Delphi 1, które jest 16-bitowe).
Przekraczanie i wkraczanie
Wracamy do pracy krokowej. Po zatrzymaniu programu w punkcie przerwania, masz wiele możliwości określenia bieżącego stanu kodu. Możesz umieścić zmienne na liście wyrażeń testowych, dokonać inspekcji obiektów przy pomocy Inspektora Śledzenia lub obejrzeć stos wywołań. Masz również możliwość wykonania dalszej części kodu krok po kroku, aby przekonać się co się dzieje ze zmiennymi oraz obiektami w miarę postępowania programu.
W miarę kontynuowania programu krok po kroku, przekonasz się, że linia kodu przeznaczona do wykonania w następnej kolejności jest podświetlona na niebiesko. Jeżeli otwarłeś okno listy wyrażeń testowych i Inspektora Śledzenia, ich zawartość będzie uaktualniana po wykonaniu każdej linii kodu. Jakiekolwiek zmiany zmiennych staną się natychmiast widoczne w oknie listy lub Inspektora.
Debugger środowiska IDE posiada dwie główne komendy pracy krokowej: przekraczanie (Step Over) i wkraczanie (Step Into).
Przekraczanie
Operacja przekraczania (Step Over) oznacza wykonanie kolejnej linii kodu źródłowego i zatrzymanie się na linii po niej następującej. Określenie tej operacji mianem przekraczania jest trochę niepoprawne - mogłoby bowiem sugerować, że dana linia kodu jest „przekraczana” w sensie dosłownym, bez jej wykonywania. Chodzi tu jednak o coś zupełnie innego: otóż, nawet jeżeli dana linia zawiera instrukcję powodującą wywołanie procedury lub funkcji, potraktowana zostaje jako niepodzielna całość - nie nastąpi wejście śledzenia do wnętrza procedury/funkcji.
Najprostszym sposobem wykonania linii kodu źródłowego z trybie Step Over jest naciśnięcie klawisza F8.
Wkraczanie
Wkroczenie (Step Into) pozwala na wejście w kod dowolnej procedury lub funkcji, jaka zostanie napotkana w trakcie pracy krokowej. Komenda wkroczenia, zamiast wykonywać funkcję lub procedurę i przejść do następnej linii (jak miało to miejsce przy przekraczaniu), umieszcza punkt wykonania w pierwszej linii kodu wywołanej funkcji lub procedury. Następnie można przejść przez tę funkcję lub procedurę linia po linii, stosując jedno z poleceń przekraczania lub wkraczania. Skrótem klawiszowym dla polecenia Trace Into jest F7.
|
W miarę krokowego wykonywania programu wszystkie niezbędne moduły źródłowe będą automatycznie ładowane przez Edytor Kodu i wyświetlane na ekranie jeżeli zajdzie taka potrzeba. |
Po przeanalizowaniu stanu zmiennych i wykonaniu innych operacji związanych z wykrywaniem usterek, można wznowić wykonanie programu z pełną szybkością klikając na przycisku Run. Program rozpocznie normalną pracę aż do momentu napotkania punktu przerwania.
|
Jeżeli posiadasz Delphi w wersji Professional lub Client/Server, możesz wkraczać do wnętrza kodu VCL. Kiedy napotkasz metodę VCL, polecenie Trace Into przeniesie Cię do kodu źródłowego tej metody. Możesz dokonać inspekcji dowolnych zmiennych. Do pola Search path na stronie Directories/Conditionals opcji projektu musisz w tym celu dodać ścieżkę dostępu do kodu źródłowego VCL. Uaktywnienie tej opcji wymaga zbudowania projektu po dodaniu ścieżki do kodu VCL. Dla większości programistów wkraczanie w kod VCL przynosi znikome korzyści. Jednak osoby doświadczone w programowaniu uznają te cechę za użyteczną. |
Wkraczanie do następnej linii
Kolejną, rzadziej stosowaną komendą debuggera, jest wkraczanie do następnej linii (Trace To Next Line - Shift+F7). Prawdopodobnie nie będziesz zbyt często korzystał z tego polecenia, a już szczególnie do czasu, kiedy lepiej zaznajomisz się z zasadami wykrywania błędów i programowaniem dla Windows w ogólności. Niektóre funkcji interfejsu API Windows korzystają z mechanizmu zwanego funkcją zwrotną. Oznacza to, że funkcja Windows wywołuje jedną z Twoich funkcji, aby wykonać określoną czynność.
Jeżeli punkt wykonania wskazuje na funkcję Windows API posiadającą mechanizm zwrotny, polecenie Trace To Next Source Line przemieści punkt wykonania do pierwszej linii funkcji zwrotnej. Efekt działania tego polecenia jest podobny do zwykłego wkraczania, chociaż specyficzna sytuacja, w której stosowane jest to polecenie, jest całkowicie odmienna. Jeżeli nie jesteś w stanie zrozumieć tego o czym mówię, nie przejmuj się. Nie jest to rzecz ważna z punktu widzenia rzeczy, których powinieneś nauczyć się w tym rozdziale.
|
Praca krokowa w ramach procedury/funkcji zwrotnej doprowadzi w końcu do dyrektywy end. Naciśnięcie w tej sytuacji klawisza F8 spowoduje zwrócenie sterowania do Windows, sam zaś program wyjdzie ze stanu zatrzymania. Sytuacja ta wywołuje często dezorientację mniej doświadczonych programistów - z jednej strony program kontynuuje normalną pracę, z drugiej zaś - IDE pozostaje ciągle oknem aktywnym. Uaktywnienie programu (np. za pomocą kombinacji klawiszy Alt+Tab lub kliknięciem w odpowiedni przycisk na pasku stanu Windows) likwiduje tę dwuznaczną sytuację. |
Jak wspomniałem wcześniej, praca krokowa jest prostą, lecz intensywnie wykorzystywaną techniką wykrywania błędów - zapamiętaj więc dobrze związane z nią skróty klawiszowe: F7 (Trace Into), F8 (Step Over), F9 (Run).
Śledzenie wewnątrz bibliotek DLL
Zasadniczo testowanie biblioteki DLL podobne jest do testowania pliku wykonywalnego. W kodzie biblioteki umieszcza się punkty przerwań, a debugger po napotkaniu dowolnego z nich przerywa wykonanie programu, identycznie jak w przypadku pliku EXE. Zazwyczaj biblioteka DLL jest testowana przez stworzoną do tego celu aplikację testującą, pracującą pod kontrolą debuggera.
Czasem może się jednak okazać niezbędne przetestowanie biblioteki DLL z plikami wykonywalnymi, które powstały w innych środowiskach programistycznych. Powiedzmy, na przykład, że budujesz bibliotekę DLL, która będzie współpracować z aplikacją stworzoną w środowisku Visual Basic - zdecydowanie nie możesz uruchomić tej ostatniej pod kontrolą debuggera Delphi. Możesz za to wymusić na debuggerze Delphi uruchomienie aplikacji VB jako aplikacji-hosta dla przedmiotowej biblioteki DLL (oczywiście aplikacja-host musi zawierać kod, ładuje bibliotekę i wywołuje z niej żądane podprogramy); aplikację-host specyfikuje się w oknie dialogowym Run Parameters.
W celu wyświetlenia tego okna należy wybrać polecenie menu RunParameters. Nazwę aplikacji-hosta należy wpisać w pole Host Application, a następnie kliknąć na przycisku Load, co spowoduje uruchomienie programu. Wygląd okna Run Parameters przed przystąpieniem do wykrywania błędów w bibliotece DLL przedstawiony został na rysunku 10.11.
Rysunek 10.11. Określenie aplikacji-hosta w oknie Run Parameters |
|
Po uruchomieniu aplikacji-hosta można przystąpić do poszukiwania błędów w bibliotece DLL tak samo, jak robi się to w przypadku zwykłych programów: należy po prostu umieść punkty przerwań w kodzie źródłowym biblioteki.
|
Okno Run Parameters posiada zakładkę Remote; zakładka ta pozwala ustawić parametry dla procesu wykrywania błędów w aplikacji znajdującej się na zdalnym komputerze. Zdalne wykrywanie błędów należy do zaawansowanych zagadnień i nie będzie tutaj omawiane. |
Okno dziennika zdarzeń
Dziennik zdarzeń jest specjalnym plikiem Delphi zawierającym komunikaty diagnostyczne - generowane przez Delphi, Twoją aplikację, a niekiedy przez Windows. Przykładowo, w dzienniku tym znajdują się informacje o załadowanych modułach (głównie bibliotekach DLL), czasie uruchomienia i zakończenia aplikacji, napotkaniu punktu przerwania itp. Aby wyświetlić okno dziennika zdarzeń, wybierz polecenie menu ViewDebug WindowsEvent Log. Na rysunku 10.12 przedstawione zostało okno dziennika zdarzeń w trakcie procesu testowania aplikacji.
Okno dziennika zdarzeń posiada własne menu kontekstowe umożliwiające wyczyszczenie dziennika, zapisanie go do pliku tekstowego lub dodanie komentarza. Zapisanie dziennika do pliku ułatwia jego przeglądanie lub poszukiwanie określonego tekstu. Polecenie Properties umożliwia dostosowanie okna dziennika zdarzeń do własnych potrzeb. Okno ustawień dziennika zdarzeń wygląda identycznie jak strona Event Log w oknie Debugger Options (omawiana dalej w sekcji „Strona Event Log”).
Rysunek 10.12. Okno dziennika zdarzeń |
|
Jedna z funkcji Windows API - OutputDebugString (omawiana w sekcji „Funkcja OutputDebugString”) umożliwia wysłanie własnego komunikatu do dziennika zdarzeń.
Okno modułów
Okno modułów (Modules) pokazuje nazwy załadowanych aktualnie modułów, dołączonych do nich plików kodu źródłowego, a także symboli (funkcji, procedur i zmiennych) wyeksportowanych z określonego modułu. W celu otwarcia okna należy wybrać polecenie menu ViewDebug WindowsModules. Okno modułów jest niezwykle zaawansowanym narzędziem, dlatego szczegóły jego działania nie będą tutaj omawiane. Wskazane jest, abyś poświęcił trochę czasu na eksperymenty z tym oknem, aby przekonać się jak ono działa. Rysunek 10.13 przedstawia okno modułów w działaniu.
Rysunek 10.13. Okno modułów w działaniu |
|
Techniki wykrywania błędów
Przy okazji omawiania zagadnień związanych z debuggerem środowiska IDE wspomnianych zostało kilka technik wykrywania błędów. Teraz omówimy jeszcze kilka dodatkowych sposobów, które ułatwią Ci pracę w trakcie poszukiwania błędów.
Funkcja OutputDebugString
Czasami pomocne może okazać się obserwowanie zachowania się programu w trakcie jego pracy. Innym razem chcesz śledzić wartości zmiennych bez potrzeby zatrzymywania programu w punktach przerwań. Właśnie do tego celu służy funkcja OutputDebugString, będąca doskonałym narzędziem w procesie wykrywania błędów, często nie zauważanym przez programistów - głównie ze względu na brak dyskusji na jej temat. Spójrz na ostatni wpis do dziennika zdarzeń na rysunku 10.12; wpis ten powstał na skutek następującego fragmentu kodu:
OutputDebugString(`Wnętrze metody Button1Click...');
Ponieważ Delphi jest zainstalowane jako debugger systemowy, wszelkie łańcuchy generowane przy użyciu funkcji OutputDebugString będą pojawiać się w dzienniku zdarzeń. Wywołania tej funkcji mogą pojawiać się w dowolnych miejscach kodu źródłowego.
Aby wyświetlić wartość zmiennej, wcześniej trzeba przetworzyć ją na łańcuch i przekazać w takiej postaci funkcji OutputDebugString. Przykład:
procedure TForm1.FormCreate(Sender : TObject);
var
X : Integer;
S : string;
begin
{Dowolny fragment kodu...}
S:=Format(`X:= %d', [X]);
OutputDebugString(PChar(S));
end;
Dzięki funkcji OutputDebugString można obserwować zachowanie się programu nawet w przypadku sekcji kodu silnie uwarunkowanych czasowo.
Śledzenie błędów naruszenia ochrony dostępu
Błąd naruszenia ochrony dostępu jest generowany przez Windows, gdy program próbuje zapisać coś do fragmentu pamięci, który do niego nie należy. Błędy tego typu napotykają wszyscy programiści w trakcie prac nad aplikacjami Windows.
Błędy naruszenia ochrony dostępu są trudne do wyśledzenia, zarówno dla początkujących, jak i zaawansowanych programistów Windows. W miarę rozwijania swoich umiejętności, wielu programistów zyskuje szósty zmysł wykrywania przyczyny powstawania tego typu błędów. Poniżej znaleźć można kilka wskazówek, którymi należy kierować się przy wyszukiwaniu nieuchwytnych błędów naruszenia ochrony. Nie są to jedyne sytuacje powodujące w efekcie załamanie się programu, ale ich występowanie jest najczęstsze.
Niezainicjowane wskaźniki
Niezainicjowany wskaźnik to zadeklarowany wskaźnik, który posiadając przypadkową wartość nie wskazuje żadnego konkretnego obiektu w programie (mówiąc inaczej - wskazuje na losowy fragment danych.) W najlepszym przypadku wskaźnik taki pokazuje na nieużywany fragment pamięci. W najgorszym przypadku niezainicjowany wskaźnik wskazuje na fragment pamięci gdzieś w Twoim programie. Sytuacja taka może prowadzić do niekonsekwentnego zachowania się programu przy każdym jego uruchomieniu. Przed pierwszym użyciem wskaźnika, a także po usunięciu obiektu na który wskazuje, przypisuj mu wartość nil. Próba odwołania się do wskaźnika zawierającego wartość nil spowoduje zatrzymanie programu z błędem naruszenia ochrony, jednak tym razem linia kodu źródłowego, która wywołała błąd, zostanie podświetlona przez debugger umożliwiając szybką identyfikację problemowego wskaźnika.
Usuwanie nieistniejących już wskaźników
Usunięcie wskaźnika, który został usunięty już wcześniej, owocuje błędem naruszenia ochrony.
Zastosowanie znajduje tutaj rada udzielona w przypadku niezainicjowanych wskaźników: ustawiaj usunięte wskaźniki na wartość nil. Usunięcie wskaźnika zawierającego nil jest całkowicie bezpieczne. Dlatego ustawianie wskaźnika, po usunięciu obiektu na który wskazuje, na wartość nil będzie gwarancją uniknięcia niepożądanych efektów w sytuacji przypadkowego usunięcie wskaźnika po raz drugi.
Przekraczanie zakresu tablicy
Zapis danych poza przydzielony rozmiar pamięci (w szczególności - tablicy) może spowodować błąd naruszenia ochrony. W pewnych sytuacjach poza dostępną pamięć nie spowoduje żadnych efektów ubocznych, program będzie pracował poprawnie, jednak po pewnym czasie „wysypie się”. Kiedy zajdzie taka sytuacja, będziesz szukał błędu w miejscu, w którym program przerwał pracę, podczas gdy rzeczywisty problem wystąpił w całkowicie innej części kodu. W innym przypadku zapis poza dostępny obszar pamięci z miejsca okaże się nieprawidłowy i program natychmiast przerwie pracę. W ekstremalnych sytuacjach może dojść nawet do załamania systemu Windows.
Przekraczanie rozmiaru tablicy można nieco zminimalizować stosując sprawdzanie rozmiaru (opcja kompilacji $R). Przy włączonej opcji sprawdzania zakresu (domyślnie), przy każdej operacji na tablicy kompilator będzie sprawdzał, czy przetwarzany element mieści się w jej zakresie. Dla przykładu, poniższy fragment kodu spowoduje błąd kompilatora:
procedure TForm1.ButtonClick1(Sender : TObject);
var
A : array[0..20] of Char;
begin
A[30] := `a';
end;
W powyższym kodzie następuje próba dostępu do 30 elementu tablicy, która składa się tylko z 21 elementów. Kompilator zauważa, że dostęp do tablicy następuje poza jej zadeklarowanym zakresem i generuje błąd. Sprawdzanie zakresu nie działa jednak w przypadku zmiennych. Następny fragment kodu nie spowoduje powstania błędu kompilatora:
procedure TForm1.ButtonClick1(Sender : TObject);
var
X : Integer;
A : array[0..20] of Char;
begin
X := 30;
A[X] := `a';
end;
Mimo że zakres tablicy zostaje przekroczony o dziewięć bajtów, nie pojawia się żaden błąd kompilatora, ponieważ w chwili kompilacji wartość X nie jest znana kompilatorowi. Jeżeli jednak powyższy kod kompilowany był z opcją {$R+}, wystąpi błąd wykonania, gdyż kompilator wygenerował kod sprawdzający legalność odwołań.
Naruszenie ochrony dostępu przy wyjściu z programu
Jeżeli program zostaje zatrzymany z błędem naruszenia ochrony dostępu podczas normalnego kończenia swojej pracy, jest to zwykle oznaka zbyt małego rozmiaru stosu. Chociaż jest to sytuacja nietypowa dla programu 32-bitowego, może jednak zaistnieć w przypadku warunków ekstremalnych. Przyczyną błędu pogwałcenia dostępu podczas wyjścia może być również próba usunięcia nieistniejącego już wskaźnika, o czym była mowa wcześniej.
Krótkie wskazówki do procesu wykrywania błędów
Oprócz wszystkich, przedstawionych do tej pory wskazówek, warte zastosowania mogą okazać również poniższe:
Zamiast stosować punkt przerwania, zmodyfikuj właściwość Caption formularza tak, aby wyświetlała wartość zmiennej. Ze względu na prostotę użycia, możesz również wykorzystać w tym celu komponent typu Label. Zmień tekst etykiety, tak aby wskazywała wartość zmiennej lub dowolną inną informację, która będzie Ci potrzebna.
Aby zwolnić pracę programu (być może w celu obejrzenia efektu jego działania w zwolnionym tempie) uaktywnij warunkowy punkt przerwania lub punkt przerwania listy wyrażeń testowych. Punkty przerwań tego typu zwalniają pracę programu ze względu na ciągłą potrzebę sprawdzania warunku przerwania programu.
Korzystaj z okna Evaluate/Modify, aby tymczasowo modyfikować wartości zmiennych w czasie pracy programu. Dzięki temu będziesz mógł zaobserwować wpływ różnych wartości na działanie programu, bez potrzeby jego rekompilowania kodu za każdym razem.
Wybierz polecenie menu RunInspect, następnie wpisz wartość Self w pole Expression. W ten sposób będziesz mógł obserwować klasę, na której zatrzymał się debugger.
Użyj funkcji MessageBeep($FFFF) jako dźwiękowego wskaźnika osiągnięcia określonego punktu w programie. Jest to funkcja Windows API, wydająca charakterystyczny dźwięk przez głośnik przy wywołaniu z parametrem -1.
Aby wstrzymać sesję debuggera, wybierz polecenie menu RunProgram Reset lub naciśnij kombinację klawiszy Ctrl+F2.
Stosuj tymczasowe zmienne, rozbijając długie ciągi obliczeniowe lub łańcuchowe wywołania metod, aby móc kontrolować rezultaty działania programu na bardziej elementarnym poziomie.
Korzystaj z funkcji ShowMessage, MessageBox lub MessageDlg w celu wyświetlenia efektów śledzenia działania programu. (Preferowaną metodą jest ShowMessage, ponieważ jej jedynym parametrem jest łańcuch komunikatu.)
|
Jeżeli pracujesz w Delphi pod kontrolą Windows 95, korzystaj oszczędnie z polecenia Program Reset. W niektórych przypadkach, użycie tego polecenia do przerwania pracy aplikacji może spowodować załamanie się Windows 95. Ponieważ nie wszystkie systemy Windows 95 zachowują się w ten sposób, być może w ogóle nie będziesz miał do czynienia z tym problemem. Windows NT cierpi z tego powodu nieporównywalnie mniej niż Windows 95, więc w przypadku tej platformy możesz dowolnie często korzystać z polecenia Program Reset. Osobiście, korzystam z tego polecenia tylko w przypadku gdy aplikacja zablokuje się podczas sesji debuggera. |
Jedną z najlepszych rad, jaką mogę Ci przekazać w sprawie wykrywania błędów, jest korzystanie z programów sprawdzających pamięć, takich jak np. Memory Sleuth firmy TurboPower Software. Być może otrzymałeś ten program jako część Delphi 4 (był on dołączany za darmo do Delphi przez ograniczony okres czasu). Memory Sleuth sprawdza pamięć na okoliczność systematycznego jej gubienia. Program tego typu może zaoszczędzić wiele czasu podczas testowania aplikacji. Jeżeli aplikacja powoduje gubienie pamięci, stanie się ona z pewnością źródłem problemów dla swych użytkowników. Kiedy użytkownicy miewają problemy, miewasz je również Ty. Wykrywając i pozbywając się tego typu usterek odpowiednio wcześnie, zaoszczędzisz sobie czasu, a Twoim użytkownikom frustracji.
Jeżeli nie otrzymałeś programu Memory Sleuth razem z Delphi 4, możesz zamówić go na stronie internetowej firmy TurboPower (www.turbopower.com). Innym programem wykrywającym gubienie pamięci jest BoundsChcecker firmy NuMega Technologies.
Opcje debuggera
Opcje debuggera mogą być ustawiane na dwóch poziomach: projektu i środowiska. Opcje na poziomie projektu były omawiane w poprzednim rozdziale, w sekcjach „Strona Compiler” i „Strona Linker”. Opcje ustawiane na poziomie globalnym znajdują się w oknie ustawień debuggera (Debugger Options). W celu wywołania tego okna należy wybrać polecenie menu ToolsDebugger Options.
U dołu tego okna znajduje się pole wyboru Integrated debugging. Opcja ta decyduje o tym, czy do wykrywania błędów stosowany będzie debugger środowiska IDE. Przy wyłączonej opcji Integrated debugging zintegrowany debugger nie jest używany. Oznacza to, że po kliknięciu przycisku Run program zostanie uruchomiony, ale, ze względu na nieaktywność debuggera, nie będą funkcjonować żadne punkty przerwań.
Okno opcji debuggera składa się z czterech zakładek: General, Event Log, Language Exceptions i OS Exceptions. Każda z tych zakładek jest omawiana w dalszych sekcjach.
Strona General
Strona General (rysunek 10.14) umożliwia modyfikację ogólnych ustawień debuggera.
Rysunek 10.14. Strona General w oknie opcji debuggera |
|
Opcja Map TD32 keystrokes on run nakazuje Edytorowi Kodu stosowanie trybu mapowania naciśniętych klawiszy zgodnego z zewnętrznym Turbo Debuggerem firmy Borland. Cecha ta ma szczególne znaczenie dla osób, które spędziły dużo czasu z tym programem są przyzwyczajone do stosowanych w nim kombinacji klawiszy.
Opcja Make buffers read-only on run ustawia bufory Edytora Kodu w stan „tylko do odczytu” w trakcie pracy programu pod kontrolą debuggera. W wyniku tego po rozpoczęciu sesji debuggera nie ma możliwości edycji kodu źródłowego, aż do momentu zakończenia programu. Ja zawsze wyłączam tę opcję, ponieważ, w trakcie poszukiwania błędów, regularnie dokonuję modyfikacji w kodzie.
Pole wyboru Inspectors stay on top określa, czy okna Inspektora Śledzenia powinny znajdować się na wierzchu względem Edytora Kodu. Jest dobra cecha, ponieważ podczas pracy krokowej będziesz prawdopodobnie chciał mieć wszystkie okna Inspektora na wierzchu.
Opcja Rearrange editor local menu on run powoduje zmianę organizacji menu kontekstowego Edytora Kodu podczas pracy programu pod kontrolą debuggera. Kiedy opcja ta jest aktywna, polecenia menu kontekstowego specyficzne dla procesu wykrywania błędów są przesuwane na szczyt tego menu, dzięki czemu łatwiej można je znaleźć.
Strona Event Log
Strona Event Log umożliwia modyfikację opcji dziennika zdarzeń. Możesz określić maksymalną liczbę komunikatów, jaka ma prawo pojawić się jednocześnie w dzienniku zdarzeń lub pozostawić tę liczbę na poziomie nieograniczonym. Dozwolone jest również określenie rodzaju komunikatów, jakie powinny pojawiać się w dzienniku.
Strona Language Exceptions
Strona Language Exceptions pozwala określić typy wyjątków VCL, które powinny być wyłapywane przez debugger (wyjątki omawiane są w rozdziale 14. „Programowanie zaawansowane”). Najważniejszym elementem tej strony jest opcja Stop on Delphi Exceptions. Jej włączenie powoduje zatrzymywanie programu przez debugger, kiedy wygenerowany zostanie wyjątek. Kiedy opcja ta jest wyłączona, wyjątek VCL jest obsługiwany w zwykły sposób - przez okno dialogowe informujące użytkownika o niepoprawnej sytuacji jaka wystąpiła w programie.
|
Przy włączonej opcji Stop on Delphi Exceptions debugger zatrzymuje wykonanie programu po wystąpieniu wyjątku nawet wtedy, gdy jest on obsługiwany przez Twój program. Jeżeli nie chcesz, aby debugger zatrzymywał się przy każdym wyjątku, wyłącz tę opcję. Opcja ta stanowi następstwo opcji Break on exception stosowanej w poprzednich wersjach Delphi. |
Opcja Exception Types to Ignore określa typy wyjątków, które mają być ignorowane przez debugger. Wszelkie klasy wyjątków umieszczone na tej liście będą pomijane przez debugger i obsługiwane w sposób standardowy. Opcja ta faktycznie jest równoważna wyłączeniu opcji Stop on Delphi Exceptions dla wybranych typów wyjątków.
Aby dodać typ wyjątku do listy, wystarczy kliknąć na przycisku Add i wpisać nazwę klasy wyjątków. Na przykład, aby nakazać debuggerowi ignorowanie wyjątków dzielenia przez zero, należy kliknąć na przycisku Add, a następnie wpisać wartość EDivByZero w pole Exception Type. Proces ten został przedstawiony na rysunku 10.15.
Typ wyjątku dodany do tej listy będzie obowiązywał dla wszystkich projektów (również tych nowo tworzonych).
Strona OS Exceptions
Strona OS Exceptions (rysunek 10.16) określa, czy wyjątki systemu operacyjnego są obsługiwane przez debugger, czy też przez program użytkownika.
Jeżeli w sekcji Handled By ustawiona jest opcja User Program, program jest zatrzymywany przez debuggera w chwili wystąpienia wyjątku. W przypadku aktywnej opcji Debugger wyjątek VCL jest obsługiwany w standardowy sposób - przez wyświetlenie okna informacyjnego z komunikatem informującym użytkownika o nieprawidłowości, jaka wystąpiła w programie.
Rysunek 10.15. Dodawanie elementu do listy wyjątków ignorowanych przez debugger |
|
Rysunek 10.16. Strona OS Exceptions |
|
|
Przy aktywnej opcji Debugger (w sekcji Handled By), debugger zatrzymuje się przy wystąpieniu wyjątku, nawet jeżeli wyjątek ten jest obsługiwany przez Twój program. Jeżeli nie chcesz, aby program zatrzymywany był przy każdym wystąpieniu wyjątku, uaktywnij pole opcji User Program. Opcja ta zastąpiła spotykaną w poprzednich wersjach opcję Break on exception. |
Opcja On Resume określa sposób potraktowania wyjątku po wznowieniu wykonania programu, poprzedzonego wystąpieniem tego wyjątku.
Lista Exception zawiera listę możliwych wyjątków systemu operacyjnego. Aby ustawić opcję dla określonego wyjątku, trzeba wybrać go na liście, a następnie dokonać odpowiednich ustawień w sekcjach Handled By i On Resume. Ikony na prawym marginesie listy wskazują wybrane opcje obsługi i wznawiania.
Podsumowanie
Wykrywanie błędów to nigdy nie kończące się zadanie. Termin „debugging” oznacza znacznie więcej niż tylko śledzenie błędów w programie. Zmyślni programiści uczą się stosować debugger już od samego początku nowego projektu. Można powiedzieć, że debugger w równym stopniu służy wykrywaniu błędów i rozwijaniu oprogramowania. Po przestudiowaniu tego rozdziału powinieneś przyswoić sobie podstawy użytkowania debuggera. Oprócz tego będziesz musiał jeszcze spędzić trochę czasu na rzeczywistej pracy z debuggerem, aby nabrać wprawy. Niemniej jednak teraz wiesz już od czego zacząć.
Warsztat
Warsztat składa się z pytań kontrolnych oraz ćwiczeń utrwalających i pogłębiających zdobytą wiedzę. Odpowiedzi do pytań możesz znaleźć w dodatku A.
Pytania i odpowiedzi
Mój program, uruchamiany pod kontrolą środowiska IDE działał z normalną szybkością. Teraz stał się niesamowicie wolny, dlaczego tak się dzieje?
Więcej niż prawdopodobne jest to, że albo ustawiłeś w swoim programie dużą ilość punktów przerwań, do których następnie wyłączyłeś dostęp i zapomniałeś o nich lub ustawiłeś w swoim kodzie przynajmniej jeden warunkowy punkt przerwania. Przejdź do listy punktów przerwań i usuń punkty, z których nie korzystasz w danej chwili. Zwróć również uwagę na liczbę zmiennych umieszczonych na liście wyrażeń testowych (Watch List).
Potrzebuję znać wartość szesnastkową i dziesiętną jednej ze zmiennych. Czy jest możliwie do zrealizowania w liście wyrażeń testowych?
Tak. Najpierw dodaj zmienną do listy wyrażeń testowych. Następnie kliknij na niej dwukrotnie. Kiedy otwarte zostanie okno dialogowe właściwości podglądu (Watch Properties) wybierz opcję Decimal. Teraz ponownie dodaj tę samą zmienną do okna listy wyrażeń testowych, ale tym razem wybierz opcję Hexadecimal. Oba elementy znajdą się na liście wyrażeń testowych, jedna w formacie dziesiętnym, druga w szesnastkowym.
Chcę, aby program został zatrzymany w pewnym punkcie przerwania w momencie, kiedy jedna ze zmiennych osiągnie określoną wartość, a punkt ten zostanie napotkany ustaloną liczbę razy. Czy jest to możliwe?
Oczywiście. Wpisz wyrażenie warunkowe w pole Conditional okna właściwości punktu przerwania, a liczbę przejść przez ten punkt - w pole Pass Count. Kiedy spełniony zostanie warunek punktu przerwania i osiągnięta zostanie odpowiednia liczba przejść, program zostanie zatrzymany.
Podczas pracy krokowej docieram do funkcji, którą chcę przejrzeć. Kiedy naciskam klawisz F8, punkt wykonania przeskakuje funkcję. Co zrobić, aby dostać się do jej wnętrza?
Kiedy punkt wykonania znajdzie się w linii, w której wywoływana jest funkcja, zamiast klawisza F8 naciśnij klawisz F7 (Trace Into). Teraz możesz krok po kroku możesz wykonać całą funkcję.
Podczas pracy korkowej, debugger nie pokazuje wartości niektórych zmiennych. Czemu tak się dzieje?
Mówiąc krótko, chodzi o optymalizację. Kompilator optymalizuje określone sekcje kodu, a przez to uniemożliwia przeglądanie pewnych zmiennych. W istocie zmienne, z punktu widzenia debuggera, nie istnieją w tym czasie. Aby uniknąć tego problemu można wyłączyć optymalizację (strona Compiler w oknie opcji projektu). Pamiętaj o ponownym włączeniu optymalizacji przed ostateczną kompilacją aplikacji.
Wykonuję metodę linia po linii. Czasami, kiedy dochodzę do wyrażenia kończącego metodę (end), przyciskam ponownie przycisk F8 i nic się nie dzieje. Dlaczego?
Ponieważ po powrocie z danej metody, program nie ma nic więcej do wykonania, więc przechodzi do stanu jałowego. W rzeczywistości w punkcie, do którego dotarł program, nie ma więcej kodu do wykonania, więc debugger zwraca sterowanie do programu, który go wywołał.
W jaki sposób otwiera się okno deasemblacji (CPU)?
Wybierz polecenie menu ViewDebug WindowsCPU. Samo otwarcie okna jednak nie wystarczy, trzeba jeszcze wiedzieć jak z niego korzystać, a to już całkiem inna sprawa!
Quiz
W jaki sposób umieszcza się punkt przerwania w linii kodu?
Co to jest nieprawidłowy punkt przerwania?
W jaki sposób ustawia się warunkowy punkt przerwania?
W jaki sposób można zmienić właściwości elementu występującego na liście wyrażeń testowych?
Podaj najszybszy sposób na dodanie zmiennej do listy wyrażeń testowych.
Jakiego narzędzia należy użyć do przejrzenia pól i metod klasy?
Jak wejść do wnętrza metody w trakcie pracy krokowej?
W jaki sposób można zmodyfikować wartość zmiennej w czasie pracy programu?
Co umożliwia wysyłanie własnych komunikatów do dziennika zdarzeń (Event Log)?
Do czego służy opcja Integrated Debugging, usytuowana w dolnej części okna opcji debuggera?
Ćwiczenia
Załaduj program ScratchPad, który stworzyłeś w rozdziale szóstym „Praca z Projektantem Formularzy i Edytorem Menu”. Umieść punkty przerwań w metodach FileOpenClick i FileSaveClick. Uruchom program. Kiedy wykonanie programu zostanie wstrzymane dokonaj inspekcji klas OpenDialog i SaveDialog.
Kontynuując ćwiczenie pierwsze, po zatrzymaniu programu w punkcie przerwania przejdź do pracy krokowej i przeanalizuj operacje jakie kolejno wykonuje program.
Załaduj program DebugTst stworzony wcześniej w tym rozdziale. Umieść punkt przerwania we wnętrzu metody WatchBtnClick. Dodaj zmienne S i X do listy wyrażeń testowych, każdą z nich po cztery razy. Edytuj każdy z podglądanych elementów zmieniając jego opcje wyświetlania. Uruchom program, a następnie wykonaj krokowo metodę obserwując zmiany zachodzące w liście wyrażeń testowych.
Do metody wspomnianej w ćwiczeniu trzecim dodaj warunkowy punkt przerwania. Umieść go bezpośrednio za linią kodu X:=Width;. Jako warunku użyj wyrażenia X = 0, a następnie uruchom program. Co się stanie z programem? (Nic, ponieważ zmienna X nigdy nie osiąga wartości zero.)
Kontynuując ćwiczenie czwarte, edytuj warunkowy punkt przerwania i ustaw nowy warunek postaci X > 400. Uruchom program. Zmień rozmiar okna, po czym kliknij na przycisku Test Podglądu. Powtórz te czynności kilkakrotnie, zmieniając za każdym razem rozmiar okna. Co się dzieje? (Tym razem debugger zatrzymuje się w punkcie przerwania, kiedy szerokość okna przekracza 400 pikseli.)
Załaduj dowolny program i przejdź do Edytora Kodu. Umieść kursor w dowolnej linii kodu i wybierz polecenie menu kontekstowego - Run to Cursor. Poeksperymentuj z programem, aż napotkany zostanie punkt przerwania.
Załaduj ponownie program DebugTst. Umieść punkt przerwania we wnętrzu metody WatchBtnClick i uruchom program. Kiedy program zatrzyma się w punkcie przerwania, użyj Inspektora Śledzenia do przejrzenia struktury WatchBtn.
442 Część II
442 C:\Dokumenty\Roboczy\Delphi 4 dla kazdego\10.doc
C:\Dokumenty\Roboczy\Delphi 4 dla kazdego\10.doc 441
Rozdzia³ 10 ♦ Wykrywanie błędów w aplikacjach 441