Praca z debuggerem w Eclipse

 

Do czego służy debugger? 

W trakcie tworzenia kodu programistycznego zdarza się, że popełnimy błąd w kodzie, który potem skutkuje problemem z kompilacją lub nieprawidłowym działaniem programu. 

Zazwyczaj środowisko programistyczne na bieżąco monitoruje składnię naszego programu i zaznacza nam linijki z błędem lub ostrzeżeniem.

Jednakże, częstymi błędami jakie przytrafiają się w trakcie programowania obiektowego jest na przykład odwoływanie się do nieistniejącego obiektu, lub indeksu w tablicy.

Tego typu problemy możemy w wygodny sposób odszukać i poprawić stosując debugger. 

Debugger jest opcją uruchomienia naszego kodu w trybie śledzenia jego działania krok po kroku.  Dzięki niemu możemy prześledzić dokładnie które linijki kodu są wykonywane i w jakiej kolejności.

 

Aby to zademonstrować stwórzmy dwie klasy: Punkt.java i Main.java.

Punkt.java

public class Punkt 
{
	double x;
	double y;
	double z;
	
	public Punkt()	// konstruktor
	{
		x = 0;
		y = 0;
		z = 0;
	}
	
	public double getX()
	{
		return x;
	}
	public void setX(double x)
	{
		this.x = x;
	}
	public double getY()
	{
		return y;
	}
	public void setY(double y)
	{
		this.y = y;
	}
	public double getZ()
	{
		return z;
	}
	public void setZ(double z)
	{
		this.z  = z;
	}
	public double getR()
	{
		return Math.sqrt(x * x + y * y + z * z );
	}
}

 

Main.java

import java.util.Random;

public class Main 
{

	static void fillPoint(Punkt p)		// metoda na obiekcie klasy Punkt
	{
		Random rand = new Random();
		p.setX(rand.nextDouble());
		p.setY(rand.nextDouble());
	}
	
	public static void main(String[] args) 
	{
		System.out.println("Program obiczajacy promienie");
		Punkt [] tablica = new Punkt[4];
		for (int i = 0; i < 4; i++)
		{
			tablica[i] = new Punkt();
		}
		for(int i = 0; i < 5; i++)
		{
			fillPoint(tablica[i]);
			System.out.println("Promien nr " + i + " = " + tablica[i].getR());
		}
	}
}

 

Przedstawiony kod nie zawiera w sobie błędów składni, zatem Eclipse nie będzie nam zgłaszał żadnych problemów.  Jak widać program tworzy w pętli tablicę 4-elementową, którą wypełnia wartościami losowymi.

Następnie program zwraca do konsoli wartość promienia r wyliczoną na podstawie wartości współrzędnych punktów.

Jednakże, po skompilowaniu i uruchomieniu programu wzracane jest kilka wyników i następujący błąd: 

Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 4
    at debug1.Main.main(Main.java:25)

Otrzymujemy wyjątek wraz z numerem linii  wskazujący że odwołujemy się w pewnej iteracji do lementu tablica[4] czego oczywiście zrobić nie może - bo taki element nie istnieje.

Należy zatem przeprowadzić procedurę debugowania naszego kodu aby znaleźć błędną instrukcję.

 

 

Procedura debuggowania kodu programu

 Nasz program uruchamiamy w menu Run\Debug lub skrótem klawiszowym F11. Można również uruchomić debugger za pomoca ikony robaka w pasku narzędziowym.

Eclipse poprosi nas o potwierdzenie zmiany perspektywy w oknie. Potwierdzamy nasz wybór i zauważamy zmianę układu paneli w oknie Eclipse'a.

 

Mamy teraz możliwość dodania punktów nawigacyjnych (breakpoints) oraz monitorujących jakąś zmienną (watchpoints).

Breakpointy możemy ustawić klikając po lewej stronie okna z kodem i wybierając Toggle Breakpoint. 

Przegląd naszego kodu mozemy dokonać za pomocą następujących klawiszy:  lub też za pomocą klawiszy funkcyjnych na klawiaturze.

  • Step Into (F5) -   wykonuje aktualną linię kodu i przechodzi do następnej. Jeśli wyróżniona linia jest metodą, to debugger przechodzi do ciała metody. Jest to najdokładniejsza metoda przeglądania programu.
  • Step Over (F6) -  wykonuje kolejną instrukcję ale bez wchodzenia debuggerem do metody. Używane jest kiedy mamy pewność że dana metoda jest poprawnie napisana i nie ma potrzeby analizowania jej kodu.
  • Step Return (F7) - kończy wykonanie metody w jednym kroku i wraca do metody wywołującej. Używamy kiedy chcemy pominąć dalsze sprawdzanie instrukcji danej metody i przejść do jej końca.
  • Resume (F8) -  skok do następnego punktu nawigacyjnego.

 

Celem zademonstrowania działania debuggera na naszym przykładzie wykonajmy następujące czynności:

  1. postawmy pierwszy breakpoint w linii 3 klasy Punkt. (linijka kodu: double x)
  2. Uruchamiamy debuggowanie (ikona robaka, F11, lub Run\Debug) i program zatrzymuje się w konstruktorze klasy Punkt.
    Program wykonał się do pierwszego breakpointa / watchpointa. W naszym przypadku program będzie się zatrzymywał zawsze gdy użyta zostanie zmienna x.
    Pierwszy raz odwołanie do zmiennej x następuje w konstruktorze klasy Punkt, zatem tam jest pierwsze zatrzymanie programu.
  3. Wciśnięcie w tym etapie klawisza F5 (Step Into) wykona kolejną linię kodu ( double y). Wciśnięcie F7 (Step Return) spowoduje wykonanie dalszych instrukcji automatycznie i wyjście z metody (w tym przypadku z konstruktora dla obiektu tablica[0]).
  4.  Wciskamy ponownie F5 (Step Into) i ponownie wchodzimy do konstruktora, ale tym razem dla obiektu tablica[1]. 
    Kiedy znajdziemy się w konstruktorze klasy Punkt dla obiektu tablica[1] wciśnięcie F5 (Step Into) spowoduje przejście do kodu klasy Object.class. Wynika to z faktu, że klasa Punkt dziedziczy po klasie Object, zatem konstruktor klasy nadrzędnej jest wywoływany na początku klasy potomnej. 
  5. Odznaczmy teraz nasz breakpoint z linii 3 w prawym górnym oknie w zakładce Breakpoints i wstawmy nowy breakpoint w linii 38 (this.z = z
  6. Obserwujemy zmienną i w oknie Variables i wciskamy F8 (Resume) aby przejść do dalszych instrukcji naszego kodu. W pewnym momencie zmienna i osiąga wartość 4 i następny krok powoduje błąd.
    Widzimy teraz że błąd w naszym kodzie wiąże się ze źle zdefiniowaną drugą pętla for. Odwołuje się ona do obiektu o indeksie 4, który nie został stworzony. 
  7. Wychodzimy z trybu debuggowania wciskając ikonę z literką J (druga od prawej w pasku narzędziowym).