1. Giriş
Bu mühazirədə istisnalarla işləməyin vacib bir texnikasını — istisnaların zəncirlənməsini (exception chaining) nəzərdən keçirəcəyik. Bu texnika, bir istisnanı digərinə “bürüdüyünüz” halda belə, xətanın ilkin səbəbi haqqında məlumatı itirməməyə imkan verir.
Real tətbiqlərdə çox vaxt belə olur ki, xəta çağırış stekinin dərinliyində yaranır — məsələn, verilənlər bazası, fayl sistemi və ya şəbəkə ilə işləyərkən. Tutaq ki, sizdə verilənlər bazasına müraciət edən və SQLException ata bilən metod var. Amma biznes məntiqi səviyyəsində kodu texniki detallarla “çirkləndirmək” istəmirsiniz və məsələn, UserManagementException kimi öz istisnanızı atmağa üstünlük verirsiniz.
Yeni istisnanı sadəcə atsaq nə baş verər?
try {
// verilənlər bazası ilə bağlı nəsə
} catch (SQLException e) {
throw new UserManagementException("İstifadəçilərlə işləyərkən xəta");
}
Problem:
Bu halda verilənlər bazasında nə baş verdiyi (və çağırış steki!) barədə məlumat itir. Logda yalnız UserManagementException görəcəksiniz, səbəbin nə olduğu isə məlum olmayacaq.
2. Həll: ilkin istisnanın bürünməsi (chaining)
Java ilkin istisnanı yeni istisnaya səbəb (cause) kimi konstruktor vasitəsilə ötürərək “bürüməyə” imkan verir. Bu, istisnaların zəncirlənməsi adlanır.
Necə etmək olar?
Əksər standart və istifadəçi istisnalarında ikinci parametr kimi Throwable cause qəbul edən konstruktor var:
public UserManagementException(String message, Throwable cause) {
super(message, cause);
}
İstifadə:
try {
// verilənlər bazası ilə bağlı nəsə
} catch (SQLException e) {
throw new UserManagementException("İstifadəçilərlə işləyərkən xəta", e);
}
İndi printStackTrace()-ə baxsanız, həm sizin istisnanı, həm də ən ilkin səbəbədək bütün zənciri görəcəksiniz!
3. İstisnanın səbəbini necə əldə etmək olar
İstənilən Throwable obyektinin getCause() metodu var, o, ilkin istisnanı qaytarır (əgər yoxdursa, null qaytarır).
Nümunə:
try {
// ...
} catch (UserManagementException e) {
Throwable cause = e.getCause();
if (cause != null) {
System.out.println("İlkin səbəb: " + cause);
}
e.printStackTrace();
}
Bu nə üçündür?
- Sazlama üçün: yalnız yuxarı səviyyədə “nə səhv getdi” deyil, həm də xətanın stekin dərinliyində məhz harada baş verdiyini görürsünüz.
- Jurnallaşdırma üçün: xətaların bütün zəncirini loga yaza bilərsiniz.
- Tətbiqin qatları arasında məlumat ötürmək üçün: biznes qatı texniki istisnanı öz istisnasına “bürüyə” bilər, detalları itirmədən.
4. Nümunə: real tətbiqdə istisnaların zənciri
Tutaq ki, sizdə verilənlər bazasından istifadəçini yükləyən metod var:
public User loadUser(String username) throws UserManagementException {
try {
// SQLException ata bilən kod
// ...
} catch (SQLException e) {
throw new UserManagementException("İstifadəçini yükləmək mümkün olmadı: " + username, e);
}
}
Burada UserManagementException — sizin öz istisnanızdır:
public class UserManagementException extends Exception {
public UserManagementException(String message, Throwable cause) {
super(message, cause);
}
}
Xəta baş verəndə nə olacaq?
- Loglarda həm sizin istisna, həm də bütün detalları ilə orijinal SQLException görünəcək.
- Lazım olduqda ilkin səbəbi getCause() vasitəsilə əldə etmək olar.
5. İstisnalar zənciri zamanı çağırış steki necə görünür
Çıxış nümunəsi:
UserManagementException: İstifadəçini yükləmək mümkün olmadı: vasya
at UserService.loadUser(UserService.java:15)
...
Caused by: java.sql.SQLException: Connection refused
at ...
Burada hər şey görünür: çağırışların tam zənciri, biznes xətasının harada yarandığı və hansı texniki istisnanın səbəb olduğu.
6. Təcrübə: istisna zəncirini həyata keçirək
Addım 1. Öz istisnanızı yaradırsınız:
public class UserManagementException extends Exception {
public UserManagementException(String message) {
super(message);
}
public UserManagementException(String message, Throwable cause) {
super(message, cause);
}
}
Addım 2. Zəncirdən istifadə edirik:
try {
// təhlükəli nəsə
} catch (SQLException e) {
throw new UserManagementException("Verilənlər bazası ilə işləyərkən xəta", e);
}
Addım 3. Yuxarı səviyyədə emal:
Proqramın ən yuxarı səviyyəsində öz istisnamızı tutur və xətanı bütün səbəb zənciri ilə birlikdə çıxarırıq.
public class Main {
public static void main(String[] args) {
try {
runUserManagement();
} catch (UserManagementException e) {
System.err.println("Xəta baş verdi: " + e.getMessage());
// səbəb zəncirini göstəririk
Throwable cause = e.getCause();
while (cause != null) {
System.err.println("Səbəb: " + cause.getMessage());
cause = cause.getCause();
}
}
}
private static void runUserManagement() throws UserManagementException {
try {
// Verilənlər bazası xətasının imitasiya edilməsi
throw new SQLException("Verilənlər bazası ilə bağlantı yoxdur");
} catch (SQLException e) {
throw new UserManagementException("Verilənlər bazası ilə işləyərkən xəta", e);
}
}
}
7. İstisna zəncirləri ilə işləyərkən tipik səhvlər
Səhv №1: cause olmadan yeni istisna atırsınız.
catch (SQLException e) {
throw new UserManagementException("Xəta", /* cause yoxdur! */);
}
Pisdir: ilkin səbəb haqqında məlumat itirilir.
Səhv №2: öz istisnanızda cause qəbul edən konstruktoru reallaşdırmamısınız.
Əgər istisna sinifinizdə Throwable cause qəbul edən konstruktor yoxdursa, səbəbi ötürə bilməyəcəksiniz — onu əl ilə əlavə etməlisiniz.
Səhv №3: istisnanı tutursunuz və onu “susdurursunuz”, yuxarıya ötürmürsünüz.
catch (SQLException e) {
// Sadəcə loglayırıq və susuruq
}
Pisdir: xəta “itir”, proqram səhv işləməyə davam edir.
try {
userService.loadUser("vasya");
} catch (UserManagementException e) {
System.err.println("Xəta: " + e.getMessage());
if (e.getCause() != null) {
System.err.println("İlkin səbəb: " + e.getCause());
}
e.printStackTrace();
}
GO TO FULL VERSION