Go’da hata yönetimi uzun zamandan beri tartışma konusu. Fonksiyonlardan error tipini döndürmek, kodun akışını açık tutsa da sık sık kontrol satırlarıyla dolup taşabiliyor. Diğer yandan, istisna benzeri yaklaşımlar (panic/recover) daha az boilerplate vaat ediyor, ama kontrol akışını gizleyebiliyor ve beklenmedik durumları zorlaştırıyor. Bu iki yöntemin performans, okunabilirlik ve bakım üzerindeki etkileri hâlâ net değil. Sizce Go’nun mevcut hata modeli yeterli mi, yoksa dilin geleceğinde daha yüksek seviyeli bir mekanizma eklenmeli mi? Görüşlerinizi merak ediyorum.
Go dilinde hata yönetimi: error döndürmek vs istisna benzeri yaklaşımlar, hangisi daha sürdürülebilir?
👁️ 88 görüntüleme💬 4 cevap❤️ 0 beğeni
4 Cevap
Bence error döndürmek hâlâ en temiz yol; panic/recover sadece acemi birinin “sihirli çare” kutusuna atması gibi 😅. Kanka, sık kontrol satırları can yaksa bile kod akışını açık tutmak uzun vadede sürdürülebilir 😂.
Geçen sene bir mikroservis projesinde, dış API’dan dönen hataları zincirleme `if err != nil { return err }` şeklinde sarmak zorunda kaldım. Başlangıçta kod okunurluğu güzel gözükse de, fonksiyonların ortalarına serpiştirilen bu satırlar bir bakıma “görünmez duvar” gibi oldu; bir hata bulmak için iki üç dosya arasında zıplamak zorunda kalıyorduk. Özellikle bir işlem akışı içinde 5‑6 seviyeye inildiğinde, `error` döndürme modeli adeta “boilerplate çöpü” yarattı ve refactor yaparken gözden kaçan hatalarla karşılaştık. Bu deneyimden sonra, kritik olmayan ama beklenmedik bir durum olduğunda `panic`/`recover` ile “üst seviyede” bir toplama yapıp, log tutup servisi yeniden başlatmak daha temiz bir çözüm gibi geldi.
Valla, ama `panic` de bir kılıç iki ucu keskin. Prod ortamında bir `panic` kaçırıldığında tüm servis çökebilir, ki bu da SLA’yı mahvediyor. Biz de sonunda `error` döndürme modelini tamamen bırakmadık; sadece gerçekten “recoverable” olmayan, programın devam edemeyeceği hatalar için `panic` kullandık. Bu kararı verirken `recover` ile global bir interceptor (middleware) kurduk ve tüm `panic`leri yakalayıp merkezi bir hata raporu oluşturduk. Böylece `panic`nin “gizli akış” problemini bir nebze kontrol altına almış olduk. Performans açısından da fark çok büyük olmadı; `panic` bir istisna gibi stack unwind yapıyor, ama nadiren tetiklendiği için ortalama latency’de ölçülebilir bir artış yoktu.
Bence Go’nun mevcut hata modeli hâlâ yeterli, ama ekip içinde bir “hata politikası” tanımlamak şart. `error` döndürme ve `panic`/`recover` kombinasyonunu akıllıca ayırırsak, kod okunabilirliği ve bakım maliyeti dengelenebilir. Dilin geleceğinde “try/catch” gibi bir şey eklenirse, belki bu ikiliyi daha da netleştirir; ama şu anki iki‑yollu yaklaşım, doğru kullanım rehberiyle sürdürülebilir bir yapı sağlıyor. Senin projende de aynı dengeyi kurup, `panic`yi sadece “imkansız” durumlar için saklamak, uzun vadede sürüm güncellemelerini ve debugging’i kolaylaştırır.
bence panic/recover ile defer'lerin sırası ve cleanup davranışı ilginç, özellikle birden çok defer varsa ne olur kanka? Bu kombinasyonda runtime overhead’ı ölçtünüz mü?
Kanka, Go’da error döndürmek bir nevi “her adımda kontrol paneli” gibi; kodun akışı açık, kim neyin hata verdiği bir bakışta belli oluyor. Benzer bir yaklaşıma Rust’ta da rastlıyorsun, `Result<T, E>` tipini zorunlu kılıyor ve compiler bu hataları zorunlu olarak ele aldırıyor. Bu, Go’nun “error‑i döndür, kontrol et” mantığına çok yakın ve uzun vadede bakımı kolaylaştırıyor. Panic/recover ise Java’da gördüğümüz try‑catch’e benzer bir “istisna” yolu sunuyor; gerçekten beklenmeyen, kurtarılamaz durumlar için hızlı bir kaçış sağlasa da, normal iş akışını gizleyip hata izini zorlaştırıyor. Valla, sık sık panic kullandığında kodun nerede kırıldığını bulmak zorlaşıyor ve test kapsamı da daralıyor.
Bence Go’nun mevcut modeli, özellikle `errors.Is/As` ve 1.13 sonrası error wrapping gibi iyileştirmelerle zaten yeterince olgunlaştı. Üst‑seviye bir “exception” eklemek, dilin sadeliğini bozar ve performans kaybına da yol açabilir. Eğer boilerplate çok geliyorsa, `errgroup`, `go.uber.org/multierr` gibi helper paketlerini ya da generics ile tip‑güvenli wrapper fonksiyonları (örneğin `func Try[T any](fn func() (T, error)) (T, error)`) kullanmak daha sürdürülebilir. Yani, Go’nun hata modeli hâlâ yerinde; sadece senaryoya göre panic’i “kritik çöküş” için saklamak yeterli.