İçeriğe geç
Muhammet Şafak
en
Diller 3 dk okuma

Go'da hata yönetimi: error değeriyle yaşamak

İstisna (exception) mekanizması olmayan Go'da hata yönetimini dil deyimi olarak benimsemenin pratik yolu.

Kapak görseli — fırçalanmış metal çubuğa kazınmış if err != nil { return err } satırları, mor ışıklı bir kesici yanı başındaki kanalı açıyor

Go’ya PHP’den geçtiğimde en çok kafayı yorduğum şey hata yönetimi oldu. Exception tabanlı bir dilden gelince Go’nun yaklaşımı ilk başta garip hissettiriyor — hatta kasıtlı olarak kısıtlayıcı. Zamanla bu yaklaşımın neden var olduğunu ve nasıl doğal hale geldiğini anlamak, dilin diğer parçalarını da daha iyi kavramama yardımcı oldu.

Go’da hata nasıl çalışır

Go’da error bir interface’tir ve tanımı dilin spesifikasyonunda duruyor. Spesifikasyonun ifadesiyle error, “bir hata durumunu temsil etmek için kullanılan geleneksel interface”tir ve nil değeri “hata yok” demektir. Tek bir metodundan oluşur:

type error interface {
    Error() string
}

Hata üretebilecek her fonksiyon, son dönüş değeri olarak error döner. Çağıran kod bu değeri kontrol etmekten sorumludur:

package main

import (
    "errors"
    "fmt"
)

func divide(a, b float64) (float64, error) {
    if b == 0 {
        return 0, errors.New("sıfıra bölme hatası")
    }
    return a / b, nil
}

func main() {
    result, err := divide(10, 0)
    if err != nil {
        fmt.Println("Hata:", err)
        return
    }
    fmt.Println("Sonuç:", result)
}

Exception mekanizması yok. Hata, sıradan bir değer. Fonksiyon çağrısından çıkan değerlerin bir parçası.

if err != nil neden bu kadar tekrar ediyor

Go kodu okurken if err != nil bloklarının her yerde tekrar ettiğini görürsünüz. Bu şikâyet sık duyuluyor. Ama bunu bir eksiklikten ziyade kasıtlı bir karar olarak okumak gerekiyor.

PHP veya Java’da bir istisna fırlatıldığında, programın hangi noktada durduğu ve hangi catch bloğunun devreye girdiği her zaman net değildir. Exception, call stack’i atlayarak yukarıya tırmanır. Gözden kaçması kolaydır.

Go’da hata, tam olarak üretildiği noktada elimde. Onu görmezden gelmek aktif bir tercih — ve derleyici (compiler) bunu zorlamaz ama linter uyarır. Hatayı ele almak bir seçim değil, akışın bir parçası.

Bunu PHP’de yaşadığım somut bir durumla karşılaştırayım: bir dış API çağrısı sessizce başarısız oldu, istisna bir üst katmanda yakalandı ama loglama eksikti. Sorunu ancak kullanıcı şikâyeti gelince fark ettim. Go’da bu çağrı şöyle yazılırdı ve hata yayılacak yer açıkça görünürdü:

resp, err := apiClient.Call(payload)
if err != nil {
    return fmt.Errorf("dış API çağrısı başarısız: %w", err)
}

Hata sarmalamak (wrapping)

fmt.Errorf ve Go 1.13 ile gelen %w verb, hata bağlamını zenginleştirmenin standart yolu haline geldi. Go ekibinin duyurusundaki tanımıyla: %w kullanıldığında fmt.Errorf’un döndürdüğü hata, %w’ye verilen argümanı geri veren bir Unwrap metoduna sahip olur.

func loadUserConfig(userID int) (Config, error) {
    data, err := readFile(fmt.Sprintf("config/%d.json", userID))
    if err != nil {
        return Config{}, fmt.Errorf("loadUserConfig: kullanıcı %d için yapılandırma okunamadı: %w", userID, err)
    }

    var cfg Config
    if err := json.Unmarshal(data, &cfg); err != nil {
        return Config{}, fmt.Errorf("loadUserConfig: JSON ayrıştırma hatası: %w", err)
    }

    return cfg, nil
}

%w sayesinde orijinal hata sarmalanır ama errors.Is ve errors.As ile hâlâ sorgulanabilir — ilki hatayı bir değerle karşılaştırır, ikincisi belirli bir tipte olup olmadığını sınar; ikisi de zincirin tamamına bakar:

cfg, err := loadUserConfig(42)
if err != nil {
    if errors.Is(err, os.ErrNotExist) {
        // Dosya yok durumunu özel olarak ele al
    }
    log.Printf("Yapılandırma yüklenemedi: %v", err)
    return
}

Sarmalama ile doğrudan döndürme arasındaki seçim önemli. Aynı duyuru bunun için bir ölçüt de veriyor: hatayı çağırana incelemesi için açmak istiyorsanız sarmalayın, ama sarmalamak uygulama ayrıntısını dışarı sızdıracaksa sarmalamayın. return err yaptığınızda orijinal hata bilgisi korunuyor ama bağlam yok. fmt.Errorf ile sarmak, hata mesajına “kim, ne, nerede” bilgisi eklemenizi sağlıyor. Uzun bir çağrı zincirinizde hata mesajının neyin nerede başarısız olduğunu söylemesi, log içinde kayıp bir satır aramaktan çok daha hızlı.

Özel hata tipleri

Yalnızca mesaj taşımak yetmediğinde kendi error tipinizi tanımlayabilirsiniz:

type NotFoundError struct {
    Resource string
    ID       int
}

func (e *NotFoundError) Error() string {
    return fmt.Sprintf("%s bulunamadı (id: %d)", e.Resource, e.ID)
}

func findUser(id int) (*User, error) {
    user := db.Find(id)
    if user == nil {
        return nil, &NotFoundError{Resource: "Kullanıcı", ID: id}
    }
    return user, nil
}

Çağıran taraf errors.As ile bu tip üzerinden dal açabilir:

u, err := findUser(99)
if err != nil {
    var notFound *NotFoundError
    if errors.As(err, &notFound) {
        // 404 yanıt ver
    }
    // Diğer hatalar
}

PHP geliştiricisi olarak uyum süreci

Exception tabanlı bir dilden gelince en büyük alışkanlık değişimi şu: hataları görmezden gelmek artık pasif değil, aktif bir tercih gerektiriyor. _ ile hata dönüşünü ıskalamak Go topluluğunda kötü pratik kabul ediliyor — ve haklı olarak.

İkinci büyük fark: her hata ayrı ayrı ele alınıyor. PHP’de geniş bir try-catch bloğuyla on farklı hatayı tek noktada yakalıyorsunuz. Go’da her fonksiyon çağrısının yanında bir kontrol var. Başta fazla görünüyor; zamanla hatanın tam olarak nerede ve ne zaman oluştuğunu görmenin değerini anlıyorsunuz.

Bir de pek anlatılmayan bir avantajı var: kodun okunuşunda hata yolları ve başarı yolu açıkça ayrışıyor. Herhangi bir satırda “bu çağrı başarısız olursa ne olur?” sorusunun cevabı hemen bir satır aşağıda görünüyor. Derin bir exception hiyerarşisi izlemek yerine, doğrusal bir akışı takip ediyorsunuz.

Dilin bu yaklaşımı benimsemesinin bedeli söz dizimi (syntax) düzeyinde tekrar, kazancı ise hata akışının şeffaflığı. Bu trade-off size uymuyorsa Go size uymayabilir — ama projeye girmeye değer bir dürüstlük bu.


Kaynaklar

Bu konudaki deneyler

Dile özgü serileştirme yerine dondurulmuş bir JSON zarfı; polyglot kuyruk standardı BabelQueue'ya dönüştü.

Şu an ne yapıyor

Dondurulmuş JSON zarfı dört dilde — PHP, Python, Go, Node.js — aynı baytları okutuyor; sidecar ya da broker eklentisi gerekmiyor. Polyglot kuyruk kuran ekipler spesifikasyonu ve SDK'ları bugün kullanabilir.

Açık kaynak JSON Redis RabbitMQ +4 daha
Mart 2026 — Haziran 2026

Diff'i makineden çıkarmadan bir modele denetleten, sağlayıcıdan bağımsız Go CLI; CommitBrief ürününe dönüştü.

Şu an ne yapıyor

Diff hiç makineden çıkmadan yerel kod incelemesi yapıyor; `Provider` arayüzü sayesinde Anthropic, OpenAI, Gemini ve Ollama aynı sözleşmeyle takılıyor. İmzalı binary'ler yayında — diff'ini bir bulut sağlayıcısına vermek istemeyen herkes kurabilir.

Açık kaynak Go Cobra go-git +4 daha
Ocak 2026 — Nisan 2026
Etiketler: #Go
Uzmanlık: Go Geliştirici
Paylaş:

Son güncelleme:

Yorumlar

Yorum yapmak için GitHub hesabınızla giriş yapmanız yeterli. Yorumlar GitHub Discussions üzerinde saklanır.

İlgili Yazılar

Sitede Ara

Yazı, proje ve sayfalarda arama yapmak için yazmaya başlayın.

Esc ile kapat Pagefind ile güçlendirildi