Merhabalar.

Bu yazı içerisinde size Java dilinde Hata Yakalama (Exception Handling) konusundan Best Practice’ler anlatmaya çalışacağım.

Yazıya başlamadan önce belirtmek gerekir ki bu yazı sırasında http://howtodoinjava.com/best-practices/java-exception-handling-best-practices/ adresindeki makaleden faydalanarak bu yazıyı yazıyorum.

Exception Handling konusuna girmeden önce Java’daki Exception mekanizması üzerine biraz konuşmak gerekiyor. Java dilindeki exception hiyerarşisini gösteren bir görüntü şu şekilde:

Görüntüde gördüğümüz gibi en tepede tüm hata mekanizmasının hiyerarşik olarak tepesinde Throwable bulunuyor ve Checked olarak belirtilmiş. Throwable kendi altında ikiye bölünüyor ve bunlar Exception ve Error mekanizmaları. Exception Checked iken Error Unchecked. Yine Exception kendi altında çalışma zamanında meydana gelebilecek hataları kapsayacak olan fakat Unchecked olan RuntimeException ve Checked olan diğer Exception’lar şeklinde ikiye ayrılıyor. Bu noktada Checked&Unchecked Exception ve Error konusundan da bahsetmek gerekiyor.

  • Checked Exceptions: Java dilinde kod yazılırken meydana gelebilecek bir takım exception türlerinin kod içerisinde zorunlu olarak belirtildiği exception türü diyebiliriz bu türe. Exception sınıfını kalıtarak gelirler. Eğer kod yazılırken Java bizi bir Exception’ı metotlarımızın throws özelliği ile ya da try-catch yapısı ile tanıtmaya mecbur bırakıyorsa bu bir Checked Exception örneğidir arkadaşlar. Mesela Java’da dosya işlemleri ile uğraşırken hepimiz FileNotFoundException, IOExcetion gibi exception’ları bir şekilde kod içerisinde tanıtmak zorunda kalmışızdır. IOExcetion da bir Checked Exception’dır arkadaşlar. Java’nın bizi bu Exception’ları kod içerisinde yakalamaya yönelik zorlaması bu hataların harici faktörlerden gelebileceği ve JVM’in bu durumda bir şey yapamıyacağıdır. Örneğin yazmış olduğunuz bir dosya işlemi kodu içerisinde aradığınız dosya yoksa bu durumda JVM’in yapabileceği bir şey yoktur ve bu harici bir faktördür.

  • Unchecked Exception*: Java’nın bizi kod içerisinde tanımlamaya zorlamadığı Exception türüdür. Bu hatalar genel olarak programlama hatalarından dolayı oluşan ve çalışma zamanı ortaya çıkan hatalardır.Unchecked Exception’lar RuntimeException sınıfını kalıtarak oluşurlar. RuntimeException her ne kadar Checked olan Exception sınıfını kalıtsa da kendisi Unchecked Exception klasmanında yer almaktadır. Unchecked Exception’ları en iyi anlamanın ve çözüm üretmenin yolu ise loglama yapılmasıdır.

  • Error: Error’un açıklaması olarak çalışma zamanında meydana gelen ve ciddi sonuçları olan durumlar gösterilir. Örneğin OutOfMemoryError, StackOverFlowError gibi Error’lar programın bütününü ya da bir kısmını çalışamayacak hale getirebilirler. Error’ları yakalamanın ve çözmenin en garanti yolu loglama yapmaktır.

Best practices

Exception’lar hakkında bilgi edindikten sonra şimdi Best Practice’leri konusuna değinebiliriz.

Exception’ı catch bloğu içerisinde yutmayın.: Şu şekilde bir kodunuz olduğunu düşünün:

catch (NoSuchMethodException e) {
   return null;
}

Bu kod bir şekilde Exception yakalansa hiç Exception hakkında bilgi vermeden ve düzeltilebilecek bir şey varsa düzeltmeden null dönüp işi bitiriyor. Bu kodlama açısından yanlış bir yaklaşımdır. Sebebi bilinmeyen bir hatayı çözmek de mümkün olmayacaktır. Burada hata loglanabilir ya da kodun gidişatına göre önlemler alınabilinir.

Geniş yerine dar kapsamlı Exception tanımlaması yapın: Şu şekilde bir kodumuz olsun:

public void doAnything() throws Exception { //Doğru yaklaşım değil!
}  

Yukarıdaki kod bir hata olduğunda en tepedeki Exception sınıfı türünden bir hata fılatacaktır. Kodun yazılması sırasında doğru olan yaklaşım Exception’ların en spesifik olacak şekilde tanımlanması ve hata mesajlarının buna uygun veirlmesidir. Yukarıdaki yaklaşım genellikle bir metot yazımında Java’nın zorladığı Checked Exception sayısı bir kaç tane olduğunda toptan hepsinden kurtulmak için başvurulan bir yoldur ama yanlıştır. Yukarıdaki kodun doğru hali şöyle olmalıdır:

public void doAnything() throws SpecificException1, SpecificException2 { //Doğru Yaklaşım
}

Catch içerisinde genel kapsamlı yakalama yapmayın: İkinci maddedekine benzer sebeplerle try-catch bloğunun catch kısmı içerisinde genel kapsamlı bir Excetion tanımlaması yapılması yanlış bir yaklaşımdır. Şu koddaki catch kısmında Exception e yerine IOExcetion, FileNotFoundException vs. gibi daha spesifik olacak Exception sınıflarını kullanmak daha doğru bir yaklaşımdır.

try {
   someMethod();
} catch (Exception e) {
   LOGGER.error("method has failed", e);
}

Throwable Sınıfı Catch İçerisine Almayın: Yukarıdaki görüntüde fark ettiğimiz gibi Throwable hata mekanizmasının en üstünde yer alan sınıftır. JVM implementasyonlarındaki farklılıklar ve Throwable’ın en üst seviye hata sınıfı olması Throwable catch içerisine alındığında hata tetikleme mekanizmasının çalışamamsına sebep olabilir.

Hata Nesnesinin Kendisini Kullanın: Meydana gelen bir hatayı Catch içerisinde yakaladığınız zaman hatanın mesajını e.getMessage() şeklinde alabiliyoruz fakat bu yaklaşım hatanın Stack Trace’ini yok ediyor. Bu durumda hata üzerinde etki alanımızı daraltıyor. Bunun için hata fırlatılırken hata nesnesinin kendisinin kullanılması doğru bir yaklaşım olacaktır.

catch (NoSuchMethodException e) {
   throw new MyServiceException("Some information: " + e.getMessage());  //Yanlış Yaklaşım!
}
catch (NoSuchMethodException e) {
   throw new MyServiceException("Some information: " , e);  //Doğru Yaklaşım 
}

Hatalarınızı Loglayın: Catch içerisinde hatalarınızı yakaladığınız zaman hatayı anlamak ve çözümünü kolaylaştırmak için hatalarınızı loglayın. Burada aklınıza konsola yazdırsak olmaz mı şeklinde bir soru gelebilir 😊 Bu proje geliştirme sırasında çok yanlış bir yaklaşımdır arkadaşlar. Verilerin, yakalanan hataların konsola yazdırılması yerine loglara yazılması doğru yaklaşımdır çünkü takibi ve okunması hem çok daha kolay hem de daha okunabilirdir.

finally içinde Throw yapmayın: Kullanılan kaynakların kapatılması, temizlenmesi durumları söz konusu olduğunda finally bloğu try-catch bloğunun hemen peşinden kullanılabilir. Bu durumda bizim yapmamamız gereken finally bloğu içerisinde herhangi bir hata fırlatma işlemi yapmamaktır.

Yeniden Throw Edin: Eğer catch içerisinde yakaladığınız bir hata ile catch içerisinde başa çıkamıyorsanız, onu yeniden throw işlemine tabi tutun.

printStackTrace() Kullanmayın metodunu kullanmaktan kaçının. printStackTrace() metodu hatanın context bilgisi hakkında bir bilgi vermediği için sorun çözümüne de yardımcı olmaz.

Hata Yakalamayacaksanız Catch Kullanmayın: Try bloğunun genellikle catch bloğu ile birlikte kullanıldığını görürüz fakat bu bir zorunluluk değildir. Eğer try içerisinde yapılacak işlem neticesinde bir hata olsa da yakalamak, üstesinden gelmek istemiyorsanız, catch kullanmayıp finnaly bloğu ile kaynaklarınızı kapatabilirsiniz.

try {
  someMethod();  
} finally {
  cleanUp();
}

Throw early catch late: Exception Handling konusunda bize tavsiye edilen bir başka nokta da eğer bir hata yakalıyorsak bunu hemen o anda throw ile fırlatmalıyız fakat catch işlemini olabildiğince geciktirmeliyiz. Bunun tavsiye edilme nedeni meydana gelen hata hakkında olabildiğince fazla bilgi edinmektir.

Bağlantıları Kapatın: Hatayı yakalayıp başa çıktıktan sonra mutlaka finally bloğu ile o an kullandığınız veri tabanı bağlantısı, ağ bağlantısı vs. gibi bağlantıları kapatın.

İlgili hatayı throw edin. Bir dosya okuma işlemi sırasında dosyanın bulunamaması sırasında NullPointerException da fırlatılabilir fakat meydana gelen hata için doğru bir hata fırlatma yaklaşımı olmaz. Burada örneğin NoSuchFileFoundException fırlatılması hata ile ilgili daha doğru yaklaşım olacaktır.

Input’ları en erken aşamada kontrolden geçirin. Örneğin sizin bir kullanıcı ve bir de adres tablonuz var. Kullanıcı bilgileri giriyor ve siz önce kullanıcı bilgilerini validasyondan geçirip veritabanına kaydediyorsunuz sonra adresi bilgilerini validasyondan geçirip veritabanına kaydediyorsunuz. Bu yanlış bir yaklaşımdır arkadaşlar. Input’ların hepsini en erken aşamada, daha hiçbir controller’a girmeden validasyon geçirmeli, hata oluşursa geri göndermeliyiz. Bu noktada kullanıcının girdiği veriler validasyondan geçse bile DB’ye kayıt sırasında hata olabilir. Bu noktada da mutlaka Rollback işlemi yapılmalı.

Tekrarlıyan try-catch’leri template metoda alın. Diyelim ki DB bağlantısını kapatan bir try-catch bloğunuz var ve DB üzerinde operasyon yapılan her yerde bu try-catch tekrar ediyor. Bu noktada yapılması gereken bu kapama işlemini yapan bloğun bir Util sınıfı içerisinde metoda alınması ve diğer yerlerde oradan çağırılması olacaktır.

Şu şekilde bir DBUtil sınıfı içerisinde closeConnection metodu yazılabilir.

class DBUtil{
    public static void closeConnection(Connection conn){
        try{
            conn.close();
        } catch(Exception ex){
            //Connection kapatılamadı ise oluşan hatayı loglayın.
        }
    }
}

Aşağıdaki kodda da finally içerisinden DBUtil’deki closeConnection metodu çağıralarak kod tekrarının önüne geçilmiş oluyor.

public void dataAccessCode() {
    Connection conn = null;
    try{
        conn = getConnection();
        ....
    } finally{
        DBUtil.closeConnection(conn);
    }
}

Interrupted Olan Thread’i durdurun. Eğer bir Thread çalışması sırasında InterruptedException ile Thread kesintiye uğrarsa bu Thread’i durdurmamız gerekir. Bu hatanın alınması bize o an yapılan işin durdurulması gerektiğine bir işarettir. Örneğin Thread Pool kapandığında bu hata verilebilir.

Şu kodda InterruptedException oluştuğunda Thread üzerinde hiçbir şey yapılmıyor. Bu yanlış yaklaşım olur.

while (true) {
  try {
    Thread.sleep(100000);
  } catch (InterruptedException e) {} //Don't do this
  doSomething();
}

Doğru olan yaklaşım şu şekilde o an yaptığı işi ve Thread’i sonlandırması olacaktır:

while (true) {
  try {
    Thread.sleep(100000);
  } catch (InterruptedException e) {
    break;
  }
}
doSomething();

Bu yazıda anlatacaklarım bu kadar arkadaşlar. 16 madde ile Exception Handling konusunu nasıl daha iyi hale getirebileceğimizi görmüş olduk. Başka bir yazıda görüşene kadar sağlıcakla kalın.

Selam ve Sevgilerimle