Java Hata Ayıklama


Java Dili, JVM, ve The JRE    Java Dili, JVM, ve The JRE İle İlgili Paylaşım Kategorisi

Yazar: Game Master    0 Yorum    278 Görüntüleme
  • Derecelendirme: 0/5 - 0 oy

Paylaşım Tarihi: 28.04.2016, 23:06:33 #1
Game Master Cezalı Üye
Cezalı Üye
Status: Çevrimdışı Yorumları:191 Konuları:81 Kayıt Tarihi:24.04.2016

Programlamanın altın bir kuralı vardır. Program, ya doğru sonuçlar vermeli ya da hiçbir sonuç vermemeli. Çünkü yanlış sonuçlar, ciddi sorunlar yaratabilir. Program hatalarını üç gruba ayırabiliriz.
Sözdizimi yanlışları (syntax errors)                     
Bunlar programcının canını sıkabilirler, ama başkalarına zarar veremezler, çünkü derlenemezler, koşturulamazlar. Dolayısıyla yanlış sonuç vermeleri olanaksızdır. Bu gruptakiler tehlikesiz yanlışlardır.
Mantıksal yanlışlar                (logical errors)      
Programın sözdizimi doğrudur,derlenebilir, koşturulabilir. Ama yapılmak istenen işler için kullanılan deyimler yanlıştır. Yanlış işlemler, yanlış hesaplar yapar. Programın denenme aşamasında bu tür yanlışlar ortaya çıkmazsa, programın kullanılması ciddi sorunlar yaratabilir. Örneğin, bir bankada hesaplar arasında para akışını kaydeden bir program modülünün eksik veya fazla toplama yaptığını düşününüz. Kısa zamanda, üstesinden gelinmesi zor sorunlar yaratır. Bu tür yanlışlar, programcılıkta en tehlikeli sayılan yanlışlardır.
Koşma-zamanı yanlışları (run-time errors)        
Program sözdizimi ya da mantıksal yanlış içermiyor, ama bazı nedenlerle koşamıyor olabilir. Örneğin, gerekli verileri bir giriş biriminden okuyamaması, yada verileri bir çıkış birimine gönderememesi, işlemlerde sıfıra bölme gibi olanaksız bir durumla karşılaşması, vb. 
Biz bu bölümde, üçüncü tür yanlışlarla, yani koşma sırasında oluşan yanlışlarla ilgileneceğiz. Teknik ifadesiyle söylersek, program yazılırken bilinemeyecek, ama program koşarken oluşabilecek istisnai (exceptions) halleri konu edineceğiz. Koşma sırasında beklenmedik bir hata ile karşılaştığında, kaynak program başka bir önlem koymamışsa, sistem program akışını durdurur. Bu durum bazen zararlı sonuçlar da yaratabilir. Örneğin, bir I/O işleminde, gerekli veri dönüşümünün bir nedenle yapılamaması istisnai bir haldir ve bu noktadan sonra programın koşması aniden durmak zorundadır. Ama, programın koşması durunca, veri alış-verişi yaptığı dosyaları kapatamaz. Böylece o dosyalarda veri kaybına yol açabilir.
Programlamanın ilk yıllarında, bu tür yanlışların üstesinden gelebilmek için hayli zorluk çekilirdi. Java, koşma sırasında oluşan bir çok yanlışı kendiliğinden belirler, durumu kullanıcıya bildirir ve programı kapatıp işletim sistemine döner. Buna ek olarak, bu tür yanlışlar oluştuğunda, programcı ne yapmak istiyorsa, onu yapmasını sağlayan araçlara da sahiptir. Bu bölümde, bu araçların nasıl kullanıldığını göreceğiz.
Java nesne yönelimli bir programlama dili olduğuna göre, istisnai halleri bir sınıf olarak düşünmüş olması doğaldır. Java.lang paketi içinde Throwable sınıfı bu iş için yaratılmıştır. Throwable sınıfı, karşılaşılabilecek bütün istisnai halleri ortaya koyabilecek zenginliktedir. Çok sayıda alt-sınıflara ve metotlara sahiptir. Throwable sınıfı, önce iki büyük alt-sınıfa ayrılır: Exceptions ve Error. Programın yakalamasını istediğimiz istisnai halleri, Exceptions sınıfının alt sınıfları ve onlar içindeki metotlarla hallederiz. Ayrıca, programcı, kendisine gerekli olan sınıfları da buradan türetebilir. Throwable sınıfının 60 dan çok direk alt-sınıfı vardır ve her bir alt-sınıfın da başka alt-sınıfları, constructor’ları ve metotları vardır. Örneğin, çok kullanacağımız alt sınıflardan birisi RunTimeException sınıfı ile onun alt-sınıfıArithmeticException olan sınıfıdır.
Error sınıfı ise, normal koşullarda programın yakalayamayacağı hataları sistemin yakalaması içindir. Örneğin stack-overflow gibi istisnalardır. Bu gruba giren hataları, normal koşullarda, program içinde çözümleme olanağı yoktur. Bunlar, programdan çok, sistemle ilgili istisnalardır. Çoğunlukla, programın koşmasının durmasıyla sonuçlanır. Dolayısıyla, bu bölümde o tür istisnaları gidermeye uğraşmayacağız.
Yukarıda sözü geçen sınıfların hiyerarşik yapıları şöyledir:
java.lang.Object
  java.lang.Throwable
 
 
 java.lang.Object
  java.lang.Throwable
      java.lang.Error
 
 
java.lang.Object
  java.lang.Throwable
      java.lang.Exception
          java.lang.RuntimeException
                         java.lang.ArithmeticException
 
Hata yakalanmazsa
Program koşarken oluşabilecek hataları yakalamanın ne kadar önemli olduğunu anlamak için aşağıdaki basit örneği verelim.
 
class Hata01 {
  public static void main(String args[]) {
    int payda = 0;
    int pay   = 38/payda ;
  }
}
 
Bu programı koşturduğumuzda, “sıfıra bölme” (divide-by-zero) işlemiyle karşılaşır. Matematiksel olarak bu işlem olanaksızdır. Bu durumda, java run-time sistemi, programın koşmasını keser ve kullanıcıya aşağıdaki mesajı verir. Bu mesaj şunları söylüyor. Yakalanan hata, Exceptions sınıfının ArithmeticException adlı alt-sınıfı ile belirlenen hatadır. Bu hatanın açıklaması olarak yazılan ( / by zero) kısa mesajı,  divide-by-zero (sıfıra bölme) hatası yakalandı anlamındadır. Hata01.main(Hata01.java:4 dizisi ise, Hata01, main, Hata01.java, 4 verilerinin programı yöneten stack’tan, yazıldığı sıra ile alındığını gösterir.     
 
Exception in thread "main" java.lang.ArithmeticException: / by zero
        at Hata01.main(Hata01.java:4)
 
Şimdi, aynı programı, bir metot kullanarak yeniden kodlayalım.
 
class Hata02 {
  static void kesir() {
    int d = 0;
    int a = 10 / d;
  }
  public static void main(String args[]) {
    Hata02.kesir();
  }
}
 
Bu kez de aynı hata oluşacak, programın koşması kesilecek ve aşağıdaki mesaj gelecektir. Bu mesaj, yukarıdaki mesajın anlamını taşır. Ancak, programın yapısı farklı olduğundan, yönetici stack’tan çıkanlar farklıdır:
 
Exception in thread "main" java.lang.ArithmeticException: / by zero
        at Hata02.subroutine(Hata02.java:4)
        at Exc1.main(Exc1.java:7)
 
Bu mesajı şöyle yorumlayacağız. 7-inci satırda main’in çağırdığı kesir() metodunun 4-üncü satırında ArithmeticException sınıfının belirlediği divide-by-zero hatası yakalanmıştır.
Yönetici stack’ın bu biçimde hataları listelemesi, programın düzeltilebilmesi (debug) için çok yararlıdır. Çünkü hatanın nerede oluştuğunu görmemizi sağlar.
 
Hata yakalama
Java’da koşma-zamanı oluşan hatalara exceptions (istisnai haller) denir. İstisnai haller beş anahtar sözcükle işlenir:
try
catch
throw
throws
finally
Şimdi bunların nasıl kullanıldığını açıklayacağız.
try/catch
 
Önceki iki programda gördüğümüz gibi, java run-time sisteminin, koşturma anında oluşan hatayı yakalayıp, koşmayı kesmesi ve hatayı belirten bir mesaj vermesi, kuşkusuz çok yararlıdır. Bunu görünce, kaynak programımıza dönüp, oluşan hataları giderecek düzeltmeleri (debug) programda yapmamız kolaylaşır.
Ama, çoğunlıkla, program koşarken oluşabilecek hataları, programın koşmasını kesmeden giderme olanağı olabilir. Java, Throwable sınıfını bunun için yaratmıştır. İstisnai halleri çözümlemek için, java try/catch  bloklarını kullanır. Şimdi, örneklerle bu işin nasıl yapıldığını açıklayacağız.
Programda, istisnai hallerin (exceptions) oluşabileceği yerler try{} bloku içine alınır. Bu blokta bir hata oluşursa, sistem o hatayı ortaya atar (throw). Hatanın ortaya atılması demek, hatayı temsil eden bir nesnenin yaratılması demektir. Bu nesne, Throwable sınıfının alt-sınıflarından birisine aittir. Hangi alt-sınıfa ait olacağı konusu, oluşan hataya bağlıdır. Nesne, oluşan hata ile ilgili bütün ayrıntılara sahiptir. catch{} bloku atılan hatayı (yaratılan nesneyi) yakalar, hatanın ne olduğunu belirlemeye çalışır ve gerekli önlemleri alır. Hata giderilemeyecek türden ise, program kapatılır ve kullanıcıya açıklama gönderilir. Her durumda, program akışının durmasıyla sistemin kitlenmesi ve diğer kaynakların zarar görmesi kesinlikle önlenebilir. Eğer, programcı, yakalanan hata için bir çözüm öngörmüyorsa, java run-time sisteminin öngördüğü işlemler default olarak etkin olacaktır. Java run-time sistemi, istisnai hal oluştuğunda, sistem kaynaklarına zarar vermeden program akışını durdurur ve oluşan istisnai durum hakkında kullanıcıya bir açıklama gönderir.
Try/catch bloklarının sözdizimi şöyledir:
 
try {
          // istisna yaratıp yaratmadığı denenecek kodlar
    }
 catch(ExceptionType1 e1){
          // ExceptionType1 için yapılacak işler
        }
  catch(ExceptionType2 e2){
          // ExceptionType2 için yapılacak işler
        }
  …       
 
  catch(ExceptionTypeN eN){
          // ExceptionTypeN için yapılacak işler
        }
 
finally{
          // blok bitmeden yapılması istenen işler
        }
 
try blokunda atılan hatayı catch bloku yakalar ve onun hangi tip olduğunu belirlemeye çalışır. Önce, hatanın  ExceptionType1 tipinden olup olmadığına bakar. O tipten ise, istenen önlemleri alacak kodları çalıştırır. Değilse, hatayı kendisinden sonra gelen catch blokuna yollar. Bu süreç hata tipi belirlenene kadar arka arkaya devam eder. Eğer, hiçbir catch bloku hata ile uyuşmazsa, finally bloku gerekeni yapar. catch{} bloku bir tek olabileceği gibi, programcının istediği sayıda olabilir. Bu blok içinde uygun java deyimleri yer alır. ExceptinType javada Throwable sınıfının herhangi bir alt-sınıfı olabileceği gibi, programcının türettiği bir sınıf da olabilir.
Aşağıdaki program, önceki iki programın yaptığı işi yapar. Hata oluşabilecek kodlar try{} bloku içine alınmıştır. Bu blokta bir aritmetik işlem yapılmaktadır. Oluşan istisna “sıfıra bölme” hatasıdır. Bu tür hatalar ArithmeticExceptionsınıfına aittir. O nedenle, oluşabilecek hatanın ArithmeticException tipinden olacağını öngörüp, o tipten bir nesneyi işaret etmek üzere e referans (pointer) değişkenini tanımlıyoruz. Böylece
                catch(ArithmeticException e){}
blokunu oluşturuyoruz. Bu blok hatayı yakalayacaktır. E referans değişkeni, yakalanan hatayı (nesne) işaret ediyor olacaktır. Aşağıdaki program, hatayı yakaladıktan sonra kullanıcıya “Sıfıra bölme hatası.” mesajını gönderecek, ama program akışını kesmeden geride kalan kodları çalıştırmayı sürdürecektir.
class Hata03 {
  public static void main(String args[]) {
    int d, a;
 
    try {      // denenecek blok.
      d = 0;
      a = 38 / d;
      System.out.println("Bu satır yazılmaz.");
    } catch (ArithmeticException e) { // catch divide-by-zero error
      System.out.println("Sıfıra bölme hatası.");
    }
    System.out.println("catch blokundan sonraki kodlar.");
  }
}
 
Programın çıktısı aşağıdadır. Dikkat ederseniz, 7-inci satır işlevini görmemiştir. Çünkü, 6-ıncı satırda hata oluşmuş ve programın koşması kesilmiştir.
 
Sıfıra bölme hatası.
After catch statement.
 
Programda hatayı yakalayınca, mümkünse onu giderip, programın, sonuna kadar koşmayı sürdürmesini isteriz. Aşağıdaki program bunun nasıl yapılacağını gösteren basit bir örnektir. Program iki tane rasgele (random) sayı alıyor, onun birisini ötekine bölüyor. 56789 sayını, çıkan sayıya bölüyor. Bu işlemi bir for döngüsü altında 12000 kez tekrarlıyor; yani 12000 tane random sayı yaratıyor. catch blokuna önlem alıcı kodlar yazılmazsa, bölenlerden birisi sıfır olduğunda run-time sistemi program akışını kesecektir. Ama, aşağıdaki catch blokunda, sıfıra bölme hatası oluştuğunda, a=0 ataması yapılarak, programın koşmaya devam etmesi sağlanmıştır.
 
import java.util.Random;
 
class Hata04 {
  public static void main(String args[]) {
    int a=0, b=0, c=0;
    Random r = new Random();
 
    for(int i=0; i<22000; i++) {
      try {
        b = r.nextInt();
        c = r.nextInt();
        a = 56789 / (b/c);
      } catch (ArithmeticException e) {
        System.out.println("Sıfıra bölme hatası.");
        a = 0; // devam etmek için 0 atandı
      }
      System.out.println("a: " + a);
    }
  }
}
 
Throwable sınıfı toString() metodunu override ederek, hatanın açıklamasını yapacak hale getirmiştir. Bu özelliği kullanarak, yukarıdaki programı şu şekle getirebiliriz.
 
import java.util.Random;
 
class Hata05 {
  public static void main(String args[]) {
    int a=0, b=0, c=0;
    Random r = new Random();
 
    for(int i=0; i<12000; i++) {
      try {
        b = r.nextInt();
        c = r.nextInt();
        a = 56789 / (b/c);
  }catch (ArithmeticException e) {
               System.out.println("Exception: " + e);
               a = 0; // devam etmek için 0 atandı
        }
 
      System.out.println("a: " + a);
    }
  }
}
 
Ayrıca, Throwable sınıfının ve alt sınıflarının çok sayıda metotları vardır. Örneğin  printStackTrace() metodu, ilk iki programdakine benzer açıklamaları yazar.
 
 
Çoklu Hata Yakalama (Multiple Catch)
Bazen bir try bloku içinde birden çok hata oluşması olasılığı doğabilir. Bu durumlarda, catch bloku içine birden çok hata atıcı deyim konabilir. Program koşarken, hangi hata oluşmuş ise, ona ait deyim atılır. Birden fazlası atılamaz. İlk hataya karşılık gelen deyim işlerlik kazanır. Aşağıdaki örnek bunu göstermektedir.
 
        // Çoklu catch deyimleri.
class Hata06 {
  public static void main(String args[]) {
    try {
      int a = args.length;
      System.out.println("a = " + a);
      int b = 38 / a;
      int c[] = { 1 };
      c[38] = 99;
    } catch(ArithmeticException e) {
      System.out.println("0 ile bölme : " + e);
    } catch(ArrayIndexOutOfBoundsException e) {
      System.out.println("Array index oob: " + e);
    }
    System.out.println("try/catch blokundan sonraki kodlar.");
  }
}
 
Bu program koşturulmak üzere çağrılırken komut satırından hiç parametre girilmezse, a=0 olur. Dolayısıyla, sıfıra bölme hatası oluşur. Öte yandan, koşturulmak üzere çağrılırken komut satırından parametreler girilirse a>0 olur, dolayısıyla sıfıra bölme hatası oluşmaz. Ancak, hemen arkasından şu hatayla karşılaşır. 7-inci satırdaki tanımı uyarınca c[] diziminin (array) birtek bileşeni vardır, o da c[0] = 1 dir. 8-inci satırdaki deyim ise c[37] = 89 atamasını yapmaktadır. Bu olanaksızdır ve hata oluşur. Bu hata
                ArrayIndexOutOfBoundsException
adıyla bilinen hatadır. Öyleyse, bu program mutlaka hata verecektir.
Bu program koşturulmak üzere çağrılırken komut satırından hiç parametre girilmezse, sıfıra bölme  hatası dediğimiz şu hatayı atar:     
a = 0
Divide by 0: java.lang.ArithmeticException: / by zero
After try/catch blocks.
 
Bu program koşturulmak üzere çağrılırken komut satırından parametre(ler) girilirse, sıfıra bölme  hatasını atlar, arkasından gelen şu hata atımını yapar:
 
a = 1
Array index oob: java.lang. java.lang.ArithmeticException:38
try/catch blokundan sonraki kodlar.
 
catch blokunda alt-sınıflardaki hatalar daima üst-sınıftaki hatalardan önce yazılmalıdır. Çünkü, catch deyimi, üst-sınıftaki hatalardan başlayarak alt-sınıflara doğru iner. Eğer üstlerde bir hata yakalamışsa, o hatayı atar ve alttaki hatalara ulaşamaz.
Java’da erişilemeyen kodlar da hata atımına neden olur. Aşağıdaki buna bir örnektir:
 
/* 
Bu program hatalıdır.
Bir dizi catch deyimi varsa, bu deyimler alt-sınıftakilerden
başlayarak üst-sınıflara doğru sıralanmalıdır.
Aksi halde, ulaşılamayan deyimler derleme-zamanı hatası
(compile-time error)oluşturur.
*/
class Hata07 {
  public static void main(String args[]) {
    try {
      int a = 0;
      int b = 38 / a;
    } catch(Exception e) {
      System.out.println("Generic Exception catch.");
    }
 
    /*
                   Aşağıdaki koda ulaşılamaz, çünkü
                   ArithmeticException sınıfı Exception sınıfının bir alt-sınıfıdır.
    */
 
    catch(ArithmeticException e) { // HATA - ulaşılamaz
      System.out.println("Bu koda ulaşılamaz.");
    }
  }
}
 
 
İç-içe hata deyimleri
try deyimleri iç-içe konabilir. Aşağıdaki buna bir örnektir.
        // İç-içe try deyimleri.
class Hata08 {
  public static void main(String args[]) {
    try {
      int a = args.length;
 
      /*
       *Satır komutundan parametre girilmezse
       *aşağıdaki atama sıfıra bölme hatası oluşur.
      */
      int b = 38 / a;       // sıfıra bölme hatası
 
      System.out.println("a = " + a);
 
      try {           // iç-içe try blokları
        /*
         *Satır komutundan tek parametre girilirse
         *aşağıdaki deyim sıfıra bölme hatası oluşur.
       */
        if(a==1) a = a/(a-a); // sıfıra bölme hatası
 
        /*
         *Komut satırından iki parametre girilirse
         *aşağıdaki deyim
          out-of-bounds hatası oluşturur.
        */
        if(a==2) {
          int c[] = { 1 };
          c[37] = 89;       // out-of-bounds hatası
        }
      } catch(ArrayIndexOutOfBoundsException e) {
        System.out.println("Array indisini aşar : " + e);
      }
 
    } catch(ArithmeticException e) {
      System.out.println("0 ile bölme: " + e);
    }
  }
}










Aradığınızı Bulamadınız Mı ?

Konuyu Okuyanlar:
1 Ziyaretçi