Debuger jest jednym z najpotężniejszych narzędzi w arsenale każdego programisty. Daje nam unikalny wgląd w działanie programu i pozwala nam lepiej zrozumieć fragmenty kodu, który debugujemy.
Omówmy nieco bardziej tą tematykę…
Debugowanie to proces wykrywania i korygowania błędów w programie.
Istnieją różne rodzaje błędów, którymi będziesz się zajmować. Niektóre z nich są łatwe do złapania, np. błędy składniowe, ponieważ zajmuje się nimi kompilator. Innym łatwym przypadkiem jest to, że błąd można szybko zidentyfikować, patrząc na ślad stosu, co pomaga ustalić, gdzie on wystąpił.
Istnieją jednak błędy, które mogą być bardzo podchwytliwe, a ich znalezienie i naprawienie zajmuje dużo czasu. Np. subtelny błąd logiczny, który pojawił się na początku programu, może się objawić dopiero bardzo późno, a czasem jest to prawdziwe wyzwanie.
Debuger zapewnia wgląd w wewnętrzne operacje programu. Jest to możliwe poprzez wstrzymanie wykonania i analizę stanu programu poprzez dokładne zbadanie zmiennych i sposobu ich zmiany linia po linii. Podczas debugowania masz pełną kontrolę nad tym.
Przed przystąpieniem do praktyki ostatnią kwestią do zapamiętania jest to, że debugowanie nie zastępuje zrozumienia kodu. W rzeczywistości jedynym sposobem uczenia się z sesji debugowania jest ciągłe porównywanie informacji wyświetlanych nam przez debuger z naszymi oczekiwaniami dotyczącymi kodu i tego, jak naszym zdaniem powinno się ono zachowywać. Przed rozpoczęciem sesji debugowania musimy mieć wiedzę na temat tego, co próbujemy osiągnąć. Jeśli szukamy błędu, musimy z grubsza wiedzieć, co jest nieprawidłowe, tj. Co różni się od oczekiwanego zachowania lub stanu. W większości przypadków przyjmujemy również wstępne założenia, dlaczego coś jest nie tak.
Breakpoint
Punkty przerwania to specjalne znaczniki, które zawieszają wykonywanie programu w określonym punkcie. Umożliwia to sprawdzenie stanu i zachowania programu. Punkty przerwania mogą być proste (na przykład zawieszenie programu po osiągnięciu pewnego wiersza kodu) lub obejmować bardziej złożoną logikę (sprawdzanie dodatkowych warunków, pisanie komunikatów dziennika itp.).
Po ustawieniu punkt przerwania pozostaje w projekcie do momentu jego wyraźnego usunięcia, z wyjątkiem tymczasowych punktów przerwania.
Rodzaje breakpoint’ów
Line breakpoints: zawiesza program po osiągnięciu wiersza kodu, w którym ustawiony został punkt przerwania. Ten typ punktów przerwania można ustawić w dowolnym wierszu kodu wykonywalnego.
Kliknij rynnę w wykonywalnym wierszu kodu, w którym chcesz ustawić punkt przerwania. Alternatywnie umieść kursor na linii i naciśnij Ctrl + F8.
Method breakpoints: zawiesza program po wejściu lub wyjściu z określonej metody lub jednej z jej implementacji, umożliwiając sprawdzenie warunków wejścia / wyjścia metody.
Aby zawiesić program, gdy wywoływany jest domyślny konstruktor jakiejś klasy, kliknij rynnę w linii, w której klasa jest zadeklarowana, lub umieść daszek w linii i naciśnij Ctrl + F8. Kliknij rynnę w linii, w której zadeklarowano metodę. Alternatywnie umieść kursor na linii i naciśnij Ctrl + F8.
Field watchpoints: zawieszenie programu, gdy określone pole zostanie odczytane lub zapisane. Pozwala to reagować na interakcje z określonymi zmiennymi instancji. Na przykład, jeśli na końcu skomplikowanego procesu kończy się oczywista zła wartość jednego z pól, ustawienie punktu obserwacyjnego pola może pomóc ustalić przyczynę błędu.
Kliknij rynnę w linii, w której zadeklarowano pole. Alternatywnie umieść kursor na linii i naciśnij Ctrl + F8.
Exception breakpoints: wstrzymaj program, gdy zostanie rzucony Throwable lub jego podklasy. Stosują się one globalnie do warunku wyjątku i nie wymagają określonego odniesienia do kodu źródłowego.
W zależności od typu i statusu punkty przerwania są oznaczone następującymi ikonami:
Przejdźmy teraz do praktycznego zastosowania tego co omówiliśmy do tej pory. Poniżej mamy program, który ma obliczyć średnią wszystkich wartości przekazanych jako argumenty wiersza poleceń. Kompiluje się i działa bezproblemowo, jednak wynik nie jest tym, czego można by oczekiwać. Np, gdy przekazujemy 1 2 3 jako dane wejściowe, wynik wynosi 6,0. Przede wszystkim musisz pomyśleć o tym, skąd może pochodzić podejrzewany błąd. Możemy założyć, że problem nie występuje w drukowanych instrukcjach. Najprawdopodobniej nieoczekiwane wyniki pochodzą z naszej metody findAverage. Aby znaleźć przyczynę, sprawdźmy jej zachowanie w czasie wykonywania.
public class App { public static void main(String[] args) { System.out.println("Average finder v0.1"); double avg = findAverage(args); System.out.println("The average is " + avg); } private static double findAverage(String[] input) { double result = 0; for (String s : input) { result += Integer.parseInt(s); } return result; } }
1. Ustawiamy breakpoint’a
2. Z menu głównego wybierz Uruchom | Edytuj konfiguracje.
3. Wprowadź argumenty w polu „Program arguments” następnie wybierając „ok„.
4. Teraz uruchommy program w trybie debugowania.
5. Przeanalizujmy stan programu. Po rozpoczęciu sesji debugera program działa normalnie, dopóki nie zostanie osiągnięty punkt przerwania. Kiedy tak się dzieje, wiersz, w którym program został zatrzymany, zostaje podświetlony i pojawia się okno narzędzia debugowania. Podświetlona linia nie została jeszcze wykonana. Program czeka teraz na dalsze instrukcje od ciebie. Stan zawieszenia pozwala badać zmienne, które utrzymują stan programu.
6. Przenalizujemy dostępne możliwości sterowania procesem debugowania.
Przycisk pierwszy od góry pozwala uruchomić na nowo program. Drugi od góry pozwala nam na wznowienie działania programu po jego zatrzymaniu przez breakpoint (przepuszcza przez proces breakpoint wykonując program dalej) lub zakończeniu go całkowicie (przycisk na samym dole). Przycisk pauzy pozwala ‘wstrzymać’ działanie programu ręcznie.
Omówmy kolejne przyciski – są bardziej interesujące, bo pozwalają nam kontrolować działanie programu ‘krok po kroku’. Zaczynając od lewej strony:
Step over (F8) — wykonuje jedną operację (przechodzi linijkę dalej). Jeśli obecna linijka była wywołaniem funkcji, wykonuje ją i wznawia debugowanie po jej wykonaniu
Step into (klawisz skrótu: F7) — wykonuje jedną operację (przechodzi linijkę dalej). Jeśli obecna linijka była wywołaniem funkcji, przechodzi do wnętrza tej funkcji
Force step into (klawisz skrótu: Alt+Shift+F7) – kroki w metodzie, nawet jeśli ta metoda jest pomijana przez Step Into.
Step out (klawisz skrótu: Shift + F8) – Wychodzi z bieżącej metody i prowadzi do metody wywołującej.
7. Ponieważ metoda findAverage nie została jeszcze wywołana, wszystkie jej lokalne zmienne, takie jak wynik, nie są jeszcze objęte zakresem, możemy jednak zbadać zawartość tablicy args (args jest w zakresie, ponieważ wciąż jesteśmy w metodzie głównej). Zawartość argumentów wyświetlana jest w linii, gdzie używana jest argumentacja:
Możesz również uzyskać informacje o wszystkich zmiennych, które są obecnie objęte zakresem w panelu Zmienne:
8. Teraz, gdy czujemy się dobrze z oknem narzędzia debugowania, nadszedł czas, aby przejść do metody findAverage i dowiedzieć się, co się w niej dzieje. Aby przejść do metody, kliknij przycisk „Step into” lub naciśnij klawisz F7
9. Przejdźmy dalej i zobaczmy, jak deklarowany jest wynik zmiennej lokalnej i jak jest zmieniany przy każdej iteracji pętli.
W tej chwili zmienna s zawiera wartość „3”. Zostanie przekonwertowany na liczbę całkowitą i dodany do wyniku, który obecnie ma wartość 3,0. Jak dotąd żadnych błędów. Suma jest obliczana poprawnie.
10. Dwa kolejne kroki prowadzą nas do instrukcji return i widzimy, gdzie było pominięcie. Zapomnieliśmy podzielić sumę przez liczbę wartości. To było przyczyną nieprawidłowego zwrotu metody.
11. Poprawmy błąd z:
return result;
na:
return result/input.length;
11. Zatrzymaj i uruchom ponownie sesję debugowania. Sprawdź, czy program działa teraz poprawnie. Pierwotna wersja zawierająca błąd zwracała nam w konsoli:
Po naszej poprawce powinno to wyglądać tak: