HATA YAKALAMAK ve EXCEPTION SINIFLARIMemik Yanık |
|
Malumunuz olduğu üzere .NET Framework ve C# programlama dili Microsoft firması tarafından hazırlanıp kullanıma sunulalı az olmadı. Bu süreçte yani .NET Framework 1.0 dağıtıldıktan bu yana geçen sürede önce 1.1 sonra 2.0 ve en son olarak 3.0/3.5 sürümleri programcıların istifadesine sunuldu. Benzer gelişme C# programlama dili için de geçerlidir. İçinde bulunduğumuz günlerde .NET Framework 3.5 ile birlikte C# derleyicisinin 3.0 sürümü gelmektedir.
C# kodu içinde çalışma anında meydana gelmesi muhtemel hataları yakalamayla ilgili sınıflarda .NET Framework’ün ilk sürümden bugüne kayda değer bir gelişme olmadığı için C# konulu her kaynakta Exception sınıfları ile ilgili bilgiyi bulmak mümkündür. Ötesi 2002 yılından bu güne kadar hata yakalama sınıflarını anlatan çok sayıda makale programcılık konulu Web sitelerinde yayınlandı. Örneğin www.csharpnedir.com/makalegoster.asp?MId=295 adresindeki yazıda Resul Çavuşoğlu ayrıntılara girmese bile C# kodu dahilinde Exception sınıflarından nasıl yararlanıldığını gayet güzel anlatmaktadır. İkinci bir kaynak vermek gerekirse; ilk Türkçe C# kitabını yazma unvanına sahip Ahmet Dizioğlu, Elif İçağasıoğlu ve Levent Çamlıbel’in ortak yazdıkları kitaplarında Exception sınıfları hakkında yeterli bilgiyi bulmak mümkündür. Ayni konular Sefer Algan'ın ilk baskısı 2003 yılında yapılan Her Yönüyle C# adlı kitabında ziyadesiyle anlatılmaktadır. Kısaca özetlemek gerekirse; her kim Exception sınıfları ve hata yakalamak hakkında Türkçe kaynak ararsa ister kitap edinsin ister Web sitelerine baksın yeterli kaynağı elyle koymuş gibi bulabilir. Exception sınıfları ile ilgili kaynakların Türkçe olması şartından vazgeçilirse MSDN’i saymasak bile binlerce makale, yüzlerce kitap bulmak mümkündür.
O zaman birileriniz kalkıp bana sormayacak mısınız: madem bu konuda kaynak bolluğu var o zaman neden bu konuda 30 sayfa uzunlukta bir makale yazıp yayınladınız? Tabii bu soru tek cümle ile cevaplanacak bir soru değildir. 2004 yılında yayınlanan ilk C# kitabımda hata yakalama ve Exception sınıflarının anlatıldığı sayfaların sayısı topu topu 7 idi. Yani yukarıda sözünü ettiğim ve Resul Çavuşoğlu tarafından kaleme alınan makalede, benim kitaptan Exception sınıfları hakkında daha fazla bilgiyi bulmak mümkündü. Çünkü sözünü ettiğim kitabı kaleme alırken kitabın sayfa sayısı fazla artmasın diye bazı konuları ayrıntılı anlatmak yerine kısaca değinmekle yetinmiştim. Sonra 2006 yılının sonunda yayınlanan ve C# 2.0’ı anlattığım kitabımda hata yakalama ve Exception sınıflarına ayırdığım sayfaların sayısını arttırıp 16’ya çıkarmıştım. Çünkü bu kitabımı 2 cilt olarak yayınlamaya karar vermiştim. Her ne kadar Exception sınıflarına C# 2.0 kitabımda 16 sayfa yer ayırmış olsam bile bu konuyu bütün yönleri ile anlattığımı söyleyemem. Derken C# 3.0 kitabımın üzerinde çalışırken Exception sınıflarına ve hata yakalama konusuna torpil geçip normalden daha fazla sayfa ayırdım. Çünkü istedim ki, C# üzerine kitap yazma cüretini gösteren Memik YANIK’ın Exception sınıfları hakkında az çok bilgi sahibi olduğumu insanlar bilsin. Daha doğrusu Exception sınıfları hakkında bu kadar ayrıntılı metni kaleme alan birisinin sıfıra bölme hatasını anlatırken değişken çalmak üzere başka kaynaklara başvurup başvurmayacağı konusunda işin meraklıları fikir sahibi olsunlar istedim. Bu metin yakında yayınlanacak C# 3.0 kitabımdaki ilgili bölümün küçük değişiklikler, eklemeler ve çıkarmalar yapılmış halidir.
Yazarlar Neden Yazarlar?
Bence bu sorunun cevabı basittir ve kısadır: Yazarlar bildiklerinin, düşündüklerinin, hissettiklerinin bilinmesini isterler. Yani bir arkadaş(Örneğin Selim Burak Şenyurt) bilgisayarın başına oturup LINQ konulu bir makale yazıyorsa, asıl demek istediği bence şudur: “Sayın okurlar bakın ben bu konuda günlerce araştırma yaptım, araştırmalarım sırasında öğrendiklerimi test ettim, sindirdim. Sizi bu konuda haberdar etmek istiyorum”. Zaten bu kaygı ile kaleme alınan metin okunmaya değer olur. Tabii ki şartların zorlaması sonucu kaleme alınan tez, rapor vs. gibi metinler genellikle yukarıda işaret ettiğim güdü ile yazılmaz.
Tabii ki bu uzun makalede bozuk cümleler, yazım hataları, hatta teknik hatalar bile olabilir. Bu durumda okurun birisi beni hatadan haberdar ettiği zaman kendisine teşekkür edip hatamı düzeltme yoluna giderim. Eklemek istediğim 2. bir konu var: Microsoft ve diğer programlama dili üreten firmalar geliştirdikleri programlama dili ile ilgili bütün ayrıntıları açıklarlar. Yoksa insanların söz konusu programlama dilini öğrenip kullanmaları mümkün olmazdı. Bundan çıkardığım sonuç şudur: Öğretim üyeleri ve yazarlar olsa olsa programlama dilini kendi cümleleri ile anlatırlar; yeni bir şeyler söyleme ihtimali yoktur. Yazarların ve hocaların olsa olsa şöyle bir iddiası olabilir: Bu konuda bilgi sahibi olmayan birisine ben Exception sınıfları ve hata yakalamayı daha iyi anlatırım, Exception sınıflarını anlatırken vereceğim örnek kodlar daha anlaşılırdır vs. Yani elin gavuru bir programlama dili hazırlamış. Ötesi programlama dilinin bütün özelliklerini, metotlarını, sınıflarını vs. örnekleri ile açıklayıp anlatmış. Biz yazarlar ve hocalar ne yapıyoruz? Kendimizce bazı konuların önemli olduğuna kanaat getirip o konular hakkında fazladan birkaç cümle yazıyoruz. Bunu yaparken bilim yapmıyoruz, Amerika’yı yineden keşfetmiyoruz. Nasıl ki Türk Milli Takımının maçı hakkında ertesi gün bütün gazetelerin verdikleri kadrolar aynı ise, golleri atan futbolcular aynı ise bütün hocalar, bütün yazarlar C# programlama dilinden söz ederken int tipindeki değişkenlerin nasıl tanımlandığını benzer cümleler ile anlatırlar. Memik YANIK bu makalede konunun uzmanlarını geçelim, C# ile 3-5 satır kod yazan herkesin bilgi sahibi olduğu Exception sınıflarını kendince anlattı. Memik YANIK’ın iddiası olsa olsa şu olabilir: Exception sınıfları bu şekilde anlatılırsa daha kolay anlaşılır. Başka bir yazar veya hocanın yoğurt yiyişi farklı olacağı için bu sınıfları farklı cümlelerle anlatır. Madem anlatılan konu aynıdır o halde o cümleyi kim kaleme alırsa alsın bütün okurların aynı şeyi anlaması gerekir. Eğer bazı okurlar anlatılanlardan farklı sonuçlar çıkarıyorlarsa kabahat yazarındır ve yazar cümlelerinin çerçevesini iyi ayarlayamamıştır.
Projeleri Çalıştırmak
Bu metni hazırlarken Visual Studio’dan yararlandım. Sizler ücretsiz olup C# için hazırlanmış Express sürümden yararlanabilirsiniz. Tabii bilgisayarınıza .NET Framework’ü kurup(ki .NET Framework Windows Vista ile birlikte geliyor) Not Defteri ile kodunuzu yazıp konsolda yani DOS penceresinde derleyebilirsiniz. Bu şartlarda çalışma anında meydana gelecek hataları yakalamada herhangi bir sorun yaşamazsınız ama kodunuzdaki hataları ayıklamanız zahmetli olur. Çünkü Visual Studio ve Express Editon’la birlikte entegre çalışan bir hata ayıklayıcı(Debugger) verilmektedir. İşin özüne inebilmek için işe Visual Studio ile hazırlanan projelerin çalıştırılması ile başlayacağım.
Bu amaçla Visual Studio ile gelen Windows Forms Application şablonundan yararlanıp “Proje1” adında C# projesi hazırladım. Bu projeyi “C:” sürücüsünde “\Proje1” klasörüne kaydettim. Projeyi kaydettiğim klasörü özellikle işaret ettim. Çünkü proje çalıştırıldığında geri planda Visual Studio tarafından yapılan işlemlerin üzerinde biraz durmak gerekecek.
Şimdi hazırladığım projede herhangi bir değişiklik yapmadan, başlangıç formuna kontrol yerleştirmeden ve proje için başka bir Class hazırlamadan çalıştıracağım. Projeleri çalıştırmak için genellikle Debug menüsündeki Start Debugging komutu kullanılmaktadır. Genellikle dedim; çünkü projeleri çalıştırmanın başka yöntemleri de var.
Her ne kadar konunun başlığı Projeleri Çalıştırmak olsa bile bu amaçla kullanacağım komutun adı Start Debugging. Yani yazdığım projeyi Debug etmek, test etmek ve mevcut hatalardan ayıklamak istiyorum. Başlangıçta Visual Studio penceresinin başlığı Proje1-Microsoft Visual Studio iken proje çalıştırıldığında Visual Studio penceresinin başlığı Proje1[Running]-Microsoft Visual Studio olmaktadır. Start menüsünden bu komutu vermek demek, projeyi entegre Debugger’ın kontrolünde test etmek demektir.
Visual Studio ile hazırlanan C# projeleri Debug menüsünden komut verilerek çalıştırıldıkları zaman Visual Studio tarafından otomatik olarak EXE dosya hazırlanmaktadır. Proje çalıştırıldığında hazırlanan EXE dosyanın yerini aşağıda görebilirsiniz. Bu EXE dosya ancak proje hazırlanırken tercih edilen .NET Framework sürümünün(2.0, 3.0 veya 3.5) kurulu olduğu bilgisayarda çalışabilir. Aşağıda ekran görüntüsü verilen dosya listesindeki “pbd” uzantılı dosyaya dikkatinizi çekmek istiyorum. Kodun debug edilmesi yani hatalardan ayıklanması ile ilgili bilgiler bu dosyaya yazılmaktadır. Tabii bu makalede hata ayıklama işlemleri üzerinde durmayacağımız için pbd uzantılı dosyanın işlevinden ve nasıl kullanıldığında söz edilmeyecektir.
Yukarıda söylendiği gibi Visual Studio ile hazırladığınız C# projelerini çalıştırmak için Debug menüsünden Start Debugging komutunu verebilir veya direk F5 tuşuna basabilirsiniz. Aslında Debug menüsünden bu komutu vermekle Visual Studio’ya bir bakıma “hazırladığım projeyi entegre hata ayıklayıcısının nezaretinde test etmek istiyorum ve varsa hataları ayıklamak istiyorum” demiş oluyorsunuz. En başında belirtmek gerekir ki Start Debugging komutu verildiği zaman proje için otomatik olarak hazırlanan EXE dosya başkalarının kullanıp yararlanacağı kopya değildir. Elbette Start Debugging komutu sayesinde hazırlanıp projeye ait klasörün içinde yer alan “\Bin\Debug” klasörüne yerleştirilen EXE dosyayı alıp başka bilgisayarda çalıştırmak mümkündür. Ancak debug modunda iken hazırlanan EXE dosyanın dağıtılması önerilmiyor.
Şimdi gelelim şu Debug moduna. Programcılar genelde uygulamalarını geliştirmeyi ve test etmeyi Debug modunda yaparlar. Ne zaman ki uygulama tamamlanıp testlerden geçer o zaman Release sürümü hazırlayıp kullanıcılara öyle verirler. Yukarıdaki sayfalarda işaret edildiği gibi Visual Studio ile yeni C# projesi hazırlanıp kaydedildiği zaman projeye ait klasörün içinde “Bin” ve “Obj” adında 2 klasör hazırlanmaktadır. Derleme sırasında hazırlanan geçici dosyalar “Obj” klasörüne konulmaktadır. Obj klasörünün altında “Debug” ve “Release” adında 2 klasör hazırlanmaktadır. Aynı şekilde “Bin” klasöründe “Debug” ve Release” adında 2 klasör hazırlanmaktadır. Uygulamanın Debug sürümü Bin klasörünün içinde bulunan \Bin\Debug klasörüne konulurken Release sürümü \Bin\Release klasörüne yerleştirilmektedir.
Debug modunda iken proje Debug menüsündeki Start Debugging komutu ile çalıştırıldığında hem “Obj” klasörünün altındaki Debug klasörüne hem de “Bin” klasörünün altındaki “Debug” klasöründe bazı dosyalar oluşmaktadır. Üzerinde çalıştığınız projeyi Ctrl+F5 tuşları ile çalıştırırsanız projeyi Debugger’dan bağımsız çalıştırmış olursunuz.
Debug modundan Release moduna geçmek istiyorsanız Visual Studio’nun Debug menüsünden komut verip Configuration Manager diyalog kutusunu ekrana getirmelisiniz. Bu komut Debug menüsünde yer almıyorsa Tools menüsündeki Options komutu ile ekrana getirilen diyalog kutusunda ayarlama yapmalısınız.
Bu ekran görüntüsünü aldığım sırada üzerinde çalıştığım projenin dahil olduğu Solution bir tek projeye sahipti. Bu sırada Solution’da birden fazla proje olsaydı o projeler de listelenirdi. Bu diyalog kutusundaki Active Solution configuration ve Active solution platform liste kutularında yapılan seçimlerden Solution’daki bütün projeler etkilenmektedir.
“Active Solution configuration” liste kutusunu açıp Release’i seçerseniz Debug modundan Release moduna geçmiş olursunuz. Bu andan itibaren Debug menüsünden Start Debugging komutunu verip projeyi çalıştırırsanız EXE kodun Release sürümü hazırlanır ve “\Bin\Release” klasörüne yerleştirilir.
Hataları Yakalamak
Visual Studio ile C# uygulaması geliştirilirken konu hatalar olduğunda akla hemen iki kavram gelmektedir: Hata Ayıklamak ve Hata Yakalamak. Visual Studio ile birlikte gelen entegre hata ayıklayıcı(Debugger) sayesinde ilk bakışta görülmeyen hataları ayıklayabilmektesiniz. Bu bölümün konusu çalışma anında gelişen şartlara bağlı olarak meydana gelen hataları yakalamaktır.
Bir önceki bölümde bütün ayrıntıları ile olmasa bile hata ayıklama ve entegre debugger hakkında bilgi verildi. Hata ayıklama ve Debug işlemi hakkında anlatılanlar daha çok programcının işini kolaylaştırmaya yönelik işlemlerdi. Programcılıkta hata denildiği zaman asıl çalışma zamanında kullanıcının başrolde olduğu hatalar akla gelmektedir. Örneğin kullanıcıdan doğum tarihinin günü istenir ve gün bilgisi olarak kullanıcı 31’den büyük bir değeri girerse programcı herhangi bir tedbir almadıysa hata meydana gelir ve projenin çalışması kırılır. İşte bundan sonraki sayfalarda çalışma anında karşılaşılması muhtemel olan hataları nasıl yakalayabileceğinizden adım adım söz edilecektir.
C# projelerinde hata yakalama try-catch-finally blokları ile yapılmaktadır. Hata yakalama işlemlerinin nasıl yapıldığını örnek bir olay üzerinde anlatmak istiyorum. Kullanıcıdan bir işlem için tarih istediğinizi varsayalım. TextBox’a tarih olarak değerlendirilemeyecek bilgi girilirse programın çalışması kırılabilir. Aşağıda verilen metot işletildiğinde TextBox’ın içeriği dönüştürülüp DateTime tipindeki değişkene aktarılır.
private void textBox1_Leave(object sender, EventArgs e) { DateTime Tarih; Tarih = Convert.ToDateTime(textBox1.Text); }
Yerli yabancı yazarlar hata yakalamanın nasıl yapıldığını anlatmak istediklerinde genellikle kullanıcıdan DateTime tipinde bilgi isterler. Benim için tehlike asıl burada başlıyor: Şimdi birisi kalkıp “hata yakalama işlemini anlatırken kullanıcıdan tarih istemeyi senden önce akıl ettim, bu fikrin patenti bende derse” ne yaparım, kendimi nasıl savunurum? Tabii buna bir de değişken adı benzerliği eklenirse yandım demektir. Şimdi izninizle yukarıda verdiğim kodu biraz değiştireceğim ve “Tarih” yerine farklı ada sahip değişken tanımlayabildiğimi kanıtlamaya çalışacağım. Tabii burada benim için ikinci bir tehlike daha var: Ya benden önce yazarın birisi kitap veya makale yazıp DateTime tipindeki değişkene aktaracak bilgiyi kullanıcıdan TextBox aracılığı ile istemişse? Yani anlayacağınız programcılık üzerine yazmak zor iş.
private void textBox1_Leave(object sender, EventArgs e) { DateTime History; History = Convert.ToDateTime(textBox1.Text); }
Kullanıcı TextBox’a tarih veya zaman olarak değerlendirilebilinecek bilgi girdiği sürece bu kod hatasızca çalışır. Ne zamanki kullanıcı DateTime tipine dönüştürülemeyecek bilgiyi TextBox’a girip başka bir nesnenin üzerine giderse bu kod hata verir ve programın çalışması kırılır.
Burada yapılması gereken şudur: Hataya neden olma ihtimali yüksek olan bu satırı try bloğuna almak ve meydana gelebilecek hataları catch bloğunda yakalamaktır. Bu amaçla yukarıda verdiğim kodu aşağıdaki gibi düzenledim.
private void textBox1_Leave(object sender, EventArgs e) { DateTime History; try { History = Convert.ToDateTime(textBox1.Text); } catch { MessageBox.Show("Girdiğiniz tarih veya saat yanlış"); textBox1.Focus(); } } Bu kodda hataya neden olabilme ihtimali olan satırı try bloğuna aldım. Ardından catch bloğunda tahmin ettiğim hata meydana geldiğinde yapılacak işlemler için hazırlık yaptım. catch bloğunda kullandığım Focus() metodu sayesinde kontrol tekrar formdaki ilk TextBox’a geçer.
2. veya 3. denemede kullanıcı TextBox’a uygun bilgi girerse hata meydana gelmez ve dolayısıyla catch bloğuna yazılan satırlar işletilmez. catch-try bloklarının çalışma şeklindeki ayrıntıları ortaya çıkarmak için Convert sınıfının ToDateTime() metodu ile string bilginin DateTime tipine çevrildiği satırı ayrı bir metot olarak düzenledim(“Tarih” adına sahip olmayan değişken tanımlayabilme becerisine sahip olduğumu bu şekilde kanıtladıktan sonra yine Türkçe değişken adına dönelim).
private void textBox1_Leave(object sender, EventArgs e) { DateTime Tarih; try { Tarih = tarih_yap(textBox1.Text); } catch { MessageBox.Show("Girdiğiniz tarih veya saat yanlış"); textBox1.Focus(); } } public DateTime tarih_yap(string str) { return Convert.ToDateTime(str); }
Her ne kadar Convert sınıfının ToDateTime() metodu ile string bilgiyi DateTime tipinde bilgiye dönüştüren satır ayrı bir metodun içinde yer alsa bile “tarih_yap()” adını verdiğim bu metot try bloğunun içinden çağrılıp işletildiği için bu metotta meydana gelecek hatalar yine yakalanır. Başka bir deyişle try bloğunun içinden çağrılan tarih_yap() metodunda herhangi bir hata meydana gelmesi halinde programın işletimi yine catch bloğuna geçer.
Bundan şu sonuç çıkarılabilir: C# uygulamalarının başlangıç noktası Main() metodu içinde try-catch bloğu hazırlanırsa, başka bir deyişle uygulamayı başlatan Run() metodu try bloğuna yazılırsa uygulamanın neresinde hata meydana gelirse gelsin bu hata yakalanır. Eklemek gerekirse, C# uygulamaları Main() metodundan itibaren çalışmaya başlar ve Main() metodunun sonuna gelindiğinde ise uygulamanın çalışması sona ermiş olur.
static void Main() { try { Application.Run(new Form1()); } catch { MessageBox.Show("Herhangi bir yerde hata meydana geldi"); } }
try-catch-finally blokları ile ilgili olarak hemen söylenmesi gereken birkaç kural daha var. Örneğin try bloğundan hemen sonra ya catch ya da finally bloğu gelmelidir. Bu nedenle aşağıdaki gibi düzenlenen try-catch bloğu derleme sırasında hataya neden olur. Çünkü bu kodda try ile catch blokları arasında bir satır bulunmaktadır.
private void textBox1_Leave(object sender, EventArgs e) { DateTime Tarih; try { Tarih = Convert.ToDateTime(textBox1.Text); } this.Text = "Hata yakalama blokları"; catch { MessageBox.Show("Girdiğiniz tarih veya saat yanlış"); textBox1.Focus(); } }
try-catch-finally blokları ancak bir metodun içinde yer alabilirler. Çünkü C# derleyicisi ancak metotlar şeklinde düzenlenmiş kod parçalarını işletebilmektedir. Bu nedenle try bloğu içinde metot kodlanmak istenirse derleme sırasında hata meydana gelir.
Hata yakalama konusuna bu şekilde giriş yaptıktan sonra şimdi biraz işin geri planı üzerinde duralım. Bildiğiniz gibi programcılar genellikle uygulamalarını Debug modunda hazırlayıp entegre hata ayıklayıcısının desteği ile hatalardan ayıklarlar. Ayrıca çalışma anında karşılaşılan kullanıcı kaynaklı hatalarından korunmak veya içinde bulunulan şartlardan dolayı meydana gelmesi muhtemel hataları yakalayabilmek için try-catch blokları hazırlarlar. Daha sonra uygulamalarının Release sürümlerini hazırlayıp kullanıcıların istifadesine sunarlar.
Bu döngüyü gözetip yukarıda verdiğim koddaki try-catch deyimlerini sildim. Başka bir deyişle olası hataları yakalamak üzere yaptığım hazırlıkları iptal ettim. Devamında Build menüsünden Build komutunu verip üzerinde çalıştığım projeyi derledim. Daha önceki konulardan bildiğiniz gibi Build menüsünden komut verilip üzerinde çalışılan proje derlendiğinde Debug modunda iken hazırlanan EXE dosya “\bin\debug”, Release modunda iken derleme yapıldığında ise EXE dosya “\bin\release” klasörüne konulmaktadır. Derleme işlemini yaparken Debug modunda olduğum için derleme sonucu hazırlanan EXE dosya aşağıda işaret edildiği üzere “\bin\debug” klasörüne yerleştirildi.
Bu klasördeki EXE dosya çift tıklanıp proje çalıştırılıp TextBox’a girilen bilginin tarihsel bilgiye dönüştürülmesi sırasında hata meydana gelmesi sağlanırsa ekrana aşağıda verdiğim diyalog kutusu gelir. Bu diyalog kutusundaki Continue düğmesini tıklayıp hatanın atlanılıp göz ardı edilmesini sağlayabilir veya Quit düğmesini tıklayıp uygulamanın çalışmasını sona erdirebilirsiniz. Meydana gelen hata hakkında ayrıntılı bilgi edinilmek istendiğinde ise Details düğmesi tıklanır.
Tahmin edeceğiniz gibi bu proje Debug menüsündeki Start Debugging komutu ile çalıştırılsaydı bu hata ile karşılaşıldığında projenin çalışması kırılırdı ve hataya neden olan satır işaret edilirdi. Çünkü Debugger’ın başlangıç ayarlarında böyle bir hata ile karşılaşıldığı zaman uygulamanın çalışması sona erdirilmektedir. Yukarıda ise uygulamayı entegre debugger’dan bağımsız çalıştırdığım için uygulamanın çalışması kırılmadı. Eğer yukarıda yaptığım gibi çalışma anında meydana gelmesi muhtemel bu hatayı yakalamak için try-catch bloğu hazırlasaydım projeyi ister debug modunda, ister Visual Studio kurulu olmadığı başka bir bilgisayarda çalıştırmış olsaydım değişen bir şey olmazdı.
Şimdi hata yakalama konusunda 2. bir örnek vereceğim. Hazırlayacağım örnekte 5 elemanlı bir dizi değişken tanımlayıp for döngüsü ile dizi değişkenin elemanlarına bilgi aktaracağım. Dizi değişkene aktaracağım bilgileri forma yerleştirmiş olduğum ListBox’tan alacağım. Aşağıda verilen 4 satırlık kodda tanımladığım 3 değişken(“Sayi”, “Takimlar” ve “i”) C# programcıları ve yazarlar tarafından çok sık kullanılan değişken adlarıdır. “i”, “sayi” ve “takimlar” adlı değişkenleri başka kaynaklardan yararlanmadan tanımlayabildiğime bu konuda araştırma yapanları belki inandırabilirim(!). “Sayi” yerine “number”, “i” yerine “I” ve “takimlar” yerine “ekipler” adında değişkenler tanımlayabilirdim.
string[] takimlar = new string[5]; int adet = listBox1.Items.Count; for (int i=0; i<adet; i++) takimlar[i]=listBox1.Items[i].ToString();
ListBox, 5 veya daha az elemana sahip iken bu kod sorunsuz olarak çalışır. Ancak ListBox’ın eleman sayısı 5’ten fazla iken bu kod işletildiğinde programın çalışması kırılır ve aşağıdaki gibi bir hata mesajı alınır.
Tahmin edeceğiniz gibi uygulamanın kırılmasını sağlayan entegre hata ayıklayıcıdır. Eğer bu projeyi derleyip EXE dosyayı Visual Studio’dan yani Entegre hata ayıklayıcıdan bağımsız çalıştırsaydım uygulamanın çalışması kırılmayıp aşağıdaki gibi bir mesaj alınırdı.
Bu hatanın meydana gelme nedeni dizi değişkenin olmayan 6. elemanına bilgi aktarılmak istenmesidir. Yapılan tanımlamadan dolayı dizi değişken 5 elemana sahip olduğu için hata meydana geldi. Bu gibi hataların önüne geçip programın çalışmasının kırılmasını önlemek için aşağıdaki gibi try-catch blokları hazırlanabilir.
private void Form1_Click(object sender, EventArgs e) { string[] takimlar = new string[5]; int adet = listBox1.Items.Count; try { for (int i = 0; i < adet; i++) takimlar[i] = listBox1.Items[i].ToString(); } catch { MessageBox.Show("Dizi Değişkenin sınırı aşıldı\n"+ "Yalnızca ilk 5 eleman diziye aktarıldı"); } listBox2.Items.AddRange(takimlar); }
Bu kodda hata meydana geleceğini öngördüğüm satırları try bloğuna aldım. Bu satırlarda hata meydana gelmezse kod sorunsuz çalışır ve programın işletimi catch bloğundan sonraki ilk satıra geçer. Hata meydana gelirse hatanın meydana geldiği noktada işleme ara verilir ve catch bloğundaki satırlara geçilir.
Bu örnekte catch bloğunda kullanıcıya mesaj vermekle yetindim. Bu şekilde düzenlenen kodun programın çalışmasını kırmadığını göstermek için forma 2. bir ListBox yerleştirdim ve AddRange() metodu ile dizi değişkenin elemanlarını 2. ListBox’a aktardım.
Şimdi yukarıda hazırladığım catch bloğunda değişiklik yapacağım. Hazırlamış olduğum örnekte try bloğundaki satırlar nasıl bir hataya neden olurlarsa olsunlar, başka bir anlatımla nasıl bir hata meydana gelirse gelsin catch bloğu işletilir. Bu catch bloğundaki satırların yalnızca dizi değişkenlerle ilgili olarak indis sorunu yaşandığında işletilmesini isteseydim aşağıdaki gibi düzenleme yapardım.
try bloğundaki satırlarda çalışma anında dizi değişkenlerle ilgili bir hata meydana geldiğinde IndexOfRangeException sınıfının örneği hazırlanıp catch bloğuna gönderilir(aşağıda verdiğim kodda “Hata” yerine “Hataspor” adında değişken tanımlayıp kullandım. Birkaç satırdan meydana gelen bu kodda bilseydim ki Orhan babaya ayıp olmaz “hata” yerine “hatalarimla_sev_beni” adında bir değişken tanımlayıp kullanırdım.
private void Form1_Click(object sender, EventArgs e) { string[] takimlar = new string[5]; int adet = listBox1.Items.Count; try { for (int i = 0; i < adet; i++) takimlar[i] = listBox1.Items[i].ToString(); } catch(IndexOutOfRangeException Hata) { MessageBox.Show("Meydana gelen hatanın mesajı:\n"+ Hata.Message); } listBox2.Items.AddRange(takimlar); }
Bu örnekte sistem tarafından fırlatılan istisnanın veya hazırlanıp catch bloğuna gönderilen IndexOutOfRangeException tipindeki nesnenin Message özelliğinin içeriğini ekrana yazdım. Exception sınıfından türetilen IndexOutOfRangeException sınıfının Message özelliğinden başka Source, HelpLink, StackTrace, TargetSize ve InnerException gibi özellikleri de bulunmaktadır.
Bu kodu tekrar yorumlamak gerekirse; try ve catch blokları birlikte çalışmakta ve try bloğundaki herhangi bir satırda hata meydana geldiğinde meydana gelen hataya göre hemen bir Exception nesnesi hazırlanıp programın işletimi catch bloğuna geçmektedir.
Yukarıda verdiğim kodda catch bloğuna gönderilen exception nesnesinin IndexOutOfRangeException tipinde olup olmadığını araştırdım. Bu sırada catch bloğuna gönderilen Exception nesnesi IndexOutOfRangeException sınıfının örneği ise catch bloğuna yazılan satırlar işletilir.
try bloğunda meydana gelen hatadan dolayı catch bloğuna gönderilen bütün nesneler System’de bulunan Exception sınıfından türetilen sınıfların örnekleridir. Bu kodda dizi değişkenin olmayan bir elemanı üzerinde işlem yapılmak istendiği için catch bloğuna IndexOutOfRangeException tipinde bir nesne gönderilir. Konu üzerinde düşünmenizi sağlamak için bu makalenin ilk sayfalarında verdiğim örneği aşağıdaki gibi düzenledim.
private void textBox1_Leave(object sender, EventArgs e) { DateTime History; try { History = Convert.ToDateTime(textBox1.Text); } catch(IndexOutOfRangeException Hata) { MessageBox.Show("Girdiğiniz tarih veya saat yanlış"); textBox1.Focus(); } }
Bu şartlarda TextBox’a DateTime tipine dönüştürülemeyecek bilgi girilip TextBox’tan ayrılıp Leave olayı meydana getirildiğinde try bloğundaki satırlar hataya neden olur ve programın işletimi catch bloğunda geçer. Ne ki catch bloğunda dönüştürme işlemi sonucu meydana gelen hata yakalanamaz. Çünkü catch deyimine ait parantezin içinde IndexOutOfRangeException tipinde değişken tanımlandığı için catch bloğundaki satırlar işletilmeyip meydana gelen hatadan dolayı programın çalışması kırılır.
Şimdi ise hata yakalamanın nasıl yapıldığı anlatılırken ilk akla gelen sıfıra bölme hatasını yakalayan bir örnek vereceğim. Bu amaçla forma 3 TextBox ve 1 düğme yerleştirdim. İlk 2 TextBox’a bilgi girilip “Hesapla” adını verdiğim düğme tıklandığında ilk 2 TextBox’’ın içeriklerini Byte tipindeki değişkene aktarıp 1. sayıyı ikinciye bölüp çıkan sonucu 3. TextBox’a yazacağım. Bu işlemi yapacak kodu aşağıda verdim.
private void Hesapla_Click(object sender, EventArgs e) { byte Sayi1 = Convert.ToByte(textBox1.Text); byte Sayi2 = Convert.ToByte(textBox2.Text); float Sayi3 = Sayi1 / Sayi2; textBox3.Text = Sayi3.ToString(); }
İzninizle tam burada biraz durup bir şeyler eklemek istiyorum. Bu 4 satırlık kod ile ilgili olarak önce amacımın ne olduğunu ortaya koyacağım. Amacım net: Sıfıra bölme hatasını anlatacağım. Eğer ilkokul öğrencilerine sıfıra bölme hatasını anlatıyor olsaydım elime bir hesap makinesi alır ve herhangi bir sayıyı sıfıra bölerdim. Hesap makinesi sıfıra bölme işleminin sonucu olarak LCD ekranına yazdığı Error’u öğrencilere gösterip onları kolayca ikna ederdim. Madem konumuz programcılık ve kullanıcıların yapması muhtemel sıfıra bölme hatasını yakalayacağız; birbirine böleceğimiz 2 sayıyı kullanıcıdan isteriz. Ayrıca kullanıcıya mümkünse ikinci sayının yani bölen’in sıfır olmasını rica ederiz. Amacımız sıfıra bölme hatasını yakalamak olduğuna göre hata yapılmadan hatayı yakalama imkanımız olamayacağına göre kullanıcıdan 2 sayı isteyeceğiz. Asıl sorun ise kullanıcıdan sayıları isterken yaratıcı olabilmek veya kimsenin aklına gelmeyen bir tekniği kullanabilmektir.
Kullanıcıdan sayı isterken “bu sayıları şu TextBox’lara gir” dersek başkalarının fikrini çalmış olabiliriz. Ne yazık ki yıllardır bilgisayar ve programcılık üzerine yazdığı binlerce sayfada milyonlarca cümleyi kuran Memik YANIK bundan 4 yıl kadar C# üzerine yazarken boş bulunup birbirine böleceği 2 sayıyı kullanıcıdan isterken TextBox’lara girmesini istemişti. Tabii bununla kalmayıp kullanıcının TextBox’lara girdiği bilgileri dönüştürüp Sayi1, Sayi2 adında 2 değişkene aktarmıştı. Memik Yanık bu 2 değişkeni yıllar önce yazdığı kitaplarında kullanmış olsa bile bu değişkenleri namı hesabına kaydetmeyi, register etmeyi akıl etmemiş.
Başka makale ve kitap yazarlarının telif haklarına tecavüz etmemek için bu 4 satırlık kodu aşağıdaki gibi düzenledim. Bu kodda TextBox’lara girilecek sayılar byte tipindeki değişkenlere aktarılmaktadır. Formdaki ilk 2 TextBox’a 255’ten küçük bir sayı girilirse bu kod hatasızca çalışır. Ancak kullanıcı 2. TextBox’a 0 yazarsa sıfıra bölme hatası meydana gelir ve programın çalışması kırılır.
private void Hesapla_Click(object sender, EventArgs e) { byte Number1 = Convert.ToByte(textBox1.Text); byte Number2 = Convert.ToByte(textBox2.Text); float Number3 = Number1 / Number2; textBox3.Text = Number3.ToString(); }
Aynı şekilde TextBox’lara 255’ten büyük bir değer yazılırsa taşma(değişkenler byte tipinde olduğu için) meydana gelir ve programın çalışması kırılır. Hata meydana geldiği zaman programın çalışmasının kırılmasını engellemek için try-catch bloğu hazırladım. Değişkenlere Sayi1, Sayi2 ve Sayi3 gibi adlar vermenin mecburi olmadığını böylece kanıtladıktan sonra tekrar Türkçe değişken adlarına dönelim.
private void Hesapla_Click(object sender, EventArgs e) { byte Sayi1, Sayi2; float Sayi3; try { Sayi1 = Convert.ToByte(textBox1.Text); Sayi2 = Convert.ToByte(textBox2.Text); Sayi3 = Sayi1 / Sayi2; textBox3.Text = Sayi3.ToString(); } catch { MessageBox.Show("Hata meydana geldi"); textBox1.Focus(); } }
Ek açıklama yapmak gerekirse bu koddaki try bloğundaki satırlar işletilirken ister taşma hatası ister sıfıra bölme hatası meydana gelsin her şartta kullanıcıya catch bloğundaki mesaj verilip tekrar ilk TextBox’ın üzerine gidilir. Konu üzerinde düşünmenizi sağlamak için bu kodun catch bloğunu aşağıdaki gibi düzenledim. C# programlama dilinin ne “Error” ne de “error” adında bir anahtar kelimesi olmadığı için bu kodda yakalanacak Exception nesnesinin aktarılacağı değişkenin adını “Hata” olarak seçmek yerine “error” adını kullanmanız önerilir. Elin gavurları gelip bu değişken bana aittir demeyeceğine göre sorun yaşamazsınız.
catch(Exception Hata) { object Tip = Hata.GetType(); MessageBox.Show("Hata meydana geldi\n Meydana gelen Hata:"+ Tip.ToString()); textBox1.Focus(); }
Burada yapılan şudur: catch anahtar kelimesine ait parantezlerin içinde Exception tipinde bir değişken tanımladım. catch deyimi, try bloğundan gönderilen nesnenin tipinin parantezlerin içinde tanımlanan değişkenle aynı tipte olup olmadığına bakar. Gönderilen Exception nesnesi System.Exception tipinde ise catch bloğundaki satırlar işletilir. Hata nedeniyle try bloğundan catch bloğunda gönderilen exception nesnesinin tipi ister IndexOutOfRangeException olsun ister OverflowException her şartta catch bloğundaki satırlar işletilir. Çünkü IndexOutOfRangeException ve OverflowException sınıfları System.Exception sınıfından türetilmiş sınıflardır.
Bu koda önce object tipinde bir değişken tanımlayıp catch bloğuna gönderilen nesnenin tipini GetType() metodu ile öğrendim. Catch bloğundaki satırların işletilmesinin nedeni sıfıra bölme iken aşağıdaki gibi bir mesaj alınır.
Gördüğünüz gibi bu sırada meydana gelen hata sıfıra bölme işlemi kaynaklı olduğu için catch bloğuna DivideByZeroException sınıfının örneği gönderilmiş. TextBox’lardan birisine Convert sınıfının ToByte() metodu ile byte tipine dönüştürülemeyecek bilgi girip ondan sonra yukarıda verdiğim kodu işletseydim aşağıdaki gibi hata mesajı alırdım.
Entegre Hata Ayıklayıcısı ve Exception Sınıfları
Yukarıda sözü edilen sınıflardan başka .NET Framework ile birlikte OverflowException, InvalidCastException, FormatException, ArgumentException, ArithmeticException, OutOfMemoryException, ArrayTypeMismatchException NullReferansException ve MemberAccessException gibi sınıflar da gelmektedir.
En çok kullanılan hata yakalama sınıflarından böylece biraz söz ettikten sonra mevcut Exception sınıflarıyla entegre hata ayıklayıcının ilişkisinden biraz söz edelim. Bu amaçla Debug menüsünden komut verip ekrana Exceptions diyalog kutusunu getirdim.
Bu diyalog kutusunda C# uygulamaları dahilinde yararlanabileceğiniz Exception sınıfları ile entegre hata ayıklayıcısı arasındaki ilişki ayarlanmaktadır. Bu konuda bilgi vermek için forma bir TextBox ve düğme yerleştirip ”islem” adını verdiğim düğme için aşağıda verdiğim kodu hazırladım.
private void islem_Click(object sender, EventArgs e) { byte sayi; try { sayi = Convert.ToByte(textBox1.Text); } catch { MessageBox.Show("Hata meydana geldi"); } }
Tahmin edeceğiniz gibi Debug modunda iken uygulama Start Debugging komutu ile çalıştırılıp formdaki ilk TextBox’a 255’ten büyük bir değer girilmesi halinde hata meydana gelir ve OverflowException sınıfının örneği hazırlanıp catch bloğuna gönderilir.
Şimdi öyle bir ayarlama yapacağım ki uygulama Debug modunda iken Start Debuging komutu ile çalıştırıldığında catch bloğundaki satırların işletilmesi yerine uygulamanın çalışmasının kırılmasını sağlayacağım. Bu amaçla Exceptions diyalog kutusunda önce Common Language Runtime Exceptions adlı exception sınıfı grubuna ait namespace’lerin listelenmesini sağladım. Ardından System’de yer alan OverflowException sınıfını buldum.
Verilen ekran görüntüsünde tespit edebileceğiniz gibi Thrown özelliği pasif iken User-unchandled özelliği aktif durumdadır. Başka bir deyişle kodun herhangi bir yerinde meydana gelebilecek ve OverflowException sınıfının fırlatılmasına neden olan hata yakalanmazsa programın çalışması kırılır. Yukarıda verdiğim kodda TextBox’a 255’ten büyük bir değer girilirse, dolayısıyla byte tipindeki değişkene 255’ten büyük bir sayı aktarılmak istenirse meydana gelebilecek hata yakalandığı için uygulamanın çalışması kırılmaz. Başka bir deyişle mevcut ayarlarda byte değişkene 255’ten büyük bir değer aktarılmak istenirse ve OverflowException hatasını yakalayan bir catch bloğu yoksa uygulamanın çalışması kırılır. Benzer şekilde .NET Framework ile gelen sınıfların hemen hepsi için User-unhanded sütunundaki onay kutusu seçili durumda olduğundan yakalanmayan bütün hatalar uygulama Debug modunda iken çalışmanın kırılmasına neden olurlar.
Bu diyalog kutusunda System’de yer alan OverflowException sınıfının User-unchandled özelliğini aktif durumda iken ayrıca Thrown özelliğini aktif duruma getirdikten sonra OK düğmesi ile bu diyalog kutusunu kapattım. Ardından yukarıda verdiğim kodu aşağıdaki gibi düzenledim. Değişken adı benzerliği olmasın diye “sayi” yerine “number” adını kullandım.
Önce bu kodu biraz yorumlayalım: TextBox’a 255’ten büyük bir değer girilip bu kod işletilirse ToByte() metodunun kullanıldığı satır hataya neden olur ve catch bloğuna OverflowException sınıfının örneği gönderilir. TextBox’a 100 sayısı girilip bu kod işletildiğinde ise try bloğundaki karşılaştırma doğru değerini vereceği için catch bloğuna yine OverflowException hata sınıfının örneği gönderilir. Tekrar etmek gerekirse ister TextBox’a 100 ister 255’ten büyük bir sayı yazılsın OverflowException sınıfının örneği catch bloğuna fırlatılır.
Bu şartlarda yani Exceptions diyalog kutusunda OverflowException sınıfının Thrown özelliği seçili durumda iken Debug modundaki uygulama Start Debugging komutu ile çalıştırıldığında TextBox’a ister 100 girilsin ister 255’ten büyük sayı girilsin uygulamanın çalışması kırılır. Çünkü Exception diyalog kutusunda yaptığım ayarlamanın anlamı şudur: Her ne nedenden olursa olsun ister direk Throw deyimi ile, ister yapılmak istenen işlem OverflowException hatasını üretirse uygulamanın çalışması kırılır.
Tabi uygulamayı Build menüsündeki komutlarla derleyip sonra klasik yöntemlerle(yani EXE dosyanın olduğu klasöre gidip EXE dosyayı çift tıklayarak) Visual Studio’dan bağımsız olarak çalıştırırsanız TextBox’a ister 255’ten büyük sayı girilsin ister 100 girilsin catch bloğundaki satırlar işletilir ve uygulamanın çalışması kırılmaz ve catch bloğu işletilerek hata yakalanır.
Finally Bloğu
Bazen herhangi bir nedenden dolayı yarı yolda vazgeçilen bir işlemden dolayı geride bazı kalıntılar kalır. Bu gibi durumlar kaynak israfına neden olur. Bu konuda bilgi vermek için aşağıda verdiğim ancak pratik değeri olmayan uygulamayı hazırladım.
Kullanıcı bir resmin dosyasının içeriğini görmek istediğinde “Resim Seç” düğmesini tıklayacak. Bu düğme tıklandığında Aç diyalog kutusu ekrana getirilecek ve istediği resim dosyasını seçecek. Bu işlemler için aşağıda verdiğim kodu hazırladım. Kullanıcı Aç diyalog kutusunda PictureBox’ta görüntülenebilecek uygun bir resim dosyasını seçtiği sürece bu kod hatasızca çalışır.
1994 yılında yayınlanan Clipper kitabımın birçok sayfasında “DosyaAdi” adında değişken tanımlamışım. 2004 yılında yayınlanan C# kitabımda ise “dosya” adında değişkenler tanımlamışım. Doğrusunu söylemek gerekirse kısacak kodları hazırlarken değişkenin adı şu olsun bu olmasın diye kaygım yıllardır olmadı. Değişkene ad seçerken genellikle değişkene aktaracağım bilgiyi göre seçim yaparım. Örneğin tanımladığım değişkene Fenerbahçe bilgisini aktarmayı düşünüyorsam değişkene genellikle “takim” adını veririm. Yıllar önce yani 1990’lı yılların başında programcılık kitabı yazarlarının ve hocaların arasında “dosya” yerine “kutuk” adında değişken tanımlama alışkanlığı yaygındı. Sonra “kutuk” unutuldu ve “dosya” diyenler çoğaldı. Gerçekte programcılık sitelerindeki makalelerde ve kitaplarda “File” karşılığı “dosya” adında değişken tanımlama alışkanlığı çok yaygındır.
private void Resim_sec_Click(object sender, EventArgs e) { openFileDialog1.ShowDialog(); string kutuk = openFileDialog1.FileName; Bitmap Resim = new System.Drawing.Bitmap(kutuk); pictureBox1.Image = (Bitmap)Resim; Resim = null; }
Ancak kullanıcı OpenFileDialog nesnesi sayesinde ekrana getirilen diyalog kutusunu dosya seçmeden kapatır veya uygun olmayan bir dosyayı seçerse hata meydana gelir. Kullanıcının yanlış yaptığı veya yapmadığı dosya seçiminden dolayı meydana gelecek hata konusunda kendisine bilgi vermek için kodu aşağıdaki gibi düzenledim.
private void Resim_sec_Click(object sender, EventArgs e) { System.Drawing.Bitmap Resim; openFileDialog1.ShowDialog(); string kutuk = openFileDialog1.FileName; try { Resim = new System.Drawing.Bitmap(kutuk); pictureBox1.Image = (Bitmap)Resim; Resim = null; } catch { MessageBox.Show("Uygun dosya seçmediniz"); } }
Kullanıcı “Aç” diyalog kutusunda resim dosyası seçmezse veya uygun olmayan bir dosyayı seçerse catch bloğundaki satır işletilir ve kullanıcıya bu konuda bilgi verilir. Ancak bu |