Go'da hata yönetimi, dilin tasarımının merkezinde yer alır. error bir arayüz tipidir ve fonksiyonlar başarısızlığı bu değeri döndürerek bildirir. Bu rehber, orta seviye geliştiriciler için Go'nun hata modelini, sarmalama (wrapping), hata karşılaştırma ve kayıt (logging) pratiklerini uygulanabilir biçimde ele alır.
Önkoşul olarak Go sözdizimine, fonksiyon imzalarına ve defer davranışına aşina olman beklenir. Buradaki örnekler Go 1.13 ve sonrasında kullanılan errors.Is, errors.As ve %w sarmalama mekanizmasına dayanır; bu API'ler Go 1.13 ile standart hale gelmiştir. Dilin daha eski sürümlerinde bu fonksiyonlar bulunmaz.
Hata Neden Değerdir?
Go'da istisna (exception) mekanizması yoktur. Bir fonksiyon başarısız olabileceğini imzasında error döndürerek belirtir:
func ReadConfig(path string) ([]byte, error) {
data, err := os.ReadFile(path)
if err != nil {
return nil, err
}
return data, nil
}
Bu yaklaşımın sonucu şudur: hata, kontrol akışının görünür bir parçasıdır. Çağıran taraf err değerini ele almak zorundadır; aksi halde derleyici şikâyet etmez ama davranış bozulur. Bu yüzden ilk kural, dönen her error değerini bilinçli olarak ele almaktır. _ ile hatayı yutmak, çoğu durumda sessiz bir hata kaynağıdır.
Sentinel Hatalar ve errors.Is
Paket düzeyinde tanımlanan ve anlamı sabit olan hatalara "sentinel hata" denir. Standart kütüphanedeki io.EOF veya sql.ErrNoRows buna örnektir.
var ErrNotFound = errors.New("kayıt bulunamadı")
func FindUser(id int) (*User, error) {
// ...
if notFound {
return nil, ErrNotFound
}
return u, nil
}
Çağıran taraf bu hatayı doğrudan karşılaştırabilir, ancak hata sarmalanmış olabileceği için == yerine errors.Is tercih edilmelidir:
u, err := FindUser(42)
if errors.Is(err, ErrNotFound) {
// kayıt yok durumuna özel davranış
}
errors.Is, sarmalama zincirini takip ederek hedef hatayı arar. Doğrudan err == ErrNotFound karşılaştırması, hata bir katman yukarıda sarmalandığında başarısız olur.
Hataları Sarmalama: %w
Bir hatayı üst katmana taşırken bağlam eklemek faydalıdır. fmt.Errorf içinde %w fiili, orijinal hatayı zincire bağlar:
func LoadUserProfile(id int) (*Profile, error) {
u, err := FindUser(id)
if err != nil {
return nil, fmt.Errorf("kullanıcı profili yüklenemedi (id=%d): %w", id, err)
}
return profileFrom(u), nil
}
Burada dikkat edilmesi gereken nokta, %w ile %v arasındaki farktır. %v yalnızca metni ekler ve errors.Is/errors.As ile geri izlemeyi imkânsız kılar. %w ise zinciri korur. Yalnızca bir %w kullanılabilir; birden fazla hatayı birleştirmek istiyorsan errors.Join (Go 1.20 ile standart kütüphaneye eklendi) değerlendirilebilir.
Tip Tabanlı Hatalar ve errors.As
Bazı hatalar, taşıdıkları ek veriyle anlam kazanır. Örneğin bir doğrulama hatası, hangi alanın geçersiz olduğunu içerebilir:
type ValidationError struct {
Field string
Msg string
}
func (e *ValidationError) Error() string {
return fmt.Sprintf("%s alanı geçersiz: %s", e.Field, e.Msg)
}
Çağıran taraf bu tipi errors.As ile çıkarabilir:
var ve *ValidationError
if errors.As(err, &ve) {
fmt.Println("geçersiz alan:", ve.Field)
}
errors.As, zincirdeki herhangi bir hatanın hedef tipe atanabilir olup olmadığını kontrol eder. Bu sayede sarmalanmış hatalar bile tip bilgisini kaybetmez.
Hata Metinleri ve Kayıt Pratikleri
Hata metinleri küçük harfle başlamalı ve noktalama ile bitmemelidir; çünkü bu metinler başka hataların içine gömülür. "Kullanıcı bulunamadı." yerine "kullanıcı bulunamadı" tercih edilir.
Kayıt (logging) tarafında iki yaygın hata vardır:
- Hem loglayıp hem döndürmek. Aynı hatayı her katmanda loglamak, günlükleri gereksiz şişirir ve kök nedeni gizler. Genellikle hata, ele alındığı en üst katmanda bir kez loglanır.
- Bağlamı kaybetmek.
log.Println(err)yerine, hatanın hangi işlem sırasında oluştuğunu belirten bir mesajla birlikte loglamak teşhisi kolaylaştırır.
panic ve recover Ne Zaman Kullanılır?
panic, programın devam edemeyeceği durumlar içindir: bozulmuş bir değişmez (invariant), eksik yapılandırma veya programcı hatası. Beklenen çalışma zamanı hataları için error döndürmek doğru yaklaşımdır.
recover yalnızca defer içinde çalışır ve genellikle bir sunucunun tek bir isteği kurtarıp süreci ayakta tutması gibi sınırlı durumlarda kullanılır. recover'ı normal hata akışı için kullanmak, hatayı görünmez kılar ve hata ayıklamayı zorlaştırır.
Yaygın Hatalar
- Hatayı yok saymak:
_ = doSomething()veya dönüş değerini kontrol etmemek. Derleyici uyarmaz; hata sessizce kaybolur. %vile sarmalamak:errors.Is/errors.Asçalışmaz, hata zinciri kopar.- Aynı hatayı tekrar tekrar loglamak: Gürültü yaratır, kök nedeni örter.
- Sentinel hataları
==ile karşılaştırmak: Sarmalanmış hatalarda başarısız olur;errors.Iskullan. panic'i normal akış için kullanmak: Kütüphane kodunda beklenmeyenpanic, çağıranı hazırlıksız yakalar.
Güvenlik Etkileri
Hata mesajları, kullanıcıya veya dış sistemlere gösterildiğinde bilgi sızıntısına yol açabilir. Dosya yolları, SQL parçaları, iç değişken adları veya yığın izleri (stack trace) saldırgana sistem hakkında ipucu verir. Bu nedenle:
- Dış API yanıtlarında genel bir hata mesajı döndür; ayrıntıyı yalnızca sunucu tarafı günlüklerine yaz.
- Kimlik doğrulama ve yetkilendirme hatalarında, "kullanıcı yok" ile "şifre yanlış" ayrımını dışarıya sızdırma.
- Günlüklerde hassas veriyi (token, parola, kişisel veri) maskele.
Özet
Go'da hata yönetimi, hatayı bir değer olarak ele alıp bağlamı koruyarak yukarı taşımaya dayanır. errors.Is ve errors.As ile hata kimliğini ve tipini güvenle sorgula, %w ile zinciri koparma, panic'i yalnızca gerçekten olağan dışı durumlara sakla ve hata mesajlarını dışarıya sızdırma. Bu pratikler dile ve sürüme bağlı olmadan uzun süre geçerliliğini korur.