Debugowanie programów w C# w Visual Studio 2008

ŁF

Artykuł w trakcie pisania. Nie poprawiać, nie zwracać uwag.

1 Debugowanie programów w C# pod Visual Studio 2008
     1.1 Wstęp
     1.2 Debuger
     1.3 Jak tego używać
          1.3.1 Podstawowe narzędzia

Debugowanie programów w C# pod Visual Studio 2008

Wstęp

Artykuł został napisany na bazie Visual Studio 2008 Professional Edition, ale opisane narzędzia istnieją i działają tak samo także w innych wersjach tego środowiska. Ponadto artykuł ten jest oparty o napisaną niegdyś przeze mnie wersję dla Delphi.

Jeśli masz już za sobą stworzenie większego kawałka kodu, to na pewno wiesz też, że jego pomyślne skompilowanie to dopiero początek walki o jego poprawne działanie. Na forum jest pełno tematów typu "co jest źle w tym kodzie", "czemu to nie działa", "potrzebuję zmienić jedną rzecz" itp. Kod się kompiluje, uruchamia, często nawet nie wyskakują błędy - ale nie działa prawidłowo. Coś jest nie tak, właściwie to wszystko jest nie tak, ale co to powoduje i jak to sprawdzić?

Debuger

Dawno, dawno temu, za siedmioma górami i siedmioma lasami budowano olbrzymie komputery, lampowe pierwowzory malutkich pecetów. Owe komputery składały się z olbrzymiej ilości lamp (tranzystory nie były wtedy jeszcze w powszechnym użyciu), a lampy niestety miały (i mają) dwie wady: duże zużycie mocy i krótki czas życia. Krótki czas działania, wynikający z istnienia wielu lamp o krótkiej żywotności powodował, że dawne komputery częściej nie działały niż wykonywały obliczenia. Ponadto ich rozmiar powodował spore problemy z namierzeniem usterki. Któregoś razu okazało się, że kolejny błąd w działaniu systemu spowodował robak, którego śmiertelnie popieścił prąd w trakcie wędrówki po płycie. Robak (konkretnie pluskwa), po angielsku bug, nadał nazwę całemu procesowi wyszukiwania i usuwania błędów w programie (poczucie humoru programistów...) - debugowaniu, po polsku "odpluskwianiu".

Początkujący programiści zanim nauczą się posługiwać debuggerem, czasem korzystają z tricku polegającego na umieszczanie w kodzie programu linijki (bądź linijek) wypisujących dokąd to kod się wykonał zanim się wykrzaczył (MessageBox.Show() - brzmi znajomo?...). To jednak wymaga wielu kompilacji programu, i celowania na chybił/trafił w podejrzane miejsca, a co za tym idzie jest to czaso- i pracochłonne. Dlatego Visual Studio zawiera pakiet narzędzi ułatwiających, czy też może raczej w ogóle umożliwiających wyłapywanie błędów w kodzie - debugger.

Debugger (odpluskwiacz ;-)) VS umożliwia uruchomienie programu (oraz podłączenie się do już uruchomionego programu) w trybie pracy krokowej, przejście do pracy ciągłej i z powrotem do pracy krokowej, zatrzymanie warunkowe bądź bezwarunkowe, sprawdzenie wartości zmiennych, stosu wywołań i stanu wątków. Umożliwia także wykonanie większości kodu "z palca" - wpisanego w odpowiednie okno (Immediate Window - Dwbug -> Windows -> Immediate) już po skompilowaniu i uruchomieniu kodu, co umożliwia zmianę stanu aplikacji lub jego bardziej zaawansowany podgląd.

Jak tego używać

Najważniejszym elementem debuggera są tzw. breakpoints (punkty zatrzymania - tu lepiej chyba też jest używać angielskiego słowa). Breakpoint to specjalnie oznaczona linijka kodu, która spowoduje zatrzymanie PRZED NIĄ wykonywania programu - program przejdzie w tryb pracy krokowej, co pozwala na sprawdzenie rzeczy wymienionych akapit wyżej. Breakpoint stawia się klikając dwukrotnie na lewym marginesie okna edycji kodu (niezależnie od tego, czy aplikacja jest uruchomiona, czy nie):
breakpoint.png
Kiedy program zatrzyma się na breakpoincie wygląda to tak:
breakpoint-hit.png

No właśnie, a co to jest praca krokowa? Otóż jest to wykonywanie programu pod kontrolą debuggera - uruchamianie np.: jednej linijki kodu, jednej instrukcji procesora, bloku kodu... W trakcie zatrzymania kodu (ale nie zamknięcia programu, zatrzymanie a zamknięcie to dwie różne rzeczy) masz do dyspozycji pewne fantastyczne narzędzie pozwalające na podgląd wartości zmiennych i stałych. Można nim także zmieniać wartości zmiennych. Narzędzie dostępne jest pod dwiema postaciami: w oknie Locals (Debug->Windows->Locals). Zobaczysz tu listę wszystkich zmiennych lokalnych:
locals.png
oraz w wygodniejszej postaci - pod kursorem myszki, jednak zobaczysz tu tylko wartość wskazanej przez siebie zmiennej. Wystarczy najechać na zmienną i przez chwilę nie ruszać myszką:
locals-mouse.png

Inne narzędzia pozwalające testować zatrzymany program dostępne są w menu Debug->Windows. Od razu mówię, że do skutecznego debugowania programu przydaje się skompilowanie (Shift+F6, Build->Build nazwa-Twojego-projektu), a jeszcze lepiej zbudowanie całego projektu (albo i solution) od nowa z ustawionym trybem kompilacji Debug:
debug-solution-configuration.png
Rebuild projektu:
rebuild.png
Rebuild całego solution, czyli wszystkich projektów za jednym zamachem:
rebuild-solution.png
Pewnym ułatwieniem może być to, że uruchamiany w trybie debugowania projekt może być zbudowany w trybie Release, wszystkie informacje i właściwości nadal będą dostępne. Jeśli jednak projekt korzysta z innych projektów w postaci bibliotek, to muszą być one skompilowane w trybie Debug. Visual Studio uprzedzi, jeśli postawisz breakpoint w kodzie, który nie będzie umożliwiał zatrzymania się przez to, że jest skompilowany do wersji Release.

Podstawowe narzędzia

W menu Debug masz kilka interesujących pozycji:

  • Attach to process, Step into (F11), Step over (F10), Toggle breakpoint (F9);
  • dodatkowo przed wystartowaniem kodu - Start Debugging, Start without debugging,
  • dodatkowo po uruchomieniu kodu i zatrzymaniu na breakpoincie - Continue, Stop debugging, Restart, QuickWatch

Po pierwsze i najważniejsze - start debugowania. Możesz zrobić to na dwa sposoby. F5 - uruchomić program, jeśli wykonywanym w kodzie są jakieś breakpointy kod zatrzyma się w takim miejscu (zatrzyma, nie zakończy!). F10 (Step over) lub F11 (Step into) - uruchomienie debugowania od razu w trybie pracy krokowej. W tym drugim przypadku breakpointy nie są potrzebne, program zatrzyma się na pierwszej linijce kodu przed jej wykonaniem. Jeśli debugujesz aplikację hostowaną np. w iis (strona internetowa, webservice itp.), to do danego miejsca kodu możesz dostać się tylko poprzez uruchomienie debugowania aplikacji przez F5 i postawienie breakpointa w interesujacym Cię miejscu.

Co robi Step over? Po pierwsze - skrót klawiszowy - F10 - zapamiętaj go. Po drugie - załaduj do VS jakiś projekt (chociażby File->New->Project->Console application) i wciśnij F10. Program zostanie skompilowany (o ile wcześniej nie był), uruchomiony, a potem zatrzymany (ale nie zakończony). W edytorze jedna linijka kodu została podświetlona - w tym miejscu program został spauzowany. Ponieważ plikiem, w którym zaczyna się wykonywanie kodu w aplikacji konsolowej, jest plik cs i statyczna publiczna metoda Main w klasie Program, masz przed swoim oczami najprawdopodobniej zawartość pliku Program.cs, i linijkę ze słowem kluczowym { (w trybie Release jest to linijka }):
step-over-start.png

Jeśli znowu wciśniesz F10, wykona się kod w tej linijce (tutaj rozpoczęcie bloku programu - {), i zostanie podświetlona następna linijka. W ten sposób możesz wykonać cały program - to jest właśnie praca krokowa. W międzyczasie możesz obserwować, która część kodu jest wykonywana i jak jest wykonywana, a jeśli kod nie wykonuje się tak, jak według Ciebie powinien, to znaczy że namierzyłeś błąd (co nie znaczy, że łatwo znajdziesz jego powód i go usuniesz).

Jeśli chcesz skończyć debugowanie, po prostu wciśnij F5 (skrót do Debug->Continue) - program wykona się do końca zatrzymując się jedynie na breakpointach. Jeśli chcesz zakończyć od razu debugowanie i nie przeszkadza Ci, że program zostanie zabity, wciśnij Sfift+F5 (Stop debugging). Uwaga! W przypadku debugowania aplikacji hostowanej np. w IIS Shift+F5 nie zakończy jej działania, tylko odłączy VS od IIS.

Step Over wykonuje jedną instrukcję, nieważne czy jest to przypisanie wartości do zmiennej, czy wywołanie Twojej funkcji albo procedury. Jeśli trzymasz się konwencji jedna instrukcja - jedna linijka, to Step Over działa tak, jakbyś wykonywał kod linijka po linijce nie wchodząc wewnątrz tego, co wykonuje się w każdej z linijek.
Jeśli program nigdy nie dotrze do kolejnej instrukcji/linijki (np.: zawiesi się wskutek źle skonstruowanej pętli wewnątrz jednej z metod), efekt będzie taki, jakbyś po prostu uruchomił program. Shift+F5 zakończy zawieszony program i jego debugowanie, możesz też użyć polecenia Break All (w menu Debug lub pod postacią przyciska pauzy w górnym pasku z przyciskami), wtedy program zostanie zatrzymany na aktualnie wykonywanej instrukcji.

Co zrobić, jeśli chcesz wykonać linijkę po linijce wywoływaną metodę? Ano, po pierwsze musisz mieć kod źródłowy projektu, w którym się znajduje i musi być on skompilowany wedle instrukcji podanych w części "Jak tego używać". Jeśli tak jest, zerknij na pozycję Debug->Step into - skrót klawiszowy F11 (też go zapamiętaj). Step into pozwala na wykonanie kodu rzeczywiście linijka po linijce - z "wchodzeniem" do wnętrza wszystkich metod/funkcji/procedur. Jest to bardzo przydatne, aczkolwiek na dłuższą metę dość męczące (bo przeważnie nie interesuje nas wykonanie linijka po linijce wszystkich 153 funkcji siedzących w debugowanym kawałku programu, tylko kilka konkretnych, które są podejrzane o powodowanie błędów. Dlatego opcji Step into używa się przeważnie w kombinacji ze Step Over.

Różnicę pomiędzy Step into a Step Over doskonale zauważysz po wykonaniu w trybie krokowym poniższego kodu (raz przejedź przez kod programu wciskając F10, a drugi raz - F11):

class Program
{
	public int a()
	{ 
		return 44; 
	}

	static void Main(string[] args)
	{
		new Program().a();
	}
}

Jeżeli już masz dość wykonywania programu w trybie krokowym, ale nie chcesz, żeby się wykonał do końca, możesz go zakończyć - służy do tego opcja Stop debugging (Debug->Stop debugging albo przycisk stop na górnym pasku z przyciskami) - skrót Shift+F5. Może ona zatrzymać program nie tylko w trybie pracy krokowej, ale także program uruchomiony poprzez F5, co bardzo przydaje się, gdy program zawiesi się (czyli wpadnie w nieskończoną pętlę) w trakcie pisania kodu.
Jeśli chcesz wykonać program do końca, naciśnij F5. Program zostanie wykonany, jednak na każdym napotkanym breakpoincie nastąpi zatrzymanie. Jeśli chcesz tego uniknąć, musisz użyć opcji Debug->Detach all - debuger zostanie odłączony od wykonywanego programu, czyli nastąpi taka sytuacja, jakbyś uruchomił program bez debuggera (Ctrl+F5).

W trakcie szukania błędów przydaje się (o ile nie jest najważniejszy) podgląd wartości zmiennych - bo przecież to właśnie nieprawidłowe wartości tych zmiennych (albo nieprawidłowe przyjęcie założeń co do ich wartości) najczęściej powodują błędy. Do podglądu zawartości zmiennych tylko w trakcie pracy krokowej programu służy wcześniej wspomniane narzędzie Locals wspomagane najazdami myszką na zmienne oraz kolejne, Watch (Debug->Windows->Watch):
watch.png
Nazwy zmiennych wpisuje się "z palca" w lewą kolumnę ("Name"). Ja wpisałem tam this.

CDN

0 komentarzy