IntelliJ IDEA w akcji – Debuggowanie kodu

Debuggowanie to proces, który ułatwia szukanie i analizę błędów w kodzie źródłowym. Może też posłużyć w celu prześledzenia pewnej ścieżki wykonania w programie, który jest skomplikowany, a którego jeszcze dobrze nie znamy (np. gdy dołączamy na nowy projekt lub zmieniamy pracę).

Debuggowanie to bardzo potężne narzędzie. Korzystając z IntelliJ IDEA do debuggowania kodu, możesz:

  • wykonywać swój program krok po kroku, linia po linii,
  • sprawdzać wartości zmiennych w trakcie działania programu,
  • ustawiać breakpointy, które zatrzymują program w pewnych okolicznościach, abyś mógł sprawdzić stan obiektów,
  • zmienić wartości zmiennych w programie, który już został uruchomiony.

IntelliJ IDEA ma bardzo wiele do zaoferowania w kontekście debuggowania. Poniżej zaprezentuję Ci najważniejsze funkcje tego mechanizmu na prostym przykładzie. Będziemy debuggować poniższą klasę – utworzyłem nowy projekt w IDEA i dodałem ją do projektu (w taki sam sposób, jaki zaprezentowałem w poprzednich rozdziałach):

package com.kursjava;

public class DebuggowaniePrzyklad {
  public static void main(String[] args) {
    int[] liczby = new int[] { 2, 10, 0, -25, 2, 100 };

    System.out.println(znajdz(10, liczby));
    System.out.println(znajdz(2, liczby));
    System.out.println(znajdz(-33, liczby));
  }

  public static int znajdz(int wartosc, int[] tab) {
    for (int i = 1; i <= tab.length; i++) {
      if (tab[i] == wartosc) {
        return i;
      }
    }

    return -1;
  }
}

Metoda znajdz ma na celu zwrócenie pierwszego indeksu w tablicy przekazanej jako argument, pod którym znajduje się liczba przekazana w argumencie wartosc. Jeżeli podanej liczby nie ma w tablicy, metoda ta powinna zwrócić wartość -1.

W tej metodzie można zauważyć trzy następujące problemy związane ze zmienną pętli:

  1. Inicjalizujemy ją wartością 1, zamiast 0, więc zawsze pominiemy pierwszy element tablicy.
  2. Jeżeli tablica posiada tylko jeden element, to w związku z punktem nr 1, wyjdziemy w pierwszym obiegu pętli poza zakres tablicy (bo jedyny element w tablicy jednoelementowej ma indeks 0, a zmienna pętli ma na początku ustawianą wartość 1).
  3. Warunek zakończenia pętli jest nieprawidłowy – powinniśmy użyć operatora < zamiast <=. Jeżeli szukanej liczby nie będzie w tablicy, to wykonanie tej metody zakończy się wyjątkiem ArrayIndexOutOfBoundsException.

Dodałem do projektu konfigurację uruchomieniową (zgodnie z opisem z poprzednich rozdziałów) dla klasy DebuggowaniePrzyklad i uruchomiłem program skrótem Shift + F10 – wynik jest następujący:

1 4 Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: Index 6 out of bounds for length 6 at com.kursjava.DebuggowaniePrzyklad.znajdz(DebuggowaniePrzyklad.java:14) at com.kursjava.DebuggowaniePrzyklad.main(DebuggowaniePrzyklad.java:9)

Zgodnie z oczekiwaniami, program zakończył się błędem, oraz wypisał nieprawidłowy wynik dla linii:

System.out.println(znajdz(2, liczby));

Liczba 2 w tablicy jest na samym początku, więc zamiast wypisać 4, program powinien wyświetlić 0, ale nasza metoda znajdz pomija (nieprawidłowo) pierwszy element tablicy. Uruchomimy teraz program w trybie debuggowania.

Tryb debuggowania

Aby debuggować program, uruchamiasz go prawie tak samo, jak w normalnym trybie. Musisz najpierw zdefiniować konfigurację uruchomieniową – może to być ta sama konfiguracja, której używasz do „zwykłego” uruchamiania aplikacji.

Następnie, należy użyć skrótu Shift + F9 lub kliknąć na przycisk z „robakiem”, znajdujący się po prawej stronie przycisku do normalnego uruchamiania programu:

Lokalizacja przycisku służącego do uruchamianie programu w trybie debuggowania
Lokalizacja przycisku służącego do uruchamianie programu w trybie debuggowania

Jeżeli uruchomisz teraz program w trybie debuggowania, to wykona się on dokładnie tak samo, jak w normalnym trybie – nie będzie żadnej różnicy. Stanie się tak dlatego, ponieważ jeszcze nie ustawiliśmy w naszym kodzie źródłowym żadnego miejsca, w którym działanie programu miałoby zostać wstrzymane.

Aby dodać breakpoint, który spowoduje zatrzymanie programu, kliknij lewym przyciskiem myszki na szary margines po lewej stronie edytora kodu – np. w pierwszej linii metody main:

Tworzenie i reprezentacja breakpointu w edytorze kodu
Tworzenie i reprezentacja breakpointu w edytorze kodu

Na zaznaczonym marginesie możesz dodawać i usuwać breakpointy, klikając je lewym przyciskiem myszki. IDEA zaznacza breakpoint jako czerwoną kropkę, a cała linia jest lekko podświetlona na czerwono.

Panele debuggowania

Gdy tym razem uruchomisz program w trybie debuggowania (Shift + F9), wykonanie programu zatrzyma się na pierwszej linii w programie, która miałaby się wykonać, a na której ustawiony jest breakpoint. W naszym przypadku jest to linia z definicją tablicy liczby.

W oknie IDEI pojawią się nowe panele i przyciski, które powiązane są z trybem debuggowania:

Opis elementów trybu debuggowania
Opis elementów trybu debuggowania

Zaraz zobaczymy w akcji działanie wielu z zaznaczonych powyżej elementów, ale najpierw krótko je opiszę:

  • W edytorze kodu na niebiesko podświetlona została linia, na której wykonanie programu się zatrzymało. Istotne jest tutaj to, że ta linia nie została jeszcze wykonana – jest to pierwsza linia kodu, która zostanie wykonana, gdy program zacznie znowu działać bądź każesz IDEA wykonać tę linię.
  • Panel z przyciskami „Wykonywanie kodu krok po kroku” – przyciski te służą do wykonywania kolejnych fragmentów kodu, ale znajdziesz tam także dodatkowe funkcjonalności. Najczęściej używane przyciski z tego panelu to:
    • Show execution point (skrót Alt + F10) – pierwszy od lewej – powoduje nawigację w edytorze kodu do miejsca, gdzie nastąpiło zatrzymanie wykonania.
    • Step Over (skrót F8) – drugi od lewej – powoduje wykonanie pojedynczej, aktualnej linii kodu, i przejście do następnej, na której wykonanie ponownie jest zatrzymywane. Uwaga: ta „pojedyncza” linia kodu może być złożona z wywołań wielu metod, więc takie wykonanie linii może faktycznie być wykonaniem dużo większego fragmentu kodu. Dla przykładu, linia kodu może wywoływać metodę, która składa się z 10 linii kodu, w których wywoływane są kolejne metody itd.
    • Step Into (skrót F7) – trzeci od lewej – wejdź „do środka” instrukcji – jeżeli aktualna linia kodu wywołuje pewną metodę, to możesz „wejść” do treści tej metody i kontynuować debuggowania w tej metodzie.
    • Step Out (skrót Shift + F8) – piąty od lewej – dokończ wykonywanie aktualnej metody i zatrzymaj wykonywanie programu na pierwszej instrukcji, która po niej następowała.
    • Run to Cursor (skrót Alt + F9) – siódmy od lewej – wznów działanie programu i zatrzymaj go ponownie, gdy wykonanie dojdzie do linii kodu, w której aktualnie ustawiłeś kursor w edytorze kodu.
    • Evaluate Expression... (skrót Alt + F8) – przedostatni – potężne narzędzie do sprawdzania wartości złożonych wyrażeń – możesz np. na rzecz pewnego obiektu wywołać metodę i sprawdzić, co zwraca.
  • Panel Variables (Wartości pól i zmiennych) – bardzo przydatny panel, w którym wyświetlane są wartości wszystkich zmiennych i obiektów, które są aktualnie dostępne w kodzie źródłowym w miejscu, w którym zatrzymaliśmy się z wykonywaniem programu. Możemy przeglądać zagnieżdżone wartości obiektów, dzięki czemu mamy w danym momencie wgląd w stan naszego programu, co ułatwia szukanie i analizowanie błędów. Wartości w tym panelu są aktualizowane wraz z wykonywaniem programu.
  • Panel Frames (Stos wywołań metod) – lista wykonywanych metod – gdy metoda A wykona metodę B, a metoda B metodę C, to na tej liście zobaczysz metody C, B, A – w kolejności odwrotnej do ich wywoływania. Dzięki tej liście widzisz, jak wyglądało wykonywanie programu do momentu wykonania aktualnej metody. Możesz kliknąć na nazwę metody na tej liście, a IDEA przeniesie Cię do jej definicji.
  • Panel z przyciskami „Wznawianie i zatrzymywanie programu” – dwa najważniejsze przyciski na tym panelu to „Resume Program” (skrót F9, drugi od góry), oraz „Stop” (Ctrl + F2, czwarty od góry). Pierwszy wznawia wykonywanie programu, które będzie trwało do jego zakończenia, bądź napotkania kolejnego breakpointa. Drugi całkowicie zatrzymuje program.

Zobaczymy teraz powyższe funkcjonalności w akcji.

Debuggowanie programu

Gdy uruchomimy nasz program w trybie debuggowania, w panelu Variables zobaczymy tylko jedną zmienną args, czyli tablicę z argumentami przekazanymi do programu. Gdy będziesz wznawiał wykonanie i zatrzymywał program, przechodząc w międzyczasie przez kolejne linie kodu oraz różne metody i klasy, zawartość tego okno będzie się zmieniać.

Skorzystaj ze skrótu F8, aby nakazać IDEA wykonać „Step Over” – spowoduje to wykonanie aktualnej linii kodu i zatrzymanie programu w następnej. Gdy teraz zajrzysz do panelu Variables, zobaczysz, że została do niego dodana zmienna liczby. Stało się tak, ponieważ w linii, która właśnie została wykonana, zdefiniowaliśmy właśnie tę tablicę. Możesz kliknąć na mały trójkąt przy nazwie zmiennej, aby obejrzeć jej zawartość:

Zawartość okna Variables - zmienne oraz ich wartości
Zawartość okna Variables - zmienne oraz ich wartości

Program ponownie jest zatrzymany – wykonaliśmy tylko jedną linię kodu. Obecnie program zatrzymany jest na pierwszym wywołaniu metody znajdz:

System.out.println(znajdz(10, liczby));

Wejdziemy teraz do źródła metody znajdz – w tym celu skorzystamy ze skrótu F7, by wykonać „Step Into”. Gdy skorzystasz z tego skrótu (bądź naciśniesz przycisk, który mu odpowiada na jednym z paneli, które opisałem w poprzednim rozdziale), IDEA zapyta Cię, do źródła której metody chcesz wejść. W powyższej linii kodu wywoływana jest zarówno metoda println obiektu out z klasy System, jak i nasza metoda znajdz:

Korzystanie z funkcjonalności Step into - wybieramy metodę, którą chcemy debuggować
Korzystanie z funkcjonalności Step into - wybieramy metodę, którą chcemy debuggować

IDEA wyświetla podpowiedź: „Wybierz metodę, do której chcesz przejść, za pomocą klawiszy strzałek. Naciśnij Escape, aby anulować.

Nacisnąłem strzałkę w prawo na klawiaturze, co spowodowało, że zaznaczona została metoda znajdz zamiast println, a następnie zatwierdziłem Enterem. Wykonanie naszego programu przeszło do pierwszej linii metody znajdz:

Wynik użycia funkcjonalności Step Into
Wynik użycia funkcjonalności Step Into

Zauważ, że panele Frames oraz Variables zostały uaktualnione. Do stosu wywołań metod (panel Frames) dodana została kolejna metoda: znajdz. Z panelu Variables zniknęła jej poprzednia zawartość, ale zamiast niej pojawiła się nowa – zmienne wartosc oraz tab, czyli argumenty metody znajdz. IDEA wyświetla tam także tab.length, czyli liczbę elementów w tablicy.

Co więcej, w kodzie metody, w instrukcji warunkowej, po prawej stronie tab[i], IDEA umieściła podpowiedź w nawiasach kwadratowych: [ArrayIndexOutOfBoundsException] – jest to wyjątek, który może zostać rzucony w tym fragmencie kodu.

Naciśnij raz przycisk Step Over lub użyj skrótu F8, aby nakazać IDEI wykonanie pierwszej linii kodu metody znajdz. Zawartość panelu Variables zostanie ponownie zaktualizowana – zostanie do niej dodane kilka informacji:

Lista zmiennych dostępnych w metodzie znajdz, którą debuggujemy
Lista zmiennych dostępnych w metodzie znajdz, którą debuggujemy

W panelu widzimy zmienną pętli o nazwie i, a także jej aktualną wartość. IDEA pokazuje także wartość w tabeli tab, która znajduje się pod indeksem, którego wartością jest właśnie zmienna i: tab[i] = 10.

Jeżeli wykonasz kolejną linię kodu (skrót F8), to zauważysz, że wykonanie kodu „wejdzie” do ciała instrukcji warunkowej. Jest to dopiero pierwszy obieg pętli w metodzie znajdz, a szukana wartość już została znaleziona – powinno się to stać dopiero w drugim obiegu pętli, bo szukana liczba 10 jest na drugim miejscu w tablicy tab. Sygnalizuje nam to, że metoda nie działa prawidłowo – znaleźliśmy błąd w metodzie znajdz. Zmienna i powinna być inicjalizowana wartością 0, a nie 1.

Aktualnie wykonanie programu zatrzymane jest na linii:

return i;

Gdy teraz wykonamy jedną linię kodu (F8), to przejdziemy z powrotem do metody main – metoda znajdz zostanie zakończona, a program będzie kontynuowany od miejsca, w którym wywołana została metoda znajdz.

Będąc z powrotem w metodzie main, dokańczamy wykonanie linii z pierwszym wywołaniem metody znajdz (została tam jeszcze do wykonania metoda println) za pomocą F8. Wykonanie programu przejdzie do następnej linii i zatrzyma się na niej:

System.out.println(znajdz(2, liczby));

Skorzystaj ponownie z przycisku Step Into (lub skrótu F7) i ponownie wybierz za pomocą strzałek metodę znajdz i zatwierdź Enterem. Wykonanie przejdzie do pierwszej linii kodu metody znajdz. W ramach ćwiczenia, skorzystamy teraz z przycisku Step Out (skrót Shift + F8), aby wyjść z metody znajdz – efekt będzie taki, że cała metoda zostania wykonana, a następnie wrócimy do metody main do miejsca, w którym metoda znajdz była wywołana. W tej linii program ponownie się zatrzyma.

Teraz, gdy program jest znowu zatrzymany, możesz kliknąć przycisk Evaluate Expression... (Alt + F8). Jest to bardzo potężne narzędzie, które pozwala na wpisywanie złożonych wyrażeń, których wartość IDEA wyznaczy dla nas na podstawie aktualnego stanu programu. W ramach ćwiczeń możesz wpisać tam args.length, a IDEA wyświetli liczbę elementów w tablicy-argumencie metody main. Po wpisaniu wyrażenia, naciśnij Enter – wynik powinien być jak poniżej:

Przykładowe wyrażenie w oknie Evaluate
Przykładowe wyrażenie w oknie Evaluate

IDEA wyświetliła wynik result = 0, ponieważ naszemu programowi nie przekazaliśmy żadnych argumentów. Możesz także, zgodnie z podpowiedzią, użyć skrótu Ctrl + Shift + Enter, a IDEA zapamięta wpisane przez Ciebie wyrażenie i będzie je od teraz wyświetlać w oknie Variables. Jest to dobry sposób na szybkie sprawdzanie np. wartości w zagnieżdżonych polach pewnych obiektów.

Okno Evaluate pozwala na wpisywanie skomplikowanych wyrażeń. Mógłbyś wpisać np. warunek args.length == 0, a IDEA wypisałaby, w przypadku naszego programu, wartość true. Jeżeli miałbyś zmienną typu String, mógłbyś np. wpisać wyrażenie:

a.toLowerCase().endsWith("txt")

Miej na względzie, że w tym oknie możesz odnosić się do metod i pól obiektów, używać operatorów porównania itd.

Zamknij okno Evaluate klikając przycisk Close. Użyjemy teraz przycisku Resume Program (skrót F9), który znajduje się po lewej stronie dolnych paneli debuggowania (jest reprezentowany przez zieloną ikonę |>). Spowoduje to wznowienie całego programu, który dokończy swoje działanie, ponieważ po drodze nie będzie już żadnych breakpointów.

Oznaczenia stosu wywołań metod

Gdy będziesz debuggować bardziej złożone programy, czasem będziesz potrzebował przejść do metod klas, które znajdują się w bibliotekach, od których Twój projekt jest zależny, chociażby od Biblioteki Standardowej Java lub Spring.

W stosie wywołań metod (panel Frames), metody, które pochodzą z bibliotek, a nie z klas, z których składa się Twój projekt, są zaznaczane w inny sposób – spójrz na poniższy obraz:

IDEA prezentuje metody z bibliotek na żółtym tle na Stosie wywołań metod
IDEA prezentuje metody z bibliotek na żółtym tle na Stosie wywołań metod

Debuggując program, przeszedłem do kilku metod wewnątrz Standardowej Biblioteki Java – zauważ, że na liście stosu wywołań metod mają one żółte tło. Z kolei metoda z klasy z mojego projektu, która jest na samym dole tej listy, ma białe tło. W ten sposób możesz łatwo odróżnić metody z Twojego projektu od metod z bibliotek.

Dodatkowo, możesz wyłączyć pokazywanie metod z bibliotek na tej liście, klikając na zaznaczony powyżej przycisk z ikoną lejka (który ma tytuł Hide Frames from Libraries). Dzięki temu, wszystkie metody z bibliotek będą ukryte na powyższej liście, poza pierwszą (czyli tą, w której aktualnie się znajdujemy debuggując program).

Stosowanie breakpointów i Run to Cursor

W swoich programach możesz ustawiać wiele breakpointów – wykonanie programu (w trybie debuggowania) zatrzyma się na pierwszym, na który natrafi wykonanie programu.

Jeżeli program się zatrzyma, a Tym wznowisz jego działanie, to jeżeli wykonanie programu natrafi na kolejny breakpoint, to program ponownie się zatrzyma. Możesz w ten sposób ustawić breakpointy w kluczowych miejscach w kodzie, które chcesz przeanalizować.

Miej na uwadze, że kolejność breakpointów w kodzie źródłowym zazwyczaj nie będzie odpowiadać kolejności, w jakiej będą one zatrzymywać wykonanie programu – możesz przecież umieścić metodę, od której zaczyna się wykonanie kodu w danej klasie, na jej końcu.

Breakpointy mogą być warunkowe, tzn. możesz przypisać im pewien warunek, który musi zostać spełniony, aby breakpoint spowodował zatrzymanie wykonywania programu. Aby ustawić taki warunek, najpierw ustaw w danej linii breakpoint, klikając na margines po jej lewej stronie lewym przyciskiem myszki, a następnie kliknij czerwoną kropkę, która symbolizuje breakpoint, prawym przyciskiem myszki – pojawi się wtedy poniższe okno:

Ustawianie warunkowego breakpointa
Ustawianie warunkowego breakpointa

Ustawiłem powyższy breakpoint, by zatrzymywał program tylko wtedy, gdy wartość zmiennej i przekroczy liczbę 3.

Przydatną funkcjonalnością jest także Run to Cursor (skrót Alt + F9). Jeżeli program jest zatrzymany, możesz kliknąć linię kodu w edytorze, a następnie skorzystać z Run to Cursor, co spowoduje wznowienie działania programu, który będzie wykonywał się aż wykonanie nie dojdzie do linii, którą wskazałeś w edytorze kodu.

Ustawianie wartości zmiennych w trakcie działania programu

Podczas debuggowania programu, możesz zmienić wartość pewnej zmiennej. Dzięki temu możesz prześledzić, jak zachowa się program, gdy ta zmienna będzie miała konkretną wartość, bez potrzeby zatrzymywania programu i uruchamiania go ponownie.

Aby to zrobić, w panelu Variables znajdź zmienną, której wartość chcesz zmienić, a następnie kliknij na nią prawym przyciskiem myszki:

Funkcjonalność Set Value w menu w oknie z wartościami zmiennych
Funkcjonalność Set Value w menu w oknie z wartościami zmiennych

Z menu, które się pojawi, wybierz Set Value... (lub skorzystaj ze skrótu F2). Gdy to zrobisz, będziesz mógł bezpośrednio w panelu Variables zmienić wartość tej zmiennej:

Ustawianie wartości zmiennej w trakcie działania programu
Ustawianie wartości zmiennej w trakcie działania programu

Podsumowanie

Debuggowanie to taka funkcjonalności, z której trzeba kilka razy skorzystać, aby zrozumieć, co i jak możemy osiągnąć. Trzeba poklikać przyciski, posprawdzać wartości w różnych panelach, podebuggować różnego rodzaju programy, mniej i bardziej złożone. Zachęcam do testowania tej funkcjonalności – jest bardzo przydatna w pracy zawodowej.

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Nie musisz podawać swojego imienia, e-mailu, ani strony www, aby opublikować komentarz. Komentarze muszą zostać zatwierdzone, aby były widoczne na stronie.