sobota, marca 01, 2008

Sztuczki tropiciela błędów ;-)

Zwykle tropiąc błąd w kodzie najpierw wymyślamy gdzie problem może być, później zaczynamy go śledzić. Jest to rozwiązanie skuteczne i prawie zawsze działa.
Jednak gdy mowa o aplikacji która pracuje wielowątkowo [a takich jest coraz więcej] nie zawsze jest to podejście najlepsze.
Wtedy przydaje się logowanie.

A w logowaniu, szczególnie gdy tropimy błąd przydaje się taki oto banalny kawałek kodu:

Throwable tr = new Throwable();
tr.printStackTrace();


Niby nic, a cieszy ;-) Wiemy już kto wywołał nasz kawałek kodu.
Czasem przydaje się też wędrówka po stack trace'ie:

public static void logStackTrace() {
Throwable tr = new Throwable();
StackTraceElement[] elements = tr.getStackTrace();
String str = "";
// zaczynamy od 1 żeby nie było widać wołania tej metody ;-)
for (int idx=1; idx<elements.length; idx++) {
str+=elements[idx]+" ";
}
System.out.println(str);
}


Czasem potrzebujemy też wiedzieć jakie wątki odwołują się do naszego kawałka kodu, wtedy używamy równie znanego:

Thread.currentThread();


Możemy go wypisać:

System.out.println(Thread.currentThread();


Albo użyć jako klucza w mapie:

// Używamy Hashtable'a bo jest bezpieczniejsza w przypadku wielu wątków
Map<Thread,Throwable> map = new Hashtable<Thread,Throwable>();
[....]
map.put(Thread.currentThread(), new Throwable());


Wyobraźmy sobie sytuację, że otwieramy połączenie i zamykamy je, ale nie chcemy mając jedno otwarte połączenie z danego wątku otwierać drugiego i kolejnych. W wytropieniu takiego czegoś pomóc nam może:

// Używamy Hashtable'a bo jest bezpieczniejsza w przypadku wielu wątków
public static Map<Thread,Throwable> map = new Hashtable<Thread,Throwable>();

public void openSomething() {
Throwable tr = new Throwable();
// używamy get, bo nie ufam contains, choć tutaj by świetnie zadziałało ;-)
if (map.get(Thread.currentThread())!=null) {
System.out.println("PROBLEM!!!!!");
tr.printStackTrace();
}
map.put(Thread.currentThread(), new Throwable());
[...]
}

public void closeSomething() {
map.remove(Thread.currentThread());
[....]
}


Jak widać jest to wszystko proste, a na dodatek skuteczne.
Zwykle do logowania nie używamy też System.out a czegoś bardziej sexy ;-) ale to już inna bajka.

Na koniec jeszcze przykład jak można logować w trakcie tropienia błędu nieobsłużone wyjątki:

Runnable r = new Runnable() {
@Override
public void run() {
while (true) {
Object o = null;
try {
o.wait();
} catch (InterruptedException ie) {
ie.printStackTrace();
}
}
}
};
Thread t = new Thread(r);
t.setUncaughtExceptionHandler(new UncaughtExceptionHandler() {
@Override
public void uncaughtException(Thread t, Throwable e) {
System.out.println(t);
e.printStackTrace();
}
});
t.start();


Oczywiście takie ustrojstwa mają sens w kodzie w trakcie developmentu, a nie na produkcji, choć to zależy chyba tylko od naszego wyboru :-)

[edit 01/03/2008 11:34: zmieniłem HashMap'e w Hashtable'a, bo walnąłem babola ;-) To Hashtable jest bezpieczne wątkowo, nie HashMap'a]


Podobne postybeta
Sztuczki tropiciela błędów - breakpoint na sterydach ;-)
Potfór ;-) czyli generator z yield w Java'ie
Przepływ sterowany danymi - A takie Java'owe coś ;-)
wait() i notify()/notifyAll() - najbardziej nierozumiane metody klasy Object ;-)
Sztuczki tropiciela błędów, part 4