C# Exception Handling Anti-Pattern'leri: Doğru Yakalama Sanatı
Exception'lar (İstisnalar), uygulamanızın çökmesini engelleyen kurtarıcılardır. Ancak yanlış kullanıldıklarında, hataları sessizce yutan, stack trace'i bozan, performansı yerle bir eden ve debug edilemez uygulamalara dönüşen canavarlara dönüşürler. Bu yazıda, en sık yapılan 5 anti-pattern'i ve bunların çözümlerini ele alıyoruz.
Anti-Pattern 1: Boş Catch Blokları (Sessiz Ölüm)
csharp
// ❌ KÖTÜ KOD - Hata yutuldu, kimse haberdar değil!
try
{
await veritabani.KaydetAsync();
}
catch
{
// Hiçbir şey yapma, görmezden gel.
}
Sorun: Hata oluştu, ancak ne loglandı ne kullanıcıya bildirildi ne de işlem geri alındı. Uygulama "başarılı" gibi devam eder, ancak veriler tutarsız hale gelir. Bu tarz hataları bulmak saatler sürer.
✅ Çözüm: Asla boş catch kullanmayın. En kötü ihtimalle hatayı loglayın:
csharp
try { await veritabani.KaydetAsync(); }
catch (Exception ex)
{
Logger.LogError(ex, "Veritabanı kaydı başarısız.");
throw; // Yeniden fırlat (Stack trace korunur)
}
Anti-Pattern 2: Genel Exception Yakalamak (catch(Exception ex))
csharp
// ❌ KÖTÜ KOD - Her şeyi yakala, ama ne yapacağını bilme!
try
{
dosyaOkuyucu.Read();
}
catch (Exception ex)
{
MessageBox.Show("Bir hata oluştu.");
}
Sorun: OutOfMemoryException, StackOverflowException, AccessViolationException gibi kritik sistem hatalarını yakalayıp bastırmak, uygulamanın kararsız (corrupt) bir state'te çalışmaya devam etmesine neden olur. Bu hatalar genelde kurtarılamaz.
✅ Çözüm: Sadece kurtarabileceğiniz (handle edebileceğiniz) exception'ları yakalayın. Örneğin, FileNotFoundException, SqlException, ArgumentException gibi özel tipler.
csharp
// SADECE dosya bulunamadığında yakala, diğer hataları yukarı fırlat!
try { dosyaOkuyucu.Read(); }
catch (FileNotFoundException ex)
{
Logger.LogWarning(ex, "Dosya mevcut değil, varsayılan oluşturuluyor.");
// Dosyayı oluştur ve devam et...
}
// catch (Exception ex) { throw; } // GEREKSİZ! Yakalanmayan hatalar zaten fırlar.
Anti-Pattern 3: throw ex; vs throw; (Stack Trace Katliamı)
Bu, en sinsi ve en yaygın hatadır.
csharp
// ❌ KÖTÜ KOD - Stack trace sıfırlanır!
try { HataYap(); }
catch (Exception ex)
{
Logger.Log(ex);
throw ex; // Hata tekrar fırlatılır ama stack trace sıfırlanır!
}
Sorun: throw ex;, hatanın oluştuğu orijin (line number, method) bilgisini siler ve sanki hata throw ex; satırında oluşmuş gibi gösterir. Bu, debug etmeyi imkansızlaştırır.
✅ Çözüm: Orijinal stack trace'i korumak için sadece throw; kullanın (parametresiz).
csharp
try { HataYap(); }
catch (Exception ex)
{
Logger.Log(ex);
throw; // Orijinal stack trace KORUNUR!
}
Peki ya yeni bir exception fırlatıp içine inner exception olarak eskiyi eklemek istersem?
csharp
try { HataYap(); }
catch (Exception ex)
{
Logger.Log(ex);
throw new BusinessException("İşlem başarısız oldu.", ex); // Inner exception ile!
}
Bu durumda ex artık InnerException olur ve orijinal hata kaybolmaz. Ancak üst katmanda hata yakalanırken ex.InnerException kontrol edilmelidir.
Anti-Pattern 4: Her Yerde try-catch Kullanmak (Aşırı Yakalama)
csharp
// ❌ KÖTÜ KOD
public void Metot1()
{
try { Metot2(); } catch { throw; } // Gereksiz!
}
public void Metot2()
{
try { Metot3(); } catch { throw; } // Gereksiz!
}
public void Metot3()
{
try { KarmaşikIs(); } catch { throw; } // Gereksiz!
}
Sorun: Her katmanda try-catch bloğu olması, performansı düşürür (exception oluşmasa bile try bloğunun hafif bir maliyeti vardır) ve kodu gereksiz yere şişirir.
✅ Çözüm: Exception'ları sadece onları handle edebileceğiniz (loglama, kullanıcıya mesaj gösterme, geri alma) katmanda yakalayın. Genelde bu, UI katmanı (Controller veya Middleware) veya en üst seviyedeki orchestration katmanıdır. Alttaki servis katmanları, hataları ya throw; ile yukarı iletir ya da hiç try-catch kullanmaz.
Anti-Pattern 5: finally Yerine catch ile Kaynak Temizleme
csharp
// ❌ KÖTÜ KOD
var conn = new SqlConnection("...");
try
{
conn.Open();
// İşlem yap
}
catch
{
conn.Close(); // Hata oluşursa kapat, ama ya hata Open'dan önce gelirse?
throw;
}
Sorun: Kaynakların (dosya, bağlantı, handle) her durumda (hata olsun veya olmasın) serbest bırakılması gerekir. Bunu catch içinde yapmak, hatanın türüne göre değişir ve her zaman çalışmaz.
✅ Çözüm: finally bloğu (veya using statement) kullanın.
csharp
// En iyisi: using statement (IDisposable için)
using var conn = new SqlConnection("...");
conn.Open();
// İşlem yap - Hata olsa bile connection dispose edilir.
// veya manuel finally:
var conn = new SqlConnection("...");
try { conn.Open(); }
finally { conn.Dispose(); } // Her durumda çalışır.
Doğru Strateji: Exception Filter (when) Kullanımı
C# 6 ile gelen when anahtar kelimesi, belirli bir koşula göre yakalama yapmanızı sağlar. Bu, gereksiz try-catch'leri azaltır.
csharp
try
{
await SiparisVer();
}
catch (Exception ex) when (ex.Message.Contains("Timeout"))
{
// SADECE timeout hatasıysa logla ve yeniden dene
Logger.LogWarning(ex, "Timeout, yeniden deneniyor...");
await SiparisVer();
}
catch (Exception ex) when (ex is SqlException sqlEx && sqlEx.Number == 2627)
{
// SADECE unique constraint ihlali (2627) ise kullanıcıya özel mesaj ver
throw new DuplicateKeyException("Bu kayıt zaten var.", ex);
}
// Diğer tüm hatalar (beklenmeyen) burada yakalanmaz, yukarı fırlar.
Performans Notu: Exception'lar pahalıdır (stack trace oluşturma maliyeti). Kontrol edilebilir durumlar (ör. null kontrolü) için exception kullanmayın; if ile önleyin. Exception'lar yalnızca gerçekten istisnai (exceptional) durumlar içindir.
Özet Altın Kurallar:
-
Sadece özel exception'ları yakala (FileNotFoundException, SqlException), asla
catch (Exception)yapma (en üst katman hariç). -
Kötü
throw ex;kullanma, sadecethrow;kullan. -
Boş catch kullanma, hata yutma; mutlaka logla veya yukarı ilet.
-
Kaynakları
finallyveyausingile temizle, catch içinde değil. -
Exception Filter (
when) ile koşullu yakalama yaparak kodunu temizle. -
Hata oluşabilecek yere değil, hata yönetilebilecek yere try-catch koy.
Unutmayın: İyi yönetilen exception'lar uygulamanın dostudur; kötü yönetilenler ise gece 3'te gelen telefonlardır.