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ğıranlarerr != nilolduğunda onları kullanmamalıdır. - Fonksiyon yalnızca bir hata döndürüyorsa
if err := f(); err != nil,err'in kapsamınıifile 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.ErrNoRowsgibi sentinel'ler)==ile değilerrors.Isile karşılaştırın. Hata sarmalandığı anda==başarısız olur. - Tipli bir hatayı aynı nedenle type assertion ile değil
errors.Asile çı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.Writerbö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.Iskullanın. - Nedeni
%vile kaybetmek. Amaç onu gizlemek değilse%wkullanın. - Tipli nil bir pointer'ı
errorolarak döndürmek.var e *MyErr; return eçağıran için nil değildir. Düz birnildö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.