Java

Java ile Exception Handling

20 Ağustos 2018

Merhabalar,

Bilgisayar programcıları veya yazılımcılar olarak kod yazarken sürekli olarak göz önünde bulundurmamız gereken bir şey vardır. Nasıl kod yazıyor olursak olalım, her zaman kullanıcı veya yazılımcı hatalarını göz önünde bulundurmamız gerekiyor. Bu hatalar bazen kullanıcılardan kaynaklanırken, bazen yazılımcılardan kaynaklanır. Bunun yanında bu hatalar bazen de programdan yani Java’dan -veya JVM’den- kaynaklanır.

Exception dediğimiz şey, yazdığımız program çalışırken oluştuğunda programın çalışma akışını bozar. Doğal olarak bunun olmasını istemeyiz. Bunu önlemek için oluşan bu Exception’ı çözmemiz (“handle etme” terimini kullanacağım bundan sonra) gerekir. Biz buna “Exception Handling” diyoruz.

Exception neden oluşur?

Exception çoğu zaman, kullanıcı ve program arasındaki anlaşmazlıklardan oluşur. Örneğin, program kullanıcıdan bir sayı girmesini ister, ama kullanıcı bu kurala uymayıp yazı girer. Böyle olunca program yanlış çalışmaya başlar ve bir Exception fırlatır (throw). Bu durumda programı yazan kişinin bu Exception’ı yakalaması (catch) lazım. Eğer bunu yapmazsanız, program çöker ve çalışmayı durdurur. Başka nelerden dolayı Exception fırlatılır?

  • Bir dosya açmak isterseniz ama verilen dosya yolunda öyle bir dosya yoktur.
  • İletişimin tam ortasında ağ bağlantısının kopması.
  • JVM’in belleğine aşırı yüklenme yapılması. Buna “out of memory” de deniyor. Genelde “memory leak” durumlarında oluşuyor.
  • Elinizde bir dizi (array) vardır ve olmayan bir elemanı çağırmaya çalışıyorsunuzdur.

Exception türleri nelerdir?

Şu an için bu kısımlar size teorik bilgi gibi gelecektir. Ancak kodları yazarken bu bilgilere ihtiyaç duyacaksınız. Çünkü Java, Exception Handling işini sizin yapmanızı bekliyor ve bazen sizi uyarırken bu konuda, bazen de uyarmıyor. O yüzden nerede Exception Handling yapmanız gerektiğini kendiniz bilmelisiniz ve bunun gerekliliğini hissetmeniz gerekiyor. Bunun için de bu temel bilgilere ihtiyaç duyuyoruz. Şimdi, aslında iki çeşit Exception bulunuyor: Checked ve Unchecked. Bunun yanında bir de “Error” dediğimiz bir şey var. Bu Exception’un dışında tuttuğumuz bir şey. Sırayla hepsine değineceğiz.

Checked Exceptions

Checked Exceptions dediğimiz şey, programın “compile time” esnasında handle edilmesi gereken Exception türleridir. Kullandığınız IDE, program çalışmadan önce size uyarı verir genelde. Örneğin, bir dosya okuması işlemi yapacaksanız, daha programı çalıştırmadan önce IDE size uyarı vererek “Burada Exception handle yapmanız gerekiyor.” der. Örneğin, IDE olarak Eclipse kullanalım ve aşağıdaki koda bakalım:

Şimdi, Eclipse bizi iki konuda uyarıyor aslında. İkincisi şu an için bizi ilgilendiren kısım. Bize söylediği şu:

“Burada Exception Handle yapman lazım. Hatta tam olarak FileNotFoundException’ını yakalaman lazım. Aksi halde program beklenmedik bir anda kapanabilir.”

Merak edenler için, diğer hata da şunu söylüyor:

“Normalde FileReader ile dosya okunduktan sonra dosyayı okuyan objenin kapatılması gerekiyor. Bunu yapmazsanız memory leak denilen şey oluşur ve bir zamandan sonra programınız çökebilir. Bunu önlemek için read.close() metoduyla read objesini kapatmanız gerekiyor.”

Not: close() ve read() gibi metodların da IOException denen bir Exception’ı yakalaması lazım. Bu nedenle bu metodları kullanıyorsanız, bu Exception türünü de handle etmeniz gerekiyor.

Aslında burada bir çözüm cevap eklemem gerekiyor ama önce “try-with-resources” kısmına geçtikten sonra bunun çözümüne değineceğiz. Şimdilik, örnek olması için çok da iyi olmayan bir “çözüm” önereceğim.

Unchecked Exceptions

Checked Exceptions programın “compile time” esnasında ortaya çıkıyordu. Unchecked Exception ise “runtime” esnasında ortaya çıkan Exception’lardır ve IDE’niz sizi bu konuda uyarmaz. Doğal olarak bu Exception türlerini sizin fark edip, handle etmeniz gerekiyor. Örnek verecek olursak, dört işlem yapan bir programınız olsun ve işlem için gerekli sayıları kullanıcıdan alıyorsunuz. Kullanıcı bölme işleminde paydaya 0 sayısını koyarsa, programınız hata verecektir ve çalışmayı durduracaktır. Bu nedenle sizin bu Exception’ı handle etmeniz gerekiyor. Bunun için de ArithmeticException dediğimiz Exception türünü yakalamanız gerekiyor.

Örnek olarak kullanıcıdan iki sayı alalım ve bu iki sayıyı bölerek konsola yazdıralım. Bunun için Scanner classından yardım alacağım. Kodumuz şöyle:

Program kısaca kullanıcıdan iki sayı alıyor ve bu sayıları bölüyor. Şimdi, ilk olarak normal sayılarla deneyelim. Örneğin 5 ve 5 verelim. Cevabımız 1 olacak.

First number:
5
Second number:
5
Result: 1

Şimdi, 5 ve 0 verelim.

First number:
5
Second number:
0
Exception in thread “main” java.lang.ArithmeticException: / by zero
at ExceptionHandling.main(ExceptionHandling.java:11)

Gördüğünüz gibi, burada ArithmeticException denilen bir hata aldık. Yanında da hatanın sebebi “/ by zero” yani “0 ile bölüm” hatası aldık. Bu tür hatalar program çalışmadan önce IDE bizi uyarmadığı için genelde program çalıştıktan sonra fark edilir. Her ne kadar burada çok basit bir örnek üzerinde çalışıyor olsak da, kompleks bir kodda bu tür hataların kaynağını bulmak zorlaşabilir, hatta düzeltmek daha da zor olabilir. Burada aslında sadece 0 ile bölüme bakmak doğru değil tabii ki. Örneğin, kullanıcı sayı değil de, harf girebilir. O zaman da InputMismatchException denilen bir Exception fırlatılır. Doğal olarak bunları tek tek yakalamak gerekiyor, mu acaba? Buna da daha sonra değineceğim.

Sonuç olarak kodunuzu yazarken tüm bunları göz önünde bulundurmanız gerekiyor. Programınızın beklenmedik bir şekilde sonlanmasını istemiyorsanız, bu hataların hepsini handle etmeniz gerekiyor.

Errors

Bunun üzerinde çok durmayacağım. Çünkü, Error aslında bir Exception değildir. Hatta Java’da Throwable classının içinde bulunur ve Exception’ın bir nevi kardeşidir diye biliriz. Errorlar kullanıcıdan veya programcıdan kaynaklanmıyor olabilir. Örneğin, program bilgisayarın belleğini çok fazla kullanıyordur ve bu programda hataya sebep olur. Ama aslında programın da bu kadar belleği kullanması gerekiyordur. Doğal olarak bu hata kullanıcıdan veya programcıdan kaynaklanmıyordur.

Exceptions

Aslında konuya biraz tersten girmiş gibi oldum ama bir sebepten dolayı bunu yaptım. Şimdi, Java’da classların hiyerarşisini düşünelim. Unchecked ve Checked Exceptions diye ikiye ayırma işlemi yapmıştık. Yani Exceptions classının aslında iki tane alt classı bulunuyor. Bunlara “Runtime Exceptions (Checked Exceptions)” ve “Other Exceptions (Compile Time Exceptions)” deniyor. Aynı zamanda Throwable diye bir classımız var ve bunun da iki alt classı bulunuyor. Bunlar Error ve Exception. Yani hiyerarşi şöyle:

Sanırım buraya kadar her şey anlaşılmıştır.

Peki, bu Exception classı bizim ne işimize yarıyor? Fark ettiyseniz Exception classı tüm Exception türlerinin üstünde bulunuyor. Doğal olarak eğer siz bir program yazıyorsanız ve bu programın ne tür Exception’lar fırlatacağını bilmiyorsanız, hızlı ve kolay bir çözüm olarak Exception yakalama işlemini yapabilirsiniz. Daha iyi anlamak adına önceki dosya okuma örneğindeki kodumuzda bir sıkıntı vardı. Dosyamızı kapatmamıştık. Çünkü, aynı anda birden fazla Exception yakalamaya çalışıyorduk. Doğal olarak iki defa “catch” işlemi yaptık. Şimdi, bunun yerine kodumuzu şöyle yazabiliriz ki, bu da çok iyi bir çözüm değil aslında.

Yukarıdaki programda her türlü Exception’ı yakalıyoruz aslında. Yani, bir nevi bu bizim için genel bir çözüm. Ama yine de en iyi çözüm değil. Çünkü, şöyle düşünelim. Exception fırlatıldığında aslında program “try” kısmının içindekileri es geçerek “catch” blokuna geçiyor. Hatamızın aslında dosyayı okuma sırasında gerçekleştiğini, yani aslında böyle bir dosyamız olmadığı için oluştuğunu düşünelim. Böyle olunca program direkt olarak “catch” blokuna atlayacak ve bu hatayı Exception classı sayesinde yakalayacak. Daha sonra bunu konsola yazdırıp programın devamında ne çalıştırması gerekiyorsa çalıştırmaya devam edecek. Durum böyle olunca program hiçbir zaman “read.close()” satırına gelmeyecek ve okunmaya çalışılan dosya hiçbir zaman kapatılmayacak. Bu da daha önce de söylediğimiz gibi daha sonraları “memory leak”e sebep olabilir. Senaryo sanırım kafanızda canlanmıştır. Peki, bunun için ne yapılabilir?

“Finally” Bloku

Çözümlerden birisi, yine de en iyisi değil ama işe yarar bir çözüm, “finally” blokunu kullanmak. Bu blokun içindeki kod programa ne olursa olsun çalıştırılır. Yani,

  • program “try” blokunun içine girip gerekli işlemleri yapar,
  • eğer Exception fırlatılırsa “catch” blokunda bunu yakalar,
  • her şey bittikten sonra “finally” bloku çalıştırılır.

O zaman biz okunan dosyayı kapatma işlemini “finally” blokunda yapabiliriz. Hemen deneyelim.

Baktığımız zaman, kodumuz biraz daha kompleks oldu gibi görünüyor. Ama aslında böyle değil. Olması gereken bu. Bu sayede programımızda beklenmedik kapanmalar, memory leak gibi sorunlar oluşmayacak. Sırf bir kaç satır koddan kaçınmak için programımızın hata almasına sebep olmak çok da mantıklı değil. Ama tabii ki bundan daha iyi bir çözüm de var. Biz bunu işe yarayan çözümlerde ikinci sıraya koyalım. Şimdi, gelelim en iyi çözüme.

try-with-resources

Yukarıdaki örnekte olduğu gibi bir “resource” yani kaynak açıldığı zaman, eğer bunu try blokunda yaparsanız, Java otomatik olarak programla işi bittikten veya Exception yakalandıktan sonra bu kaynağı kapatır. Örnekle aslında daha anlaşılır olacaktır.

Baktığımız zaman, gayet güzel bir çözüm gibi duruyor, ki öyle!

Not: Ancak bunda dikkat etmeniz gereken nokta Closeable interfaceinin implement edilmesi gerekiyor. Bunu nasıl anlarız? Eclipse’te CTRL tuşuna basarak “FileReader” yazısına tıklayın. Bu sizi “.class” dosyasına götürecektir. Daha sonra bu classın aslında “InputStreamReader” classını extend ettiğini göreceksiniz. Yine CTRL tuşuna basarak bu classa gidin. Bu class da “Reader” classını extend ediyor. Yine CTRL tuşuna basarak bu classa gidin ve bu classın da “Readable, Closeable” interfacelerini implement ettiğini göreceksiniz. Doğal olarak aslında FileReader classı Closeable interfaceini implement ediyor.

“throws / throw” Kullanımı

Eğer metodunuz bir Checked Exception türünü handle etmiyorsa, o zaman bunu “throws” sözcüğünü kullanarak metodunuzda tanımlayabilirsiniz. Bu sözcük metodunuzun yazarken “{” işaretinden önce eklenir.

İsterseniz yeni bir Exception yakalayarak bunu fırlatabilir veya yakaladığınız bir Exception’ı “throw” ile fırlatabilirsiniz. Burada şuna dikkat edin: “throws” ile “throw” aynı şey değildir. “throws” sözcüğü aslında bir Checked Exception’ı handle etmeyi geciktirirken, “throw” sözcüğü o anda o Exception’ı ayrıca fırlatmanızı sağlar.

Ama kodunuzun iyi bir şekilde çalışması ve hatalarınızı daha iyi bir şekilde görmek için çoğu zaman “try-catch-finally” daha iyi bir çözüm olacaktır. Zaten “throws” özelliğini genelde Exception’ı handle etmek istemeyip, sadece o anda ezmek için kullanıyoruz. Bir örnekle daha açıklayıcı olacaktır diye düşünüyorum. Örneğimizde kullanıcıdan aldığımız iki sayıyı böleceğiz ve bölüm 5’ten küçük çıkarsa bir ArithmeticException fırlatacağız.

Program iki sayıyı bölüyor ve sonuca göre konsola bir şeyler yazdırıyor. İlk olarak 10 ve 1 değerlerini verdim. Konsola yazılan:

First number:
10
Second number:
1
10
Works fine!

Daha sonra 4 ve 2 sayılarını verdim. Konsola yazılan:

First number:
4
Second number:
2
2
Exception in thread “main” java.lang.ArithmeticException: Result cannot be less than 5.
at ExceptionHandling.main(ExceptionHandling.java:22)

Sanırım anlaşılır olmuştur. Şunu da belirtmek gerekir ki, çoğu zaman profesyonel bir iş üzerinde çalışırken bu tür Exception ve benzeri hataları aslında bir “logger” kütüphanesi kullanarak, ayrı bir “.txt” benzeri dosyaya yazdırırız. Bu sayede program hata aldığında bile çalışırken, programı yazan kişiler alınan hataları tek bir noktada rahat bir şekilde görebilir.

Aslında bir sonraki konumuz “Custom Exceptions” yani programcının kendi yazdığı özel Exception’lar. Ancak onu bu yazıda değil de, ayrı bir yazıda daha sonra anlatmak istiyorum. Şu an için Exception’lar hakkında bilmeniz gerekenler bunlar. Tabii buradaki bir çok bilgi teorik şu an için. Başka bir zamanda da Exception’ların kullanımlarında dikkat edilmesini gereken, “best practice” dediğimiz zamanları anlatmaya çalışacağım. Şimdilik yazıyı burada bırakabiliriz, zaten yeterince uzun oldu.

    Bir Cevap Yazın

    This site uses Akismet to reduce spam. Learn how your comment data is processed.