Menu

Golang Hata Yönetimi: if err != nil, Sarmalama, Kontrol

Go hataları fonksiyonlardan döndürülen sıradan değerler olarak ele alır. error interface'ini, if err != nil'i, errors.New ve fmt.Errorf'u, hataları bağlamla döndürmeyi, errors.Is ve errors.As ile kontrol etmeyi ve her hatayı bir kez ele almayı öğrenin.

Bu sayfada çalıştırılabilir editörler var - düzenle, çalıştır ve sonucu anında gör.

Hatalar değerdir

Başarısız olabilecek bir Go fonksiyonu son sonuç olarak bir error döndürür. Çağıran onu hemen kontrol eder.

Çıktı:

parsed: 42
could not parse: strconv.Atoi: parsing "forty-two": invalid syntax

Mekanizmanın tamamı bu. Exception yok, try ya da catch yok ve gizli kontrol akışı yok: bir hata yalnızca kodunuzun onu aktardığı yere gider. Bedeli, if err != nil'in görünür tekrarıdır. Faydası, her hata noktasının sayfada görünür olması ve her birinde ne olacağına sizin karar vermenizdir.

error tipi

error, tek metodu olan yerleşik bir interface'tir:

type error interface {
	Error() string
}

Error() string metodu olan her tip bir hatadır. nil bir hata başarı demektir. Bir hatayı fmt.Println(err) ya da %v ile yazdırmak Error()'ı çağırır.

Hata oluşturmak

Çoğu durumu iki fonksiyon karşılar.

errors.New sabit metinli bir hata oluşturur. fmt.Errorf, Printf ile aynı verb'lerle bir hata biçimlendirir. Hata string'leri geleneksel olarak küçük harfle başlar ve sonunda noktalama işareti olmaz, çünkü genellikle daha uzun mesajların içine yerleştirilirler: load config: open app.yaml: no such file or directory.

if err != nil kalıbı

Deyimsel biçim şudur: çağır, kontrol et, erken dön. Başarı yolu sol kenarda kalır ve her başarısızlık olduğu anda çıkar.

func loadUser(id int) (*User, error) {
	row, err := db.Query(id)
	if err != nil {
		return nil, err
	}
	u, err := parseUser(row)
	if err != nil {
		return nil, err
	}
	if err := u.Validate(); err != nil {
		return nil, err
	}
	return u, nil
}

Dikkat edilecek iki gelenek:

  • Hata durumunda diğer sonuçlar için sıfır değeri döndürün (nil, 0, ""). Çağıranlar err != nil olduğunda onları kullanmamalıdır.
  • Fonksiyon yalnızca bir hata döndürüyorsa if err := f(); err != nil, err'in kapsamını if ile sınırlar. Dış kapsamı temiz tutar.

Bir hata dönüşünden sonra else'ten kaçının. if err != nil { return err } else { ... } mutlu yolu nedensiz yere içeri kaydırır.

Bir hatayı döndürürken bağlam eklemek

Değiştirilmeden yukarı aktarılan bir hata nereden geldiğinin hikâyesini kaybeder. open config.yaml: no such file or directory, başlangıcın hangi adımının başarısız olduğunu söylemez. fmt.Errorf ve %w verb'ü ile bağlam ekleyin:

Çıktı:

start server: read config: open /etc/myapp/config.yaml: no such file or directory
true

Her katman ne yaptığını ekler ve son mesaj, çağrının en üstünden nedene kadar inen bir iz gibi okunur. İyi bağlam işlemi ve girdiyi adlandırır: parse line 12, fetch user 42. Her seviyeye "error" ya da "failed" eklemeyin; mesaj zaten bir hatadır.

%w sarmalar: orijinal hatayı yenisinin içinde tutar, böylece errors.Is ve errors.As onu hâlâ bulabilir. %v yalnızca metni kopyalar. Bir uygulama ayrıntısını çağıranlardan bilerek gizlemek istediğinizde %v kullanın; örneğin bir veritabanı sürücüsünün hata tipine bağımlı hale gelmesinler diye.

Belirli hataları kontrol etmek: errors.Is ve errors.As

Bazen çağıranın tek bir başarısızlık türüne tepki vermesi gerekir: eksik bir dosya "varsayılanları kullan", bir timeout "yeniden dene" demektir. Bunu iki fonksiyon yanıtlar ve ikisi de sarmalamanın her katmanının içine bakar.

Pratik kurallar:

  • Önceden tanımlanmış hata değerleriyle (io.EOF, os.ErrNotExist, sql.ErrNoRows gibi sentinel'ler) == ile değil errors.Is ile karşılaştırın. Hata sarmalandığı anda == başarısız olur.
  • Tipli bir hatayı aynı nedenle type assertion ile değil errors.As ile çıkarın. errors.As, hedef tipte bir değişkene pointer alır.
  • Asla err.Error() metnine göre eşleştirme yapmayın. Mesajlar sürümler arasında değişir ve metin eşleştirme o zaman sessizce bozulur.

Kendi sentinel hatalarınızı ve hata tiplerinizi tanımlamak ve birkaç hatayı errors.Join ile birleştirmek özel hatalar sayfasında anlatılıyor.

Bir hatayı bir kez ele alın

Bir hata tam olarak bir kez ele alınmalıdır. Ele almak şunlardan biri demektir: onu (genellikle sarmalayarak) döndürmek, loglayıp devam etmek, yeniden denemek ya da kullanıcı için bir yanıta dönüştürmek. Bunlardan ikisini yapmak Go kodundaki en yaygın hata yönetimi hatasıdır.

// Wrong: logged here, and returned, so it is logged again by every caller.
if err != nil {
	log.Printf("could not fetch user: %v", err)
	return err
}

// Right: add context and return. The top of the program logs once.
if err != nil {
	return fmt.Errorf("fetch user %d: %w", id, err)
}

Loglayıp döndürmek aynı başarısızlığı loglarda birkaç kez, her seferinde son mesajdan daha az bağlamla üretir. Hataların ne yapılacağına karar verebilen yere (bir HTTP handler'ı, bir main, bir worker döngüsü) kadar yukarı akmasına izin verin ve orada loglayın.

Hatalar nerede son bulur

Programın en üstünde bir şeyin hataya göre davranması gerekir. main içinde bu genellikle onu yazdırıp sıfır olmayan bir durumla çıkmak demektir:

Bunu argümansız çalıştırmak stderr'e error: usage: app <name> yazdırır ve 1 durumuyla çıkar (diğer yolu görmek için Args paneline bir ad yazın). main'i bu biçimde tutup asıl işi run içinde yapmak programı test edilebilir kılar ve os.Exit ertelenmiş çağrıları atladığı için run içindeki defer ifadelerinin yine çalışması anlamına gelir.

Bir HTTP sunucusunda en üst, handler'dır: hatayı bir durum koduna ve istemci için güvenli bir mesaja eşler, ayrıntılı mesajı ise sizin için loglar.

Yok sayabileceğiniz hatalar ve sayamayacaklarınız

Bir hatayı yok saymak bazen doğrudur, ama okuyanlar bunun bir karar olduğunu bilsin diye _ ile açık hale getirin:

_ = conn.SetDeadline(t) // best effort

Bazı çağrılar pratikte başarısız olamaz (strings.Builder.WriteString, bytes.Buffer.Write). Diğerleri zararsız görünür ama öyle değildir: yazdığınız bir dosyadaki Close, verinin diske hiç ulaşmadığını bildirebilir ve json.Marshal channel'larda ve fonksiyonlarda başarısız olur. Emin değilseniz kontrol edin.

errcheck linter'ı (golangci-lint'e dahildir) kontrol edilmemiş hataları raporlar. go vet tek başına onları işaretlemez.

Hatalar ve panic'ler

Go'da panic de vardır, ama bir exception sistemi değildir. Normal çalışmada ters gidebilecek her şey için hataları kullanın: hatalı girdi, eksik dosyalar, ağ başarısızlıkları. Panic'i bug'lar (imkânsız bir durum, bozulmuş bir değişmez) ve devam etmenin anlamsız olduğu başlangıç başarısızlıkları için ayırın. Bir kütüphane API'si üzerinden neredeyse hiçbir zaman panic yapmamalıdır. Panic ve recover sayfasına bakın.

Tekrarı azaltmak

if err != nil uzundur ve bunun için yeni söz dizimi ekleme önerileri defalarca reddedildi; Go ekibi 2025'te hata yönetimi için söz dizimi değişikliklerini artık takip etmediğini duyurdu. Bazı kalıplar gürültüyü dilin içinde azaltır:

  • Erken dönün ve fonksiyonları küçük tutun. Tekrarın çoğu, birçok adım yapan uzun fonksiyonlardan gelir.
  • Yapışkan hata. Bir dizi yazma işlemi için ilk hatayı bir struct alanında tutun ve ayarlandıktan sonra sonraki çağrıları etkisiz yapın. bufio.Writer böyle çalışır: hatayı Flush'tan sonra bir kez kontrol edersiniz.
  • Fonksiyon başına bir kez sarmalayın. Adlı bir sonuç üzerindeki ertelenmiş bir closure, fonksiyonun döndürdüğü her hataya aynı bağlamı ekleyebilir (bkz. defer).

Sık yapılan hatalar

  • err nil değilken bir değeri kullanmak. Önce kontrol edin, sonra kullanın.
  • Loglayıp döndürmek. Birini seçin.
  • Sarmaladıktan sonra == ile karşılaştırmak. errors.Is kullanın.
  • Nedeni %v ile kaybetmek. Amaç onu gizlemek değilse %w kullanın.
  • Tipli nil bir pointer'ı error olarak döndürmek. var e *MyErr; return e çağıran için nil değildir. Düz bir nil döndürün.
  • Büyük harfle başlayan ya da noktalama işaretli mesajlar. errors.New("Failed to connect.") sarmalandığında kötü okunur. connect to db: ... yazın.

Sıkça Sorulan Sorular

Go'da hata yönetimi nasıl çalışır?

Başarısız olabilecek fonksiyonlar son sonuç olarak bir error döndürür. Çağıran onu hemen kontrol eder: v, err := f(); if err != nil { return err }. Bir error, tek metodu Error() string olan sıradan bir interface değeridir ve nil başarı demektir. Exception yoktur.

Go'da try/catch var mı?

Hayır. Go'da exception ve try/catch yoktur. Beklenen başarısızlıklar error değerleri olarak döndürülür ve if err != nil ile kontrol edilir. panic ve recover vardır, ama normal hata akışı için değil, programlama hataları ve kurtarılamaz durumlar içindir.

Go'da hata nasıl döndürülür?

Son sonuç olarak error tanımlayın ve başarıda nil döndürün. Sabit metin için errors.New("message"), aldığınız bir hataya bağlam eklemek için fmt.Errorf("reading %s: %w", name, err) ile hata oluşturun. Başarısızlıkta diğer sonuçlar için sıfır değerler döndürün.

fmt.Errorf içinde %w ile %v arasındaki fark nedir?

İkisi de orijinal hatanın mesajını yenisine koyar. %w ayrıca onu sarmalar, böylece errors.Is ve errors.As orijinali hâlâ bulabilir. %v yalnızca metni taşıyan yeni bir hata üretir. Çağıranların nedeni kontrol etmesi gerekebilecekse %w, gizlemek istediğinizde %v kullanın.

Go'da hangi hatanın döndürüldüğü nasıl kontrol edilir?

io.EOF ya da os.ErrNotExist gibi bir sentinel hatayla karşılaştırmak için errors.Is(err, target), *fs.PathError gibi belirli bir hata tipini çıkarmak için errors.As(err, &target) kullanın. İkisi de sarmalanmış hataların içine bakar. err.Error() string'lerini karşılaştırmaktan kaçının.

Coddy programming languages illustration

Coddy ile kodlamayı öğren

BAŞLA