İçeriğe Atla
Mustafa Erbay
Teknoloji · 11 dk okuma · görüntülenme Read in English

Error Handling: Dönüş Kodları mı Exception'lar mı? 3 Kritik Fark

Yazılımda hata yönetiminin iki temel yaklaşımı: dönüş kodları ve exception'lar. 20 yıllık tecrübemle bu iki yöntemin 3 kritik farkını ve ne zaman hangisini…

100%

Giriş: Hata Yönetimi, Yazılımın Karanlık Yüzü

Yazılım geliştirme serüvenimde, hatasız kod yazmanın bir illüzyon olduğunu defalarca gördüm. Asıl mesele, hataların ortaya çıkmasını engellemekten ziyade, çıktıklarında onları nasıl yöneteceğimizdir. Bu, bir sistemin kararlılığını ve güvenilirliğini doğrudan etkileyen kritik bir konu.

Hata yönetimi konusunda iki ana yaklaşımla karşılaşıyoruz: dönüş kodları (return codes) ve exception’lar. İkisi de hataları bildirme ve işleme amacına hizmet etse de, uygulama şekilleri, kod üzerindeki etkileri ve performans karakteristikleri açısından ciddi farklılıklar taşır. Bu farklılıkları derinlemesine anlamak, hangi senaryoda hangi yöntemin daha uygun olduğuna karar vermemde bana her zaman yol gösterdi.

Fark 1: Akış Kontrolü ve Kod Okunabilirliği Üzerindeki Etkisi

Dönüş kodları, bir fonksiyonun işini başarıyla tamamlayıp tamamlamadığını veya nasıl bir hata ile karşılaştığını belirtmek için bir değer döndürmesidir. Bu, genellikle bir int veya enum tipiyle yapılır; 0 başarıyı, diğer değerler belirli hata durumlarını temsil eder. Bu yöntemde, çağıran kodun her zaman dönüş değerini kontrol etmesi ve buna göre aksiyon alması gerekir.

Bu yaklaşım, kodun akışını oldukça açık bir hale getirir, çünkü her olası hata durumu için if veya switch blokları yazmak zorundasınız. Ancak, bu durum bazen “callback hell” benzeri bir “error check hell” yaratabilir. Özellikle derinlemesine iç içe geçmiş fonksiyon çağrılarında, ana iş mantığı, ardı ardına gelen hata kontrol blokları arasında kaybolabilir. Bir üretim ERP’sinde, karmaşık bir stok güncelleme operasyonunda, her adımda dönüş kodlarını kontrol etmek zorunda kaldığımda kodun okunabilirliğinin belirgin şekilde düştüğünü bizzat deneyimledim. Ana iş akışını takip etmek için her satırı dikkatlice incelemek gerekiyordu.

Exception’lar ise daha farklı bir mekanizma sunar. Bir hata meydana geldiğinde, normal program akışından çıkarak çağrı yığını (call stack) boyunca geriye doğru bir “zıplama” yaparlar. Bu zıplama, hatayı yakalayan (catch eden) ilk try-catch bloğuna ulaşana kadar devam eder. Bu sayede, ana iş mantığı kodunuz hata kontrolüyle dolmaz ve daha temiz görünür. Ben kendi yan ürünümün backend’inde FastAPI kullanırken, özellikle API endpoint’lerinde iş mantığını sade tutmak için exception’ları yoğun bir şekilde kullanıyorum. Eğer bir kullanıcı yetkisizse veya veri formatı yanlışsa, doğrudan bir HTTPException fırlatıyorum ve bu durum, ana işleme kodunu kirletmeden, middleware tarafından merkezi olarak yakalanıp uygun bir HTTP yanıtına dönüştürülüyor. Bu, kodun ana akışını gözle görülür şekilde daha okunaklı hale getiriyor. Ancak, exception’ların nerede fırlatılabileceğini bilmek ve bunları doğru yerlerde yakalamak, karmaşık sistemlerde bazen zorlayıcı olabilir.

Fark 2: Hata Yayılımı ve Yönetim Kapsamı

Dönüş kodları kullanılırken, bir hata durumunu çağrı yığını boyunca yukarı taşımak tamamen sizin sorumluluğunuzdadır. Bir fonksiyon hata döndürdüğünde, çağıran fonksiyonun bu hatayı kontrol edip ya kendisi işlemesi ya da kendi dönüş değeri olarak yukarıya doğru iletmesi gerekir. Bu manuel yayılım, hem esneklik hem de risk taşır. Esneklik, çünkü hatanın hangi seviyede ve nasıl işleneceğine dair tam kontrolünüz vardır. Risk ise, bir hata kodunu kontrol etmeyi veya iletmeyi unuttuğunuzda, hatanın sessizce yutulması ve beklenmedik davranışlara yol açmasıdır. Yıllar önce bir bankanın iç platformunda, kritik bir finansal işlemin dönüş kodunun gözden kaçırılması nedeniyle, aslında başarısız olan bir işlemin başarılı gibi gösterildiğini ve bunun düzeltilmesinin günler sürdüğünü hatırlıyorum. Bu tür “sessiz hatalar” sistemin bütünlüğünü ciddi şekilde tehdit edebilir.

Exception’lar ise hatanın yayılımını otomatik olarak yönetir. Bir exception fırlatıldığında, program normal akışını terk eder ve çağrı yığını boyunca geriye doğru, hatayı yakalayacak uygun bir try-catch bloğu arar. Eğer hiçbir yerde yakalanmazsa, programın sonlandırmasına neden olur. Bu “yakala ya da ilet” (catch or rethrow) prensibi, hataların sessizce yutulması riskini büyük ölçüde azaltır. Bir exception fırlatıldığında, ya bir yerde yakalanır ve işlenir ya da program çöker; ikisi de hatanın fark edilmesini sağlar. Özellikle derinlemesine katmanlara sahip kurumsal uygulamalarda, exception’lar sayesinde hatanın kaynağını tespit etmek ve doğru yere iletmek çok daha kolay oluyor. Çok katmanlı bir mimaride (UI -> API -> Business Logic -> Domain -> Infrastructure -> DB/External Service) bir hatanın en üst seviyeye kadar doğru metadata ile taşınması, exception’lar sayesinde çok daha otomatik ve güvenilir biçimde sağlanır. Manuel dönüş kodu ile aynı karmaşıklığı yönetmek, çok daha fazla boilerplate kodu ve hata potansiyeli yaratır.

Ancak exception’ların otomatik yayılımı da kendi zorluklarını getirir. Yanlış yerlerde Exception veya Throwable gibi genel tipleri yakalamak, spesifik hataların maskelenmesine ve yine beklenmedik durumların ortaya çıkmasına yol açabilir. Bu yüzden her zaman mümkün olduğunca spesifik exception tiplerini yakalamayı tercih ettim. Örneğin, bir veritabanı bağlantı hatası ile bir iş kuralı ihlali hatasını aynı şekilde ele almam. Her birinin kendi except bloğu olmalı.

Fark 3: Performans ve Kaynak Kullanımı Üzerindeki Etkileri

Performans, hata yönetimi stratejisi seçilirken genellikle son düşünülen faktör olsa da, özellikle yüksek performans gerektiren sistemlerde veya kritik “hot path”lerde göz ardı edilemez bir konudur. Dönüş kodları, temelde sadece bir değer döndürdüğü için performans üzerinde neredeyse hiç ek yük oluşturmazlar. Bir tamsayı döndürmek veya bir if kontrolü yapmak, işlemci döngüleri açısından oldukça ucuz operasyonlardır. Bu nedenle, Linux kernel gibi düşük seviyeli sistemlerde veya gömülü sistem programlamasında dönüş kodları yaygın olarak tercih edilir. Benim de bir kernel module blacklist uygulaması geliştirirken, hata durumlarını dönüş kodları ile yönettim. Çünkü orada her mikro saniye önemliydi ve exception fırlatmanın getireceği ek yük kabul edilemezdi.

Exception’lar ise, fırlatıldıklarında önemli ölçüde daha fazla kaynak tüketebilirler. Bir exception fırlatıldığında, çalışma zamanı (runtime) çağrı yığını üzerinde geriye doğru hareket ederek catch bloğunu bulur, bu süreçte yığını açar (stack unwinding) ve çoğu zaman bir exception nesnesi oluşturur. Bu nesne, genellikle hatanın türü, mesajı ve fırlatıldığı yerdeki yığın izi (stack trace) gibi bilgileri içerir. Stack trace oluşturmak ve yığını açmak, özellikle derin çağrı yığınlarında, kayda değer bir işlemci ve bellek yükü oluşturabilir. Örneğin her sorguda milyonlarca satırın işlendiği bir raporlama bileşeninde, beklenen bir veri eksikliği durumunda exception fırlatmak yerine dönüş kodları kullanmak daha doğrudur; çünkü sıcak döngüde sıkça exception fırlatmak rapor süresini katlayabilir. Bu tür “beklenen” hatalar için exception kullanmak, sistemi gereksiz yere yavaşlatabilir.

Bu yüzden, exception’ları sadece “istisnai” durumlar için kullanmak önemlidir. Yani, programın normal akışında beklemeyeceğiniz, ancak meydana geldiğinde işin devam etmesini engelleyecek durumlar için. Bir API gateway’de rate limiting ihlali gibi durumlarda exception kullanmak yerine, HTTP yanıt kodu döndürmek daha performanslı ve uygun bir yöntem olabilir. Ancak, bir veritabanı bağlantı kesintisi veya kritik bir konfigürasyon dosyasının bulunamaması gibi durumlar, gerçekten istisnai olup exception fırlatmayı hak eder.

Pragmatik Yaklaşımım: Ne Zaman Hangisini Tercih Ediyorum?

Yıllar içinde öğrendiğim en önemli şeylerden biri, “doğru” veya “yanlış” bir hata yönetimi stratejisi diye bir şeyin olmadığıdır. Her şey bağlama ve projenin ihtiyaçlarına bağlıdır. Ben kendi işlerimde genellikle bu ayrımı yaparım:

  1. Düşük Seviyeli Sistemler ve Performans Kritik Alanlar: C/C++ ile yazılan kütüphaneler, kernel modülleri veya bare-metal sistemlerde çalışan performans kritik servisler için genellikle dönüş kodlarını tercih ederim. Burada her işlemci döngüsü ve bellek tahsisi önemlidir. Sıcak yolda yüksek frekansla çağrılan bir kontrol fonksiyonunda, her çağrı için exception fırlatmak yerine int dönüş kodları kullanmak performansı optimum seviyede tutar.
  2. Uygulama Seviyesi ve İş Mantığı: Yüksek seviyeli dillerde (Python, Java, C#, Go) yazılan kurumsal uygulamalar, API servisleri ve iş mantığı katmanları için exception’ları tercih ederim. Bu, kodun daha temiz ve okunabilir olmasını sağlar, iş akışını boğan if zincirlerinden kaçınırım. Özellikle bir müşteri projesinde, karmaşık iş kuralları olan bir sipariş yönetim sisteminde, iş kuralı ihlallerini BusinessRuleViolationException gibi spesifik exception’larla yönettim. Bu, hatanın ne olduğunu ve hangi iş kuralının ihlal edildiğini kolayca anlamamı sağladı.

Bazen iki yaklaşımı bir arada kullanmak da gerekebilir. Örneğin, düşük seviyeli bir kütüphanenin C API’sinden gelen dönüş kodlarını, uygulama seviyesinde bir exception’a çevirerek daha üst katmanlara iletebilirim. Bu, her katmanın kendi doğal hata yönetimi mekanizmasını kullanmasına olanak tanır ve entegrasyonu kolaylaştırır. Benim kendi siteme yaptığım finansal hesaplayıcıların backend’inde, Go dilinde yazdığım bazı kritik algoritmalar dönüş kodları kullanırken, Python tabanlı API katmanı bunları kendi exception’larına çeviriyor. Bu hibrit yaklaşım, bana hem performans hem de geliştirme kolaylığı sağlıyor.

Gerçek Dünyada Hata Yönetimi: Gözlemlerim ve Dersler

Hata yönetiminde doğru aracı seçmek kadar, onu doğru kullanmak da önemlidir. Yıllar içinde gördüğüm bazı yaygın anti-pattern’ler ve bunlardan çıkardığım dersler var:

  • Dönüş Kodlarını Göz Ardı Etmek: En sık rastladığım hatalardan biri, dönüş kodlarının kontrol edilmemesidir. Bir fonksiyonu çağırıp dönüş değerini bir değişkene atamadan veya kontrol etmeden ilerlemek, potansiyel bir felakete davetiye çıkarmaktır. Kritik bir modülde veri doğrulama fonksiyonunun dönüş kodu gözden kaçtığında hatalı veriler sisteme sızabilir; örneğin yanlış fatura kesilmesi gibi sonuçların geri alınması, manuel düzeltme operasyonlarıyla günler süren maliyetli bir işe dönüşebilir.
  • Exception’ları Geniş Kapsamlı Yakalamak (Catch All): catch (Exception e) veya except Exception as e gibi genel yakalamalar, spesifik hataları gizleyerek debug etmeyi imkansız hale getirebilir. Benim tecrübemde, her zaman mümkün olduğunca spesifik exception tiplerini yakalamak ve işlemek en doğrusudur. Eğer genel bir catch bloğu kullanıyorsam, bu genellikle en üst seviyedeki bir “fallback” handler’dır ve hatayı loglayıp kullanıcıya genel bir hata mesajı döndürmek gibi son çare bir işlem yapar.
  • Beklenen Durumlar İçin Exception Kullanmak: Bir kullanıcının yanlış parola girmesi veya bir API çağrısının beklenen bir iş hatası kodu döndürmesi gibi durumlar “istisnai” değildir. Bunlar iş akışının normal bir parçasıdır ve genellikle dönüş kodları, boolean değerler veya özel durum nesneleriyle (örneğin Result<T, E> tipi) daha iyi yönetilir. Exception’ları bu tür durumlar için kullanmak, hem performansı olumsuz etkileyebilir hem de kodun okunabilirliğini azaltır. Yük altında sık tekrarlanan bir doğrulama başarısızlığını exception ile yönetmek, sunucunun işlem maliyetini gereksiz yere artırır; bunu dönüş koduna çevirmek hem yükü hafifletir hem de niyeti netleştirir.
  • Yetersiz Loglama ve Observability: Hatalar meydana geldiğinde, ne olduğunu anlamanın en iyi yolu iyi loglardır. Dönüş kodu veya exception fark etmeksizin, her hatanın uygun seviyede (INFO, WARNING, ERROR) ve yeterli detayla (stack trace, ilgili değişken değerleri) loglanması kritik öneme sahiptir. Kendi sistemlerimde journald ve merkezi log toplama araçlarını kullanarak hataları proaktif olarak izliyorum. Bir PostgreSQL WAL bloat durumu yaşadığımda, zamanında düşen bir ERROR logu sayesinde disk dolmadan müdahale edebildim.
  • Transaction Outbox ve Idempotency: Dağıtık sistemlerde hata yönetimi daha da karmaşıklaşır. Bir işlem başarısız olduğunda, sistemin tutarlı kalmasını sağlamak için transaction outbox deseni veya idempotent operasyonlar gibi mekanizmalar kullanıyorum. Bu, bir hatadan sonra bile sistemin güvenle yeniden denemeler yapabilmesini veya tutarlı bir duruma dönebilmesini sağlıyor.

Sonuç: Hata Yönetimi Bir Sanattır

Dönüş kodları ve exception’lar arasındaki seçim, sadece teknik bir tercih değil, aynı zamanda projenin doğasına, takımın alışkanlıklarına ve sistemin performans gereksinimlerine bağlı pragmatik bir karardır. Her iki yaklaşımın da kendine göre avantajları ve dezavantajları vardır ve benim tecrübemde, en iyi sonuç genellikle bu iki yöntemin akıllıca bir kombinasyonundan gelir.

Önemli olan, hangi yöntemi seçerseniz seçin, tutarlı olmak, hataları göz ardı etmemek ve sistemin hata durumlarında bile öngörülebilir ve güvenilir kalmasını sağlamaktır. Hata yönetimi, yazılımın sadece “çalışmasını” değil, aynı zamanda “güvenilir bir şekilde çalışmasını” sağlayan temel bir disiplindir. Unutmayalım ki, yazdığımız kodun sadece mutlu yolları (happy paths) değil, aksayan yolları da doğru bir şekilde yönetmesi gerekir. Bu, uzun vadede sistemin bakımını kolaylaştıran, sorun giderme süresini azaltan ve en önemlisi, kullanıcı güvenini artıran en önemli faktörlerden biridir.

Paylaş:

Bu yazı faydalı oldu mu?

Yükleniyor...

Bu yazı nasıldı?

Sıkça Sorulanlar

Bu makale ile ilgili okurların sorduğu yaygın sorular.

Mevcut bir legacy projede exception handling’e geçişi nasıl başlatırım ve hangi araçları/teknikleri kullanmalıyım?
Ben önce kritik fonksiyonları birer birer izole edip, her birinin dönüş kodu yerine bir exception fırlatmasını sağladım. Bunun için IDE’nin refactoring özelliğini ve “Extract Method” gibi araçları kullandım; böylece kodun sorumluluk sınırları netleşti. Ardından, global bir hata yakalama katmanı (middleware) ekleyerek tüm üst seviye catch bloklarını tek bir yerde topladım. Test kapsamını genişletmek için unit testlerde “assertThrows” gibi metodları ekledim. Bu adımları tekrarlayarak, yavaş yavaş tüm dönüş kodlarını kaldırdım; böylece kod okunabilirliği arttı ve yeni hatalar daha erken tespit edildi.
Performans kritik bir modülde dönüş kodları mı yoksa exception’lar mı daha avantajlı? Dezavantajları neler?
Mikro‑saniyelik gecikmelerin kritik olduğu bir işlemci döngüsünde dönüş kodlarını tercih ederim; çünkü exception fırlatmak ve yakalamak JVM/CLR içinde ekstra yığın (stack) işlemleri gerektiriyor ve bu da ölçülebilir bir ek gecikmeye yol açabiliyor. Ancak, kodun bakımını ve hata izlemeyi düşündüğümde, exception’lar çok daha temiz bir akış sağlıyor; her hatayı ayrı bir sınıfla temsil edip, loglamayı merkezi bir yerde topluyorum. Dolayısıyla, yüksek frekanslı, basit hata durumları için dönüş kodları, karmaşık iş mantığı ve hata çeşitliliği için exception’lar daha uygundur.
Bir exception yakalanmadığında ne yapmalıyım? Hata ayıklama sürecimde hangi adımları izlemeliyim?
Ben bir exception yakalanmadığında öncelikle global catch bloğumda log seviyesini “ERROR” yapıp, stack trace’i detaylı bir şekilde dosyaya yazdırıyorum. Daha sonra, hata raporunu izole etmek için ilgili modülü izole bir test ortamına taşıyarak reproduksiyon adımlarını kaydediyorum. Çoğu zaman, “try‑catch” içinde “finally” bloğu ekleyerek kaynakların (bağlantı, dosya) doğru bir şekilde serbest bırakıldığından emin oluyorum. Gerekirse, profiller ve heap dump’lar alıp, hatanın bellek sızıntısı mı yoksa mantıksal bir bug mu olduğunu analiz ediyorum. Bu sistematik yaklaşım, hatanın kök nedenini bulmamı çoğu zaman kolaylaştırıyor.
Genel kanıya göre exception kullanmak her zaman kodu yavaşlatır; bu doğru mu?
Ben bu mitin sadece bir kısmının doğru olduğunu gördüm. Exception fırlatmak, gerçekten bir hata durumunda stack unwind işlemi gerektirdiği için normal akışta bir maliyet yaratmaz; yani hata oluşmadığında performans etkisi yoktur. Ancak, sık sık “try‑catch” içinde normal kontrol akışı için exception kullanmak, örneğin bir döngüde her adımda fırlatmak, ciddi bir yavaşlamaya neden olur. Bu yüzden ben, hataların nadir gerçekleştiği durumlarda exception tercih ederim; sık karşılaşılan durumlar için ise dönüş kodları daha ekonomiktir. Yani, mitin doğruluğu kullanım bağlamına bağlıdır.
ME

Mustafa Erbay

Sistem Mimarisi · Network Uzmanı · Altyapı, Güvenlik ve Yazılım

2006'dan bu yana sistem mimarisi, network, sunucu altyapıları, büyük yapıların kurulumu, yazılım ve sistem güvenliği ekseninde çalışıyorum. Bu blogda sahada karşılığı olan teknik deneyimlerimi paylaşıyorum.

Kişisel Notlar

Bu notlar sadece sizde saklanır. Tarayıcınızda yerel olarak tutulur.

Hazır 0 karakter

Yorumlar

Sunucu Taraflı AI Moderasyon

Yorumlar sunucuda yapay zeka ile denetlenir ve kalıcı olarak saklanır.

?
0/2000

Sunucu taraflı AI denetim

✉️ Ücretsiz · Spam yok · İstediğin an çık

Yeni yazılardan haberdar olun

Yeni içerikler ve teknik notlar e-postanıza gelsin.

  • 📌
    Haftanın en iyisi Sadece okumaya değer tek yazı
  • 🔧
    Alet çantası Bu hafta kullandığım araçlar
  • 🧠
    Perde arkası Blog'a girmeyen notlar

Spam yapmıyoruz. İstediğiniz zaman ayrılabilirsiniz. · Sadece Umami (self-hosted, Google yok) ile takip.

Okuma İstatistikleriniz

0

Yazı Okundu

0dk

Okuma Süresi

0

Gün Serisi

-

Favori Kategori

İlgili Yazılar