Nesne yönelimli programlama dillerini özel kılan
yanlardan biriside sağlamış oldukları istisna yönetim mekanizmalarının
çeşitliliğidir. Çoğu zaman, özellikle çalışma zamanında oluşabilecek
hataların gölgesinden kurtulmak ve kullanıcı dostu projeler geliştirmek
zorundayız. Bunu yaparken sadece doğru kurallar ve algoritmalar yeterli
olmayabilir. Özellikle kullanıcı etkileşimli tasarımlarda, gelebilecek değişik
ve vurdumduymaz taleplere karşı hazırlıklı olmak gerekir.
Java dilinde olsun C# dilinde olsun, çalışma
zamanında oluşabilecek yada derleme zamanında oluşması kesin bir takım hataların
önüne geçmek amacıyla istisna mekanizmaları kullanılmaktadır. Istisna
mekanizlamaları şu meşhur herkesin duyduğu hatta bildiği try-catch-finally bloklarının
kullanıldığı teknik ile sağlanıyor. Kanımca, istisnaları
anlayabilmenin en iyi yolu istisna fırlatan ( ki bu terim throwing olarak
geçiyor ) bir uygulama geliştirmekten geçiyor. Bu amaçla aşağıdaki gibi çok basit bir
uygulama tasarladım. Oluşacak hata baştan belliydi. Balık baştan kokmuştu. Ama
neyseki keskin Jacobs aromalı kahvem bu kokuyu bastırıyordu. Kalkıpta, 10
elemanlı bir dizinin 10nuci indeksine, bu dizinin 0 tabanlı olduğunu bile bile
erişmeye çalışmak elbette bellekte böyle bir yer ayrılmadığı için hataya neden
olucaktı. Ama istisnaları ele almanında en kolay yolu böyle basit bir örnekten
geçiyordu.
public class Istisnalar
{
public static void main(String[] args)
{
int dizi[]=new int[10];
for(int i=0;i<10;i++)
{
dizi[i]=i*i;
}
System.out.println("Dizinin 10ncu
elemani "+dizi[10]);
System.out.println("Program sonu");
}
} |
Bu uygulamayı derleyip çalıştırdığımda aşağıdaki
gibi bir hata mesajı ile karşılaştım. Aslında şu ana kadarki kahve molalarımda,
çoğu zaman hatalar ile karşılaşmıştım ve durumu düzeltmiştim. Ancak bu kez
meydana gelen bu hatanın çalışma zamanından nasıl önleneceğini ve uygulamanın
aniden sonlandırılmasının nasıl önüne geçileceğini inceleyecektim.

Burada gerçekleşen en önemli şey, programın
çalışması sırasında bir istisnanın oluşması ve sonlandırılmasıdır. Öyleki,
hatanın meydana geldiği satırdan sonraki program kodu ifadesi
çalıştırılmamıştır. Yani, isteğimiz dışında bir sonlandırılma
gerçekleştirilmiştir. Buda elbetteki istenmeyen bir durumdur. Ancak, istisna
yönetim mekanizması yardımıyla bu kodun sorunsuz bir şekilde çalışmasını ve
istemsiz olarak aniden sonlandırılmasını engelleme şansına sahibim. Nasıl olucak
bu iş? Elbetteki, C# dilinden gelen engin tecrübelerimi burada aynen
değerlendirmek taraftarıyım. Kodu şu şekilde düzenlersem istediğimi elde
edeceğimi biliyordum.
public class Istisnalar
{
public static void main(String[] args)
{
int dizi[]=new int[10];
for(int i=0;i<10;i++)
{
dizi[i]=i*i;
}
try
{
System.out.println("Dizinin 10ncu elemani "+dizi[10]);
System.out.println("Program sonu");
}
catch(ArrayIndexOutOfBoundsException
e)
{
System.out.println("Bir hata olustu :"+e);
}
}
} |

Burada yapılan işlem aslında try-catch blokları ile
hata takibinin en basit şekliydi. Şematik olarak durum ancak aşağıdaki kadar
güzel bir şekilde dramatize edilebilirdi.

İki uygulamanın sonucu arasında çok fazla fark
yokmuş gibi görünüyor ilk başta. Acaba gerçekten böyle mi? Elbette değil.
Herşeyden önce, bu kez hataya yol açan kod satırını try bloğu içerisine aldım.
Diğer yandan, oluşacak olan hatayı catch bloğunda yakalayarak kendi istediğim
kod satırının devereye girmesini sağladım. Bu oldukça önemli. Nitekim, programın
çalışmasının normalde sonlandırılacağı yerde, catch bloğundaki kodların devreye
girmesini sağlamış oldum. Bu, özellikle kullanıcı taraflı hataların
değerlendirilmesinde oldukça önemlidir. Ancak bu kod satırlarında dikkat çeken
diğer bir nokta, hatanın bir nesne olarak tanımlanması ve mesaj olarak
gösterilmek için kullanılmasıdır.
| catch(ArrayIndexOutOfBoundsException e) |
Aslında burada yapılan, derleme zamanında try bloğu
altında meydana gelecek hatalarda, catch bloğunda belirtilen sınıf nesnesinin
işaret ettiği türden bir istisnanın fırlatılmasıdır. Dolayısıyla, burada
belirtilen türden bir istisna oluşursa ne olucaktır? Dizinin boyutu dışındaki
bir indekse erişilmesi sonucu oluşturulan bu istisna nesnesi son derece
spesifiktir. Ancak durum her zaman bu şekilde olmayabilir. Herşeyden önce, bunu
irdelemenin en iyi yolu aynı program kodunda farklı türden bir hatanın meydana
gelmesine yol açmaya çalışmak olmalıdır. Şöyleki,
try
{
int a=1;
int b=a/0;
System.out.println("Dizinin 10ncu elemani "+dizi[10]);
System.out.println("Program sonu");
}
catch(ArrayIndexOutOfBoundsException e)
{
System.out.println("Bir hata olustu :"+e);
} |
Bu haliyle kodları çalıştırdığımda, aşağıdaki hata
mesajı ile uygulamanın şahane bir şekilde sonlandırıldığını gördüm. Ne yazdığım
catch bloğı devreye girmiş nede uygulama istediğim şekilde sonlanmıştı.

Sorun şuydu. Catch bloğunda sadece, indeks
taşmasına ilişkin bir istisna nesnesi yakalanmış, ancak bu meydana gelmeden
önce, sıfıra bölme hatasını fırlatacak bir istisna oluşmuştu. Yapılabilecek iki
şey vardı. İlk aklıma gelen ikinci bir catch bloğunu dahil etmekti. Aynen
aşağıda olduğu gibi,
try
{
int a=1;
int b=a/0;
System.out.println("Dizinin 10ncu elemani "+dizi[10]);
System.out.println("Program sonu");
}
catch(ArithmeticException e)
{
System.out.println("Sifira bolme hatasi :");
}
catch(ArrayIndexOutOfBoundsException e)
{
System.out.println("Indeks tasma hatasi :");
} |

Böylece aslında birden fazla catch bloğununda nasıl
kullanılabileceğini görmüş olmuştum. Diğer yandan ikinci bir yol daha vardı.
Daha genel bir yol. Istisna yönetiminde, catch bloklarında sınıf nesneleri
tanımlanmaktaydı. Bununla birlikte bu sınıflar Exception ismi ile bitiyorlardı.
Kendimi bir anda dedektif gibi hissettim desem yeridir. Bir sonuca varmak
istiyorum ama ne? Yardımıma geniş bir java dökümanı yetişti. Burada gördüğüm
kadarı ile, istisna sınıfları aslında belli bir hiyerarşik düzen içerisinde yer
alıyor ve birbirlerinden türetilen bir sistematik oluşturuyorlardı. Bu yapıyı
zaten C# dilinden biliyordum. Ancak java'da hiyerarşi biraz daha farklıydı.
Ancak ilk dikkatimi çeken, uygulamanın çalışması sırasında yakalanabilecek
düzeyde olan istisnaların Exception sınıfından türüyor olmalarıydı. Dolayısıyla,
yukarıdaki örneği tek bir catch bloğu ile aşağıdaki gibi oluşturulabilirdim.
try
{
int a=1;
int b=a/0;
System.out.println("Dizinin 10ncu elemani "+dizi[10]);
System.out.println("Program sonu");
}
catch(Exception e)
{
System.out.println("Hata :" +e);
} |
Böylece, aslında try bloğunda, Exception sınıfından
türeyen tüm istisna sınıfları tarafından temsil edilebilecek düzeyde oluşacak
hataları yakalamış oldum. Elbette böyle bir durumda, oluşacak hataya özel kod
geliştirmemiz biraz daha zor olucaktır. Çünkü hatayı en genel seviyede
yakaladık. Aslında yeri gelimişken, java dilindeki istisna yönetimi
sistemindende bahsetmek gerekli. Bu sistem aşağıdaki gibi bir hiyerarşiye sahip.

Elbette hiyerarşi bu kadarla sınırlı değil.
Yüzlerce sınıf var. En önemli nokta, istisnaların iki ana kısımda ele alınması
ve Throwable sınıfından türemiş olmaları. Throwable sınıfı, java uygulamalarının
çalışması sonucu oluşabilecek hataları iki ana alt sınıfta kapsayarak
değelendirmekte. Bu sınıflardan bir tanesi, try catch blokları ile
yakalanamıyacak tipte olan ciddi sistem hatalarını bünyesinde barındırdan Error
ata sınıfı. Diğeri ise, şu ana kadarki asıl örneklerde de incelediğim,
uygulamanın çalışması sırasında yada derlenmesi sırasında , try catch tekniği
ile yakalanabilecek hataları içeren Exception ata sınıfı. Örneğin benim örnek
uygulamalarda incelediğim istisnalar, Exception sınıfından türeyen
RuntimeException sınıfından türemiş istisnalardı. Buradaki sınıfların detaylı
şekilde bilinmesi elbette mümkün değil. Ancak uygulamalarımızı geliştirirken çok
iyi test etmeli ve ortaya çıkan istisna mesajlarını iyi değerlendirerek ele
almalıyız diye düşünüyorum.
Gelelim istisna işleme ile ilgili diğer ilginç
noktaya. C# dilinde, try blokları içerisinde, metod çağırımları kullanıldığında,
bu metod çağırımlarında meydana gelebilecek hatalar ilgili catch bloklarınca
yakalanbilmekteydi. Bakalım aynı durum java dili içinde geçerli mi? Bu kez
hataya neden olucak kodları bir metod içinde aldım ve try bloğu içinden
çağırdım.
public class Istisnalar
{
public static void main(String[] args)
{
int dizi[]=new int[10];
for(int i=0;i<10;i++)
{
dizi[i]=i*i;
}
try
{
Metod();
System.out.println("Dizinin 10ncu elemani "+dizi[10]);
System.out.println("Program sonu");
}
catch(Exception e)
{
System.out.println("Hata : "+ e);
}
}
public static void Metod()
{
int a=1;
int b=a/0;
}
} |
Sonuç olarak uygulama başarılı bir şekilde derlendi
ve çalıştı. Demekki, hatayı oluşturacak olan kodlar başka metodlar içerisinde
bulunsa dahi, dolayısıyla uygulamanın başka bir konumunda olsa dahi, try
bloklarınca ele alınabilir ve fırlatacakları istisnalar yakalanabilir. İstisna
işlenmesi ile ilgili bir diğer ilginç konuda, iç içe geçmiş istisna bloklarının
kullanımıdır. İç içe geçmiş try blokları çoğunlukla, hataların oluştukları
yerlere göre farklı davranışlarda bulunabilirler. Neden iç içe geçmiş istisna
bloklarını kullanırız pek fazla fikrim yok. Ancak dilin zenginliğini bilmek
açısından da oldukça önemli. Bu amaçla önce aşağıdaki gibi bir örnek
geliştirdim.
try
{
Metod();
try
{
System.out.println("Dizinin 10ncu
elemani "+dizi[10]);
System.out.println("Program sonu");
}
catch(IndexOutOfBoundsException e)
{
System.out.println("Dizi tasmasi : "+
e);
}
}
catch(ArithmeticException e)
{
System.out.println("Sifira Bolme : "+ e);
} |

Dikkatlice baktığımda gördüm ki, metodun içinde
meydana gelen sıfıra bölme hatası, içteki try bloğu çalışmadan hemen önce
meydana geldiğinde, içteki try blokları çalıştırılmadan, dıştaki catch bloğu
çalıştırılmış ve program sonlandırılmıştı. O halde, bende try blokları gibi
değişik senaryoları denemeye karar verdim. Bu kez, Metod çağırımını içteki
try-catch bloğundan sonraya aldım.
try
{
try
{
System.out.println("Dizinin 10ncu
elemani "+dizi[10]);
System.out.println("Program sonu");
}
catch(IndexOutOfBoundsException e)
{
System.out.println("Dizi tasmasi : "+
e);
}
Metod();
}
catch(ArithmeticException e)
{
System.out.println("Sifira Bolme : "+ e);
} |

Ne kadar ilginç. İçteki try bloğunda meydana gelen
hata içteki catch bloğunda yakalandı ama uygulama sonlanmadan diğer kod satırı
devereye girdi ve dolayısıyla Metod isimli metodum çalıştırıldı. Burada oluşan
hatada dıştaki catch bloğunu ilgilendirdiğinden, dıştaki catch bloğuda devreye
girdi ve her iki hatada yakalandıktan sonra uygulama sonlandı. Derken aklıma
şöyle bir senaryo daha geldi. İçteki try bloğunda yakalanamıyacak cinsten bir
hata oluşsa ve bu hatayı yakalayacak catch dıştaki olsa, bu hata yakalanır
mıydı? Bir başka deyişle acaba, içteki catch bloğunca yakalanamıyacak hata,
dıştaki catch bloğunda değerlendirilmeye alınır mıydı yoksa program sonlanır
mıydı? Büyük bir merak içinde hemen aşağıdaki kodları yazdım. Aklıma gelen ilk
şey istisna sınıf nesnelerinin yerlerini değiştirmek oldu. Ne kadarda yaratıcı
değil mi :)
try
{
try
{
System.out.println("Dizinin 10ncu
elemani "+dizi[10]);
System.out.println("Program sonu");
}
catch(ArithmeticException e)
{
System.out.println(e);
}
Metod();
}
catch(IndexOutOfBoundsException e)
{
System.out.println(e);
} |

Evet şimdi şunu bir incelemekte fayda var. İçteki
try bloğunda meydana gelicek olan hatayı, içteki catch bloğunun yakalayamıyacağı
kesindi. Bu durumda java çalışma zamanında, dıştaki catch bloğuna geçerek,
içteki try bloğunda oluşan hatayı burada aramaya karar verdi. Buradada buldu.
Dolayısıyla dıştaki catch bloğunu çalıştırarak, uygulamayı bu noktada
sonlandırdı. Yani, içteki try bloğunda oluşan hata dıştaki catch bloğu
tarafından yakalandı.
İç içe geçmiş try-catch bloklarında
oluşabilecek diğer bir durum ise, herhalde catch bloklarında oluşabilecek
hataların nasıl değerlendirilebileceği olabilirdi. Öyleki içteki catch bloğunda
bir hata meydana geldiğinde bu hatanın dıştaki catch bloğu tarafından
yakalanması gerekirdi. Deneyip görmek lazımdı. Çalışmak lazımdı. Durumu en iyi
şekilde değerlendirebilmek için, her iki catch bloğunada aynı öncelikli bir
istisna sınıfı nesnesi eklemem gerektiğini düşündüm. Bu nedenlede Exception
sınıfını kullandım.
try
{
try
{
System.out.println("Dizinin 10ncu
elemani "+dizi[10]);
System.out.println("Program sonu");
}
catch(Exception e)
{
int a=1;
int b=a/0;
System.out.println("Icteki catch
"+e);
}
}
catch(Exception e)
{
System.out.println("Distaki catch "+e);
} |

Sonuç beklediğim gibi olmuştu. İçteki catch
bloğunda oluşan hata dıştaki catch bloğunca değerlendirilmeye alınmıştı. Aslında
try-catch blokları ile ilgili şu ana kadar kullanmadığım bir anahtar kelime var
şimdi aklıma geldi. O da finally anahtar kelimesi. finally blokları bir
try-catch kurgusunda, hata olsada olmasada devreye giren yapılardır. Örneğin;
try
{
Metod();
System.out.println("Dizinin 10ncu elemani "+dizi[10]);
}
catch(Exception e)
{
System.out.println(e);
}
finally
{
System.out.println("Program sonu");
} |
Burada try bloğu içinde oluşacak olan hatanın catch
bloğunda yakalanacağı kesindir. Ancak program catch bloğundan sonra hemen
sonlandırılmaz. Bu noktadan sonra finally bloğu devreye girer ve içerdiği kodlar
çalıştırılır. Dolayısıyla uygulamanın çıktısı aşağıdaki gibi olucaktır.

İstisna yakalama ile ilgili söylenebilecek yada
düşünülebilecek şeyler sadece bunlarla sınırlı değil. Örneğin, beğenmediğimiz
istisna sınıfları yerine kendi istisna sınıflarımızı yazabiliriz. Son derece
kolay olan bu işlemde, tek yapmamız gereken, istisna sınıfımızı Exception
sınıfından türetmemizdir. Derken tabi aklıma bir soru geldi. Her programcının
bir programlama dilini öğrenirken hemen hemen her konuda sorduğu bir soru. Neden
bu kadar geniş bir istisna sınıf hiyerarşisi varken kendi istisnalarımı yazmaya
çalışayım ? Bunun tek nedeni bazen meydana gelen istisnanın her şeyi tam olarak
açıklayamaması olabilirdi. Buna en güzel örnek SqlException istisna sınıfıdır
sanırım. Sql ile ilgili veri tabanı işlemlerinde oluşabilecek sorgu hatalarından
tutunda, bağlantıda oluşabilecek hatalara kadar pek çok hatayı temsil eder.
Ancak bize hata ile ilgili detaylı yada spesifik bilgiler sunmaz. Böyle bir
durumda kendi istisna sınıfımızı yazma ihtiyacı duyabiliriz. Bu konu aslında
geniş bir konu ve sanırım veri tabanlarının java dilinde kullanılması ile ilgili
kahve molalarında inceleyeceğim.
Burak Selim ŞENYURT
selim@bsenyurt.com