2. Temel
Temelde «İstisnalar» ikiye ayrılır.
• Senkron İstisnalar
• .Net Framework’ün verdiği istisnalar: InvalidArgumentException,
ArgumentIsNullException, FormatException… etc.
• Kendi uygulamanızı verdiği istisnalar: Exception sınıfından üretilmiş size ait istisnalar
• Asenkron İstisnalar: Genellikle asenkron istisnalar kendi içinde ikiye ayrılır.
• Sistem tarafından verilen ve hiçbir zaman yakalanmaması gereken hatalar:
MemoryOutOfException, StackoverflowException…
• Altyapı tarafından asenkron olarak fırlatılan hatalar. (Genellikle üçüncü parti uygulamalar
ve/veya native kütüphaneler tarafından)
3. İstisnalarıYakalamak
Kural #1: Senkron istisnalarıYAKALAYABİLİRSİNİZ(*), asenkron hataları
YAKALAMAMALISINIZ(**).
(*) Size kalmış.Yakalamamanız uygulamanızı sonlanmasına sebep olabilir. Bu
durumda yakalamak isteyebilirsiniz. Ancak bir web servis gibi bir ortamda iseniz
yaptığınız hatalar karşı tarafa aktarılacak, kullanıcı bir sorun olduğunu içeriği ile
anlayacak ve uygulamanız sonlanmayacaktır. Çünkü yakalanmayan her bir istisna
kendi çalıştığıThread’i sonlandırmaya çalışacaktır. Eğer bu MainThread ise
uygulamanız sonlanır.
(**) Genellikle asenkron hataları yakaladığınız zaman yapabileceğiniz bir şey yoktur.
Bu da bizi ileride anlatacağımız ikinci kurala getiriyor.
4. «İstisna» KavramınınVarolma Sebebi
İstisnalar, uygulama içinde tahmin edilemeyen/beklenmeyen durumları kod
geliştiriciye/kullanıcıya haber veren yapılardır. Sebebini anlamak daha iyi kod
geliştirmenize yardımcı olur.
Günümüz bilgisayarlarında istisnalar donanımsal olarak desteklenir. Onları
kullanmanın size avantajı olacağı gibi yanlış bir yaklaşımla sisteminizi olabildiğince
yavaşlatırlar.
«İstisna» ‘ların oluşmasındaki en büyük motivasyon «Call Stack» içerisinde oluşan bir
hatanın en tepede çağırılan method tarafından yakalanabilmesini sağlamak ve ek
bilgileri edinebilmektir.
5. Call Stack
• Uygulamanız herhangi bir seviyede hata verebilir. İstisna’ları size sağladığı
en tepeden bu hataları yakalayabilme şansıdır. Aşağıdaki örnekte
GetEntityInfo() metodu içinde oluşacak bir hata kendisini çağıran bütün
metotlar tarafından yakalanabilir.
• Hangi seviyede yakalayacağınız size kalmış.
• Burada sizin üzerinize düşen mimarinizi buna uygun kurmaktır.
• Nasıl?
6. İstisnalara Karşı Zeka
Önceki slide içerisinde istisnaların kendisini çağıran bütün metotlar tarafından
yakalanabileceğini gördük. İstisnayı hangi seviyede yakalamak istediğiniz,
olayı hangi seviyede çözeceğinizi belirleyecektir.
Ancak bazı durumlar bu kadar derin düşünmeyi ve olayları karışık hale
getirmeyi gerektirmez.
7. İstisnalara Karşı Zeka
Yukarıdaki örnekte açıkça gözüküyor ki bir zaten kapalı olan bir bağlantıyı tekrar
kapatmamak istisnai durumları önlüyor. Ayrıca gereksiz kod yazılmasının önüne
geçiyor. Peki ya böyle bir durumdan haberdar değilsek? Kapalı olan bir bağlantıyı
tekrar kapatmak hata verebilir veya vermeyebilir. Bu tamamen kullandığınız sınıfların
davranışına bağlı. Eğer kullandığınız sınıflar ile ilgili yeterli bilginiz yoksa nasıl olacak?
(Genelde %99 böyledir, çünkü kodlarına sahip olmuyoruz veya yardım dokümanlarını
okumuyoruz.)
if (conn.State != ConnectionState.Closed)
{
conn.Close();
}
9. İstisnalara Karşı Zeka
Yukarıdakilere benzer kod örnekleri yapabileceğiniz en büyük hatalardan biridir. Yukarıdaki örnekte hiçbir zaman bir hata
olduğunu bilemeyeceksiniz. Çağırılan metodun işin ne kadarını yaptığı veya hangi seviyede hata verdiğini bilemediğiniz gibi,
uygulamanız unstable konuma da geçmiş olabilir. Diğer metotların da artık sağlıklı sonuç döndürdüğünden emin olamazsınız.
Bir hata olduğunu öğrendiğinizde her şey çok geç olabilir. Bütün bir veritabanını veya bir sistemi bozmuş olabilirsiniz. Veya
hiçbir çözüm üretemeyeceğiniz OutOfMemoryException gibi bir hatayı da yakalamış olabilirsiniz.
Public void Method1()
{
try
{
doSomething();
}
catch
{ }
}
10. İstisnalara Karşı Zeka
Test aşaması uygulamanın en önemli aşamasıdır.
Eğer kullandığınız kütüphane ile ilgili bilgiye sahipseniz soldaki yaklaşımı uygulayabilirsiniz.
Eğer bilginiz yok ise, sağdaki yaklaşımı uygulayın.
Test aşamasında uygulama hata verecektir. Hatayı inceleyip, gerekli kod düzeltmelerini yapın.
Ancak hiçbir zaman bütün hataları yakalama yoluna gitmeyin. Her bir istisna sınıfı için ayrı kod
yazdın.
Günümüz dokümanları içinde metotların vereceği hataların listesi de bulunuyor.
if (conn.State != ConnectionState.Closed)
{
conn.Close();
}
conn.Close();
11. İstisnalara Karşı Şablonlar (Patterns)
Önceki örneklerden de gördüğünüz gibi her şeyin çözümü istisnaları kullanmak
değil. .Net bize bazı konularda basit çözümler de getiriyor.TryXXX şablonları
.Net 3.5 ile geldi.
Tam olarak bir sınıf şablonu olmasa da istisnaların devreye girmeden durumları
yönetebilmemiz için iyi bir çözümdür.
int k;
string t = GetValue();
if (!int.TryParse(t, out k))
{
// handle the situation
}
int k;
string t = GetValue();
try
{
k = int.Parse(t);
}
catch(FormatException)
{
// Handle situation
}
12. İstisnalar ve Kullanımı
class Program
{
static int GetGlobalParameter()
{
// Do some random calculation
Random r = new Random();
int p = r.Next(-10, 10);
// Do dome random calculation
if (p < 0)
{
p = 0;
}
else
{
p = p + 10;
}
return p;
}
static int GetX()
{
int p = GetGlobalParameter();
int X = 100 / p;
return X;
}
static int GetY()
{
int p = GetGlobalParameter();
int Y = 100 / p;
return Y;
}
static int Calculate()
{
int x = GetX();
int y = GetY();
return x * y;
}
static void Main(string[] args)
{
int value = Calculate();
Console.WriteLine(value);
}
}
Soldaki uygulamaGetX() ve GetY()
metotları içinde DivideByZeroException
verme eğilimindedir.
Bu kod bloğu üzerinde nasıl hata yönetimi
yapabiliriz?
Bu sorunun cevabı sorunu hangi seviyede
çözmek istediğimize göre değişir. İşte
birkaç örnek:
13. İstisnalar ve Kullanımı
class Program
{
static int GetGlobalParameter()
{
// Do some random calculation
Random r = new Random();
int p = r.Next(-10, 10);
// Do dome random calculation
if (p < 0)
{
p = 0;
}
else
{
p = p + 10;
}
return p;
}
static int GetX()
{
int p = GetGlobalParameter();
int X = 100 / p;
return X;
}
static int GetY()
{
int p = GetGlobalParameter();
int Y = 100 / p;
return Y;
}
static int Calculate()
{
int x = GetX();
int y = GetY();
return x * y;
}
static void Main(string[] args)
{
int value = 0;
try
{
value = Calculate();
}
catch (Exception exception)
{
Console.WriteLine(exception.Message);
return;
}
Console.WriteLine(value);
}
En basit yaklaşım Main() metodu içine bir
try-catch yazmak mı ????
Önceki ekranlarda anlatıldığı üzere bu bize
sadece uygulamamızın herhangi bir hata
yüzünden durmamasını sağlar. Bize hatanın
ne olduğunu söylemez. Sadece hangi
seviyede ne olduğunu anlatır.
Bu sorunu çözebilecek tek kişi uygulamayı
yazan kişidir. Çözebilmesinin tek yolu da
aynı parametreler ile uygulamayı tekrar
çalıştırabilmesinden geçer.
14. İstisnalar ve Kullanımı
class Program
{
static int GetGlobalParameter()
{
// Do some random calculation
Random r = new Random();
int p = r.Next(-10, 10);
// Do dome random calculation
if (p < 0)
{
p = 0;
}
else
{
p = p + 10;
}
return p;
}
static int GetX()
{
int p = GetGlobalParameter();
int X = 100 / p;
return X;
}
static int GetY()
{
int p = GetGlobalParameter();
int Y = 100 / p;
return Y;
}
static int Calculate()
{
int x = 0;
int y = 0;
try
{
x = GetX();
y = GetY();
}
catch(DivideByZeroException exception)
{
Console.WriteLine("GetX() veya GetY() içinde sıfıra bölme hatası");
return 0;
}
return x * y;
}
static void Main(string[] args)
{
int value = Calculate();
Console.WriteLine(value);
}
}
Bir seviye daha detaya girelim.
Main() metodu Calculate() metodunu çağırıyor.
Calculate() metodu ise GetX() ve GetY() metodlarını
çağırıyor.
Calculate() içerisine hata kontrolü koysak ne olur?
Bu kod yönetilebilir hatalara iyi bir örnek olabilir. Eğer
sıfıra bölme hatası alınıyorsa kullanıcıya bunu uyaran
bir hata mesajı ve geri dönüş değeri SIFIR verilir.
Elbette Main() bloğu içinde de buna uygun kod
yazılması gerekir.
Eğer Calculate() metodunun geri dönüş değeri SIFIR ise
bir hata var. Ama çağıran kişi bu hatanın ne olduğunu
programatik olarak bilmiyor. Sadece bir hata olduğunu
ve gerekli detayın ekrana yazıldığını biliyor.
Sadece Calculate() metodunun bulunduğu bir
kütüphane yazıyorsanız iyi bir yaklaşım olabilir.Ancak
gene de başarılı ve yeterli değil. (Örnek: Windows API)
15. İstisnalar ve Kullanımı
class Program
{
static int GetGlobalParameter()
{
// Do some random calculation
Random r = new Random();
int p = r.Next(-10, 10);
// Do dome random calculation
if (p < 0)
{
p = 0;
}
else
{
p = p + 10;
}
return p;
}
static int GetX()
{
int p = GetGlobalParameter();
if (p == 0) throw new InvalidOperationException("GetGlobalParameter() metodu
SIFIR değer döndürdü. Olası sebebi metodun içinde hatalı işlem yapılmış olması");
int X = 100 / p;
return X;
}
static int GetY()
{
int p = GetGlobalParameter();
if (p == 0) throw new InvalidOperationException("GetGlobalParameter() metodu
SIFIR değer döndürdü. Olası sebebi metodun içinde hatalı işlem yapılmış olması");
int Y = 100 / p;
return Y;
}
static int Calculate()
{
int x = GetX();
int y = GetY();
return x * y;
}
static void Main(string[] args)
{
int value = Calculate();
Console.WriteLine(value);
}
Aynı şekilde GetX() ve GetY() içerisine de hata
kontrolü konulabilir. Ancak buradaki yaklaşım
çok başarılı olmayacaktır. Çünkü hataya sebep
olan veri GetGlobalParameter() adlı metottan
geliyor.
Eğer olması gereken, GetGlobalParameter()
metodunun hiçbir zaman SIFIR döndürmemesi
ise, bu yönetmememiz gereken hatalara iyi bir
örnek olabilir. GetGlobalParameter() metodu
başkasına ait bir kütüphane içinde veya
kullanıcının ekrandan giriş yaptığı değerlerden
üretiliyorsa olası hatalara karşı kod yazmamız
gerekir.
Biraz daha iyileştirelim.
16. İstisnalar ve Kullanımı
class Program
{
static int GetGlobalParameter()
{
// Do some random calculation
Random r = new Random();
int p = r.Next(-10, 10);
// Do dome random calculation
if (p < 0)
{
throw new InvalidOperationException("GetGlobalParameter()
metodu SIFIR değer üretti. Kullanıcı hatası veya dış parametrelerden kaynaklı
olabilir.");
}
else
{
p = p + 10;
}
return p;
}
static int GetX()
{
int p = GetGlobalParameter();
int X = 100 / p;
return X;
}
static int GetY()
{
int p = GetGlobalParameter();
int Y = 100 / p;
return Y;
}
static int Calculate()
{
int x = GetX();
int y = GetY();
return x * y;
}
static void Main(string[] args)
{
int value = Calculate();
Console.WriteLine(value);
}
}
Görüldüğü gibi en alt seviyeden en üst
seviyeye kadar farklı yaklaşımlar uyguladık.
Şu anda verilen hata doğru yerde, en az kod
yazarak en çok bilgiyi veriyor. Ancak .Net
Framework’üne ait bir hata tipi
kullanıyoruz.
17. İstisnalar ve Kullanımı
public class GlobalParameterException : Exception
{
public GlobalParameterException(string message)
: base(message)
{
}
}
class Program
{
static int GetGlobalParameter()
{
// Do some random calculation
Random r = new Random();
int p = r.Next(-10, 10);
// Do dome random calculation
if (p < 0)
{
throw new GlobalParameterException("GetGlobalParameter() metodu SIFIR değer üretti.
Kullanıcı hatası veya dış parametrelerden kaynaklı olabilir.");
}
else
{
p = p + 10;
}
return p;
}
static int GetX()
{
int p = GetGlobalParameter();
int X = 100 / p;
return X;
}
static int GetY()
{
int p = GetGlobalParameter();
int Y = 100 / p;
return Y;
}
static int Calculate()
{
int x = GetX();
int y = GetY();
return x * y;
}
static void Main(string[] args)
{
int value = 0;
try
{
value = Calculate();
}
catch (GlobalParameterException exception)
{
Console.WriteLine("Global parametrenin hesaplanması ile ilgili birhata alındı. Detay:");
Console.WriteLine(exception.Message);
return;
}
Console.WriteLine(value);
}
}
Artık kendimize ait bir hata tipimiz var.
Başka bir kullanıcı hem hata tipini
anlayabilir hem de gereken açıklamayı
alabilir. Artık try-catch içerisinde ayırım da
yapılabilir. Ayrıca sınıf içerisine ek kod
yazarak daha çok bilgi toplayabilirsiniz.
Sınıf metotlarını yani işi yapmak için
çağırılacak metotları yazarken (bu örnekte
Main() haricindekilerden bahsediyoruz)
kodu bir başkasının kullanacağı şekilde
yazmalısınız. Sadece siz kullanacak olsanız
bile bu sınıfınızın daha sağlam olmasına
yardımcı olur.
18. Enterprise Design
• Eğer çok büyük bir proje geliştiriyorsanız
mimarinin içerisinde istisnaların yönetimi
ayrı bir iş kalemi olacaktır.
• İstisna yönetimi projenin her aşamasında
kullanılacağı için dikey olarak
konumlandırılır.
• Yakalanan hataların kullanıcıya
gösterilmesi, log tutulması, acil
durumların email veya SMS ile haber
verilmesi gibi işlerin tamamı bu katmanda
yapılır ve ayrı bir kütüphane olarak
düşünebilirsiniz.