#26 — debugowanie aplikacji

By 21 May 2015 May 15th, 2016 Kurs Javy

Dzisi­aj ostat­nia z lekcji w cyk­lu o Javie — o debu­gowa­niu aplikacji. Debu­gowanie to pro­ces wyszuki­wa­nia błędów i ich usuwa­nia — Java oraz Eclipse dostar­cza­ją nam wielu wygod­nych narzędzi, które pozwala­ją na metody­czne i wygodne podejście.

Ale zan­im zaczniemy cieka­wost­ka (o której sam nie wiedzi­ałem, dopiero Ania mi to uświadomiła): ter­min debu­gowanie przyp­isu­je się admi­rał Grace Hop­per, pracu­jącej w lat­ach 40-tych ubiegłego wieku przy kom­put­erze Mark II. Prob­le­mem w tam­tym cza­sie były ćmy, które blokowały elek­tro­me­chan­iczne mech­a­nizmy kom­put­era powodu­jąc błędy — konieczne było więc usuwanie ich z wnętrza kom­put­era, co później przyjęło się jako określe­nie pro­ce­dury usuwa­nia błędów w sys­temach w ogóle. Sko­ro znamy już his­torię, zajmi­jmy się jej wdroże­niem w życie :)

Lekcja

Na początku wyjaśni­jmy sobie przede wszys­tkim na czym pole­ga debu­gowanie w Javie. Narzędzia, które mamy dostęp­ne pozwala­ją nam na wstrzy­manie wykony­wa­nia pro­gra­mu w wybranym miejs­cu, a następ­nie możli­wość zbada­nia wartoś­ci zmi­en­nych w tym momen­cie oraz dal­szego wykony­wa­nia pro­gra­mu ‘krok po kroku’. Dzię­ki temu może­my dojść do przy­czyny prob­le­mu sprawdza­jąc, gdzie ‘gubimy’ potrzeb­ną nam infor­ma­cję lub co się z nią dzieje. Nieroz­er­wal­nie z debu­gowaniem pow­iązane jest poję­cie break­point’u — break­point to nic innego jak miejsce, w którym dzi­ałanie naszego pro­gra­mu zostanie ‘wstrzy­mane’ w celu dal­szej analizy.

Należy tutaj pod­kreślić, że samo włącze­nie pro­ce­su debu­gowa­nia spowol­ni dzi­ałanie naszej aplikacji (oczy­wiś­cie tylko w cza­sie debu­gowa­nia) — maszy­na wirtu­al­na Javy wymienia dużo więcej infor­ma­cji ze środowiskiem niż robiła­by to nor­mal­nie, nie przeprowadza też opty­mal­iza­cji, które mogą mieć miejsce w nor­mal­nym działaniu.

System.out.println / logger.debug()

Zan­im prze­jdziemy do nau­ki debug­gowa­nia, parę słów o częs­to stosowanym pode­jś­ciu — czyli bard­zo ‘gęstym’ logowa­niu. Oczy­wiś­cie odradza­my uży­cie System.out.println na rzecz log­gerów, ale idea pozosta­je ta sama — po co debu­gować, sko­ro mogę uży­wać metod takich jak logger.debug() i w razie potrze­by włączyć wyp­isy­wanie tych infor­ma­cji na konsolę?

Praw­idłowa odpowiedź jest jak zwyk­le pośrod­ku ;) Stosowanie logger.debug() jest równie ważne co samo debu­gowanie — w sys­temach rozpros­zonych, w których dzi­ała wiele kom­po­nen­tów, uru­chomie­nie każdego z nich w try­bie debu­gowa­nia było­by pra­cochłonne, a częs­to też niemożli­we z jakichś przy­czyn. Wtedy warto włączyć wyp­isy­wanie logów na poziomie DEBUG we wszys­t­kich kom­po­nen­tach, z których korzys­tamy, a debu­gować tylko kom­po­nent, nad którym pracujemy.

Właś­ci­we uży­cie logerów (takie, które poz­woli nam na pod­staw­ie logó dojść do tego, co się stało) jest niełatwe, ale nie zwal­nia nas z koniecznoś­ci umiejęt­nego posługi­wa­nia się debug­gerem! Cza­sem moż­na iść na skró­ty i sko­rzys­tać z log­gera, jeśli wiesz, gdzie leży prob­lem i chcesz to tylko potwierdz­ić, ale w więk­szoś­ci przy­pad­ków umiejętne posługi­wanie się debug­gerem oszczędzi Ci wiele cza­su i nerwów.

Ustawiamy breakpointy w Eclipse

Pier­wszym krok­iem w debu­gowa­niu jest ustaw­ie­nie break­pointów. Moż­na to zro­bić na dwa sposoby:

Klikamy prawym przyciskiem myszy na szare pole obok edytora kodu i z menu wybieramy 'Toggle breakpoint'

Klikamy prawym przy­ciskiem myszy na szare pole obok edy­to­ra kodu i z menu wybier­amy ‘Tog­gle breakpoint’


Dwukrotnie klikamy obok linii, na którą chcemy ustawić breakpoint

Dwukrot­nie klikamy obok linii, na którą chce­my ustaw­ić breakpoint

Są one równoważne i dzi­ała­ją w taki sam sposób. Aby usunąć break­point, postępu­je­my w ana­log­iczny sposób.

Break­pointy ustaw­iamy w lin­ijkach, w których jest kod! Tzn. Nie ustaw­iamy ich na syg­naturze metody, na klam­rach itp. Warunk­iem zatrzy­ma­nia się dzi­ała­nia pro­gra­mu jest wyko­nanie lin­ij­ki oznac­zonej break­pointem — jeśli nasz break­point zna­j­du­je się w bloku if (), który nie zostanie wyko­nany, to break­point także nie zadziała.

Jeśli chodzi o lokaliza­cję break­pointów, to ustaw­iamy je przed miejscem, w którym pode­jrze­wamy, że może wys­tąpić prob­lem. Break­pointów może­my mieć wiele w naszej aplikacji, dzi­ałanie pro­gra­mu będzie wtedy wstrzymy­wane za każdym razem, kiedy Java natrafi na break­point. Po zatrzy­ma­niu dzi­ała­nia pro­gra­mu nie moż­na ‘cofnąć’ się, musis­my ustaw­ić break­point wcześniej i ponown­ie uru­chomić debu­gowanie. Dlat­ego w wym przy­pad­ku, częs­to więcej == lepiej :)

Co ważne, dzi­ałanie pro­gra­mu zostanie wstrzy­mane przed oznac­zoną lin­ią — tzn nie zostanie ona jeszcze wyko­nana w momen­cie zatrzy­ma­nia kodu.

Debugowanie aplikacji w Eclipse

Debu­gowanie aplikacji pod Eclipse sprowadza się do jej uru­chomienia w szczegól­ny sposób — zami­ast stan­dar­d­owego przy­cisku, uży­wamy tego, oznac­zonego ikoną robaczka: Screenshot_3

Aplikac­ja uru­cho­mi się wtedy (możli­we jest także uru­chomie­nie w ten sposób np. ser­w­era Tom­cat) w try­bie debu­gowa­nia, po napotka­niu na pier­wszy break­point Eclipse zapro­ponu­je zmi­anę per­spek­ty­wy na ‘Debug’ , na co się zgadza­my. Zobaczymy wtedy poniższy ekran:

Widok Eclipse w perspektywie 'Debug'

Widok Eclipse w per­spek­ty­wie ‘Debug’

Poza stan­dar­d­owy­mi okna­mi znany­mi nam już z nor­mal­nej per­spek­ty­wy Java, mamy tutaj dodatkowe: Debug, Vari­ables oraz Breakpoints.

Okno Break­points to lista wszys­t­kich break­pointów (może­my się między nimi łat­wo przełaczać szuka­jąc czegoś w kodzie).

Okno Debug dostar­cza nam infor­ma­cji o wątku, który został wstrzy­many, jak wyglą­da stos wywołań (która funkc­ja jakiej klasy została uży­ta, żeby wywołać naszą funkcję — jest to po pros­tu kon­tekst, w jakim dany frag­ment kodu został wywołany).

Trze­cie okienko — Vari­ables — jest praw­dopodob­nie najczęś­ciej uży­wane, a dostar­cza nam infor­ma­cję o wszys­t­kich zmi­en­nych, jakie są dostęp­ne w tym miejs­cu. Warto zrwró­cić uwagę na to, co wspom­i­nałem wcześniej — w tej lin­i­jce zmi­en­na i nie jest jeszcze widocz­na. W tym okienku może­my pode­jrzeć wartość tej zmi­en­nej a także, jes­li ma ona jakieś pola, pode­jrzeć ich wartoś­ci. Dzię­ki temu mamy obraz tego co ‘widzi’ pro­gram w danym momen­cie. Nie może­my jed­nak zmieni­ać tych wartości.

W ten sposób może­my uzyskać infor­ma­cję co dzieje się ‘w tym momen­cie’, prze­jdźmy zatem do ‘podąża­nia’ za kodem — wykony­wa­nia pro­gra­mu krok po kroku.

Sterowanie działaniem programu w trybie debugowanie

Do sterowa­nia dzi­ałaniem pro­gra­mu służą nam dwie grupy przy­cisków (Eclipse ofer­u­je znacznie więcej opcji, ale te 6 przy­cisków jest pod­sta­wowa i zna­jdziesz je w każdym IDE)

Sterowanie procesem debugowania

Screenshot_5

Te przy­cis­ki pozwala­ją nam na wznowie­nie dzi­ała­nia pro­gra­mu po jego zatrzy­ma­niu przez break­point (przy­cisk po lewej) lub zakończe­niu go całkowicie (przy­cisk po prawej). Środ­kowy przy­cisk pozwala ‘wstrzy­mać’ dzi­ałanie pro­gra­mu ręcznie.

Sterowanie działaniem

Screenshot_6

Te przy­cis­ki są bardziej intere­su­jące, bo pozwala­ją nam kon­trolować dzi­ałanie pro­gra­mu ‘krok po kroku’. Przy­cis­ki te w kole­jnoś­ci od lewej do prawej to:

  • Step into (klaw­isz skró­tu: F5) — wykonu­je jed­ną oper­ację (prze­chodzi lin­ijkę dalej). Jeśli obec­na lin­ij­ka była wywołaniem funkcji, prze­chodzi do wnętrza tej funkcji
  • Step over (F6) — wykonu­je jed­ną oper­ację (prze­chodzi lin­ijkę dalej). Jeśli obec­na lin­ij­ka była wywołaniem funkcji, wykonu­je ją i wznaw­ia debu­gowanie po jej wykonaniu
  • Step return (F7) — kon­tynu­u­je dzi­ałanie aż do koń­ca bieżącej metody i wraca do try­bu debu­gowa­nia po zrwóce­niu wartoś­ci z tej metody

Najczęś­ciej będziesz uży­wała środ­kowej opcji — uży­wanie F5 szy­bko prze­niesie Cie do get­terów i set­terów oraz metod języ­ka, gdzie łat­wo się pogu­bić (wtedy możesz użyć F7, aby ‘wró­cić’ poziom wyżej). Te trzy proste oper­ac­ja pozwala­ją wykony­wać kod pro­gra­mu lin­ij­ka po linijce.

Podsumowanie

W tej lekcji nauczyłaś się, jak debu­gować aplikację — szukać błedów poprzez ręczną anal­izę jej stanów (porówny­wanie wartoś­ci zmi­en­nych w danym miejs­cu z oczeki­wany­mi, sprawdzanie warunk­ów itp). Dzię­ki temu będziesz w stanie znaleźć źródło prob­lemów szy­b­ciej i usunąć problem :)

To także ostat­nia lekc­ja z cyk­lu kur­su Javy — wiedza w nim zawarta jest wystar­cza­ją­ca, abyś samodziel­nie zaczęła się rozwi­jać w pro­gramowa­niu, ćwiczyła, prak­tykowała i samodziel­nie poszuki­wała informacji.

To jed­nak zde­cy­dowanie nie koniec! Zachę­camy do czy­ta­nia serii Niezbęd­nik Junio­ra oraz Pier­wsza pra­ca w IT, gdzie część infor­ma­cji być może będzie się pow­tarzać, ale część będzie też nowa. Sam kurs Javy także będzie lekko ewolu­ował — pojaw­ią się cza­sem rozsz­erzenia lekcji, uzu­pełn­imy rozwiąza­nia, będziemy go przeglą­dać w poszuki­wa­niu ele­men­tów, które moglibyśmy posz­erzyć albo uprościć.

Czekamy też na infor­ma­c­je od Was, czytel­ników, bo to dla Was ten blog ist­nieje :) Pisz­cie w komen­tarzach, co chci­ały­byś­cie zobaczyć na blogu w przyszłoś­ci lub czym chci­ały­byś­cie, abyśmy się zajęli. Czekamy na infor­ma­c­je od Was, byśmy wspól­nie posz­erza­li naszą wiedzę z Javy!

Licencja Creative Commons

Jeśli uważasz powyższą lekcję za przy­dat­ną, mamy małą prośbę: pol­ub nasz fan­page. Dzię­ki temu będziesz zawsze na bieżą­co z nowy­mi treś­ci­a­mi na blogu ( i oczy­wiś­cie, z nowy­mi częś­ci­a­mi kur­su Javy). Dzięki!