Go'da hatalar istisna (exception) olarak fırlatılmaz, değer olarak döndürülür. Hata olabilecek bir fonksiyon son dönüş değeri olarak error tipinde bir değer verir; çağıran taraf if err != nil ile kontrol edip hatayı ele alır ya da bağlam ekleyerek yukarı iletir. error, tek metodu Error() string olan yerleşik bir arayüzdür. Basit hatalar errors.New veya fmt.Errorf ile oluşturulur; %w fiiliyle bir hata başka bir hatayı sarmalayabilir ve errors.Is, errors.As ile errors.AsType bu zinciri dolaşarak belirli bir hatayı veya hata tipini arar.

Bu yazıda hata döndürme deyimini, sentinel hataları, özel hata tiplerini, %w ile sarmalamayı, errors.Is/As/AsType/Join/Unwrap fonksiyonlarını, özel Is metodunu ve hatanın ne zaman sarmalanması gerektiğini Go 1.27.3 ile çalıştırılmış örneklerle anlatıyoruz. Kurtarılamaz durumlar için kullanılan panic ve recover defer, panic ve recover yazısında ele alınıyor. Serinin tamamı için Go öğrenme rehberi sayfasına bakabilirsiniz.

error arayüzü ve temel deyim

error arayüzü ve temel deyim

Effective Go'ya göre hatalar geleneksel olarak error tipindedir ve bu basit yerleşik arayüz şöyle tanımlıdır: type error interface { Error() string }. Go'nun çoklu dönüş değeri sayesinde fonksiyon hem normal sonucu hem de hatayı döndürebilir. Başarı durumunda hata nil'dir. Aşağıdaki örnek, Go Blog'daki hata sözdizimi yazısında kullanılan printSum örneğine benziyor:

go
package main

import (
	"fmt"
	"strconv"
)

func topla(a, b string) (int, error) {
	x, err := strconv.Atoi(a)
	if err != nil {
		return 0, err
	}
	y, err := strconv.Atoi(b)
	if err != nil {
		return 0, err
	}
	return x + y, nil
}

func main() {
	if t, err := topla("40", "2"); err != nil {
		fmt.Println("hata:", err)
	} else {
		fmt.Println("toplam:", t)
	}

	if _, err := topla("40", "iki"); err != nil {
		fmt.Println("hata:", err)
	}
}

// Çıktı:
// toplam: 42
// hata: strconv.Atoi: parsing "iki": invalid syntax

Hata metninin strconv.Atoi: önekiyle başladığına dikkat edin. Effective Go, hata metinlerinin mümkünse kaynağını, yani hatayı üreten işlemi veya paketi belirtmesini önerir; image paketindeki "image: unknown format" bunun örneğidir. Aynı belge, hatayı _ ile yok saymanın "berbat bir uygulama" olduğunu açıkça söyler: fi, _ := os.Stat(path) yazıp sonra fi.IsDir() çağırmak, dosya yoksa programı çökertir. strconv hatalarının ayrıntısı strconv yazısında anlatılıyor.

if err != nil bloklarının çokluğu Go hakkında en sık dile getirilen eleştirilerden biridir. Go Blog'daki "[ On | No ] syntactic support for error handling" yazısı, yıllar içinde check/handle ve try gibi önerilerin değerlendirildiğini ve Go ekibinin öngörülebilir gelecekte hata yönetimi için sözdizimi değişiklikleri peşinde koşmayı bıraktığını duyurur. "Errors are values" yazısı ise hataların programlanabilir değerler olduğunu, tekrarı azaltmak için bu değerlerle kod yazılabileceğini anlatır.

Sentinel hatalar ve %w ile sarmalama

Paket düzeyinde tanımlanan ve karşılaştırma için kullanılan hata değerlerine sentinel hata denir: io.EOF, fs.ErrNotExist gibi. Standart kütüphanedeki örneklerin çoğu Err önekiyle adlandırılır. Bir hatayı yukarı iletirken bağlam eklemek istediğinizde fmt.Errorf kullanırsınız. Go 1.13 ile gelen %w fiili, oluşan hatanın içindeki hatayı korumasını, yani onu sarmalamasını sağlar; fmt belgesine göre %w bunun dışında %v ile aynıdır:

go
package main

import (
	"errors"
	"fmt"
)

var ErrBulunamadi = errors.New("kayıt bulunamadı")

func bul(id int) error {
	if id != 1 {
		return ErrBulunamadi
	}
	return nil
}

func profilGetir(id int) error {
	if err := bul(id); err != nil {
		return fmt.Errorf("profil %d: %w", id, err)
	}
	return nil
}

func eskiUsul(id int) error {
	if err := bul(id); err != nil {
		return fmt.Errorf("profil %d: %v", id, err)
	}
	return nil
}

func main() {
	err := profilGetir(7)
	fmt.Println(err)
	fmt.Println(err == ErrBulunamadi, errors.Is(err, ErrBulunamadi))
	fmt.Println(errors.Unwrap(err) == ErrBulunamadi)

	err = eskiUsul(7)
	fmt.Println(err)
	fmt.Println(errors.Is(err, ErrBulunamadi))

	a, b := errors.New("x"), errors.New("x")
	fmt.Println(a == b, errors.Is(a, b))
}

// Çıktı:
// profil 7: kayıt bulunamadı
// false true
// true
// profil 7: kayıt bulunamadı
// false
// false false

Örnekten çıkan dört sonuç var. Birincisi, sarmalanmış hata artık sentinel'e == ile eşit değildir; errors.Is ise zinciri dolaştığı için eşleşmeyi bulur. errors paket belgesi bu yüzden err == fs.ErrExist yerine errors.Is(err, fs.ErrExist) yazmayı önerir. İkincisi, %v ile oluşturulan hata ekranda aynı metni gösterir ama içindeki hatayı korumaz, bu yüzden errors.Is false döner. Üçüncüsü, errors.Unwrap bir kat soyar. Dördüncüsü, belgeye göre her errors.New çağrısı metin aynı olsa bile farklı bir hata değeri döndürür; sentinel hataların paket düzeyinde bir kez tanımlanmasının nedeni budur.

Özel hata tipleri, errors.As ve errors.AsType

Özel hata tipleri, errors.As ve errors.AsType

Hata hakkında metinden fazlasını taşımak istediğinizde Error() metodu olan kendi tipinizi tanımlarsınız. Effective Go'daki os.PathError örneği işlemi, dosya yolunu ve alttaki sistem hatasını alanlarda tutar. Çağıran taraf belirli bir hata tipini aramak için Go 1.26'dan beri errors.AsType kullanabilir; Go 1.26 sürüm notlarına göre bu fonksiyon errors.As'ın generic sürümüdür, tip güvenlidir, daha hızlıdır ve çoğu durumda kullanımı daha kolaydır:

go
package main

import (
	"errors"
	"fmt"
)

type DogrulamaHatasi struct {
	Alan  string
	Neden string
}

func (e *DogrulamaHatasi) Error() string {
	return e.Alan + ": " + e.Neden
}

func kaydet(eposta string) error {
	if eposta == "" {
		return fmt.Errorf("kayıt başarısız: %w",
			&DogrulamaHatasi{Alan: "eposta", Neden: "boş olamaz"})
	}
	return nil
}

func main() {
	err := kaydet("")
	fmt.Println(err)

	// Go 1.26 ve sonrası: errors.AsType
	if dh, ok := errors.AsType[*DogrulamaHatasi](err); ok {
		fmt.Println("alan:", dh.Alan)
	}

	// Eski ama hâlâ geçerli biçim: errors.As
	var dh *DogrulamaHatasi
	if errors.As(err, &dh) {
		fmt.Println("neden:", dh.Neden)
	}

	// Doğrudan tip iddiası sarmalanmış hatayı görmez.
	_, ok := err.(*DogrulamaHatasi)
	fmt.Println(ok)
}

// Çıktı:
// kayıt başarısız: eposta: boş olamaz
// alan: eposta
// neden: boş olamaz
// false

errors.As'a verilen hedef, aranan tipte bir değişkenin işaretçisidir; tip zaten işaretçi olduğunda bu "işaretçinin işaretçisi" anlamına gelir. Go 1.13 hata yazısı bunun garip görünse de doğru olduğunu, hedefin hata tipindeki bir değere işaretçi olarak düşünülmesi gerektiğini açıklar. Paket belgesine göre As, hedef error'u uygulayan bir tipe ya da herhangi bir arayüz tipine işaret eden nil olmayan bir işaretçi değilse panik verir. AsType bu riski tip parametresiyle derleme anına taşır. Son satır ise neden doğrudan tip iddiasının (type assertion) sarmalanmış hatalarda yetersiz kaldığını gösteriyor.

Standart kütüphanedeki hatalarla aynı desen şöyle çalışır. Hata metni işletim sistemine göre değiştiği için yalnızca alanları yazdırıyoruz:

go
package main

import (
	"errors"
	"fmt"
	"io/fs"
	"os"
)

func main() {
	_, err := os.Open("olmayan-dosya.txt")
	if errors.Is(err, fs.ErrNotExist) {
		fmt.Println("dosya yok")
	}
	if pe, ok := errors.AsType[*fs.PathError](err); ok {
		fmt.Println("işlem:", pe.Op, "yol:", pe.Path)
	}
}

// Çıktı:
// dosya yok
// işlem: open yol: olmayan-dosya.txt

Dosya işlemlerinin geri kalanı dosya okuma ve yazma yazısında anlatılıyor.

Go 1.13 yazısı errors.Unwrap fonksiyonunun da bulunduğunu, ama genellikle errors.Is ve errors.As'ı tercih etmenin daha iyi olduğunu belirtir; çünkü bu fonksiyonlar zincirin tamamını tek çağrıda inceler. Küçük bir performans ayrıntısı olarak Go 1.26 sürüm notları, biçimlendirme içermeyen fmt.Errorf("x") çağrısının artık daha az bellek ayırdığını ve genellikle errors.New("x") ile aynı ayırma davranışını gösterdiğini söyler; yani bağlamsız sabit hatalar için ikisi arasındaki seçim çoğunlukla bir üslup tercihidir.

Birden çok hata: errors.Join ve çoklu %w

Go 1.20 sürüm notlarına göre bir hata, []error döndüren bir Unwrap metoduyla birden çok hatayı sarmalayabilir; errors.Is ve errors.As bu yapıyı inceleyecek şekilde güncellendi, fmt.Errorf birden çok %w kabul etmeye başladı ve errors.Join eklendi. Doğrulama gibi bütün sorunları toplu bildirmek istediğiniz durumlar için idealdir:

go
package main

import (
	"errors"
	"fmt"
)

var (
	ErrAd    = errors.New("ad boş")
	ErrYas   = errors.New("yaş negatif")
	ErrSehir = errors.New("şehir boş")
)

func dogrula(ad string, yas int, sehir string) error {
	var hatalar []error
	if ad == "" {
		hatalar = append(hatalar, ErrAd)
	}
	if yas < 0 {
		hatalar = append(hatalar, ErrYas)
	}
	if sehir == "" {
		hatalar = append(hatalar, ErrSehir)
	}
	return errors.Join(hatalar...) // hepsi nil ise nil döner
}

func main() {
	err := dogrula("", -1, "İzmir")
	fmt.Println(err)
	fmt.Println(errors.Is(err, ErrYas), errors.Is(err, ErrSehir))
	fmt.Println(dogrula("Can", 20, "Van") == nil)

	iki := fmt.Errorf("adım 3: %w; ayrıca %w", ErrAd, ErrSehir)
	fmt.Println(iki, errors.Is(iki, ErrSehir))
	fmt.Println(errors.Unwrap(iki) == nil)
}

// Çıktı:
// ad boş
// yaş negatif
// true false
// true
// adım 3: ad boş; ayrıca şehir boş true
// true

Paket belgesine göre Join nil değerleri atar, hepsi nil ise nil döndürür ve metni, her hatanın metnini satır sonuyla ayırarak birleştirir. Son satır önemli bir ayrıntıyı gösteriyor: errors.Unwrap yalnızca Unwrap() error biçimindeki metodu çağırır, []error döndüren sarmalayıcıları açmaz ve nil döndürür. Çoklu hatalarda Unwrap yerine her zaman Is ve AsType kullanın; bu fonksiyonlar hata ağacını önce kendisi, sonra çocukları olmak üzere derinlik öncelikli dolaşır.

Özel eşleşme: Is metodu

Bir hata tipi, Is(error) bool metodu tanımlayarak kendisini başka bir hatayla eşdeğer ilan edebilir. Aşağıdaki HTTPHatasi, 5xx kodlarında kendisini ErrGecici sayıyor; böylece çağıran taraf kodları tek tek bilmeden "yeniden denenebilir mi?" sorusunu sorabiliyor:

go
package main

import (
	"errors"
	"fmt"
)

var ErrGecici = errors.New("geçici hata")

type HTTPHatasi struct{ Kod int }

func (e HTTPHatasi) Error() string { return fmt.Sprintf("HTTP %d", e.Kod) }

// Is: 5xx kodlarını geçici hata ile eşdeğer sayar.
func (e HTTPHatasi) Is(hedef error) bool {
	return hedef == ErrGecici && e.Kod >= 500
}

func main() {
	e1 := fmt.Errorf("istek: %w", HTTPHatasi{503})
	e2 := fmt.Errorf("istek: %w", HTTPHatasi{404})
	fmt.Println(errors.Is(e1, ErrGecici), errors.Is(e2, ErrGecici))
	fmt.Println(errors.Is(e2, HTTPHatasi{404}))
}

// Çıktı:
// true false
// true

Son satırda Is metodu false döndürse de errors.Is hedefe eşit olan hatayı da eşleşme saydığı için, karşılaştırılabilir bir struct olan HTTPHatasi{404} hedefle eşit bulunuyor. Paket belgesi, Is metodunun yalnızca yüzeysel karşılaştırma yapmasını ve kendi içinde Unwrap çağırmamasını ister; zinciri dolaşmak errors.Is'in işidir. Standart kütüphanedeki errors.ErrUnsupported (Go 1.21) da bu deseni kullanır: belgeye göre fonksiyonlar bu hatayı doğrudan döndürmek yerine, errors.Is(err, errors.ErrUnsupported) sorgusunu karşılayan bağlamlı bir hata döndürmelidir.

Sarmalamalı mı, sarmalamamalı mı?

Go 1.13 hata yazısının belki de en önemli bölümü budur. Bir hatayı %w ile sarmalamak, onu çağıranlara açmak demektir; yani o hata API'nizin bir parçası olur. Yazının örneğinde LookupUser fonksiyonu içeride database/sql kullanıyorsa ve sql.ErrNoRows'u %w ile sarmalarsa, çağıranlar errors.Is(err, sql.ErrNoRows) yazmaya başlar; daha sonra veritabanı paketini değiştirdiğinizde onları bozmamak için yine bu hatayı döndürmek zorunda kalırsınız. Kural şu: hatayı çağıranlara açmak istediğinizde sarmalayın, uygulama ayrıntısını açığa çıkaracaksa sarmalamayın, %v kullanın. Hangisini seçerseniz seçin hata metni aynıdır; seçim yalnızca programların hatayı inceleyip inceleyemeyeceğini belirler.

Paket API'sinde hataları belgelemek

Aynı yazının "Errors and package APIs" bölümü, hata döndüren bir paketin, programcıların bu hataların hangi özelliklerine güvenebileceğini belgelemesi gerektiğini söyler. En basit sözleşme, işlemin ya başarılı olup nil ya da başarısız olup nil olmayan bir hata döndürmesidir; çoğu durumda bundan fazlası gerekmez. "Öğe bulunamadı" gibi ayırt edilebilir bir durum bildirmek istiyorsanız, fonksiyonun belgesine bu durumda ErrNotFound'u sarmalayan bir hata döndürdüğünü yazar ve gerçekten fmt.Errorf("%q: %w", name, ErrNotFound) döndürürsünüz.

Yazının dikkat çekici bir önerisi, sentinel'i bile doğrudan değil sarmalanmış olarak döndürmektir: return fmt.Errorf("%w", ErrPermission). Doğrudan ErrPermission döndürürseniz çağıranlar err == pkg.ErrPermission yazmaya alışır ve ileride hataya bağlam eklediğiniz anda kodları bozulur. Sarmalanmış hata çağıranları baştan errors.Is kullanmaya yönlendirir. Ters yönde, başka bir paketten gelen ve uygulama ayrıntısı olan bir hatayı (yazıdaki örnekte os.Open'ın döndürdüğü *os.PathError) dışarı açmak istemiyorsanız fmt.Errorf("%v", err) ile aynı metinli yeni bir hata oluşturursunuz.

errors paketi özet tablosu

FonksiyonİşiSürüm notu
errors.New(metin)Yalnızca metin taşıyan yeni ve benzersiz bir hataGo 1.13 öncesinden beri
fmt.Errorf("...%w", err)Bağlam ekler ve err değerini sarmalar%w Go 1.13, çoklu %w Go 1.20
errors.Is(err, hedef)Zincirde hedefe eşit ya da Is ile eşleşen hata var mıGo 1.13
errors.As(err, &hedef)Zincirde hedefin tipine atanabilen hatayı bulup hedefe yazarGo 1.13
errors.AsType[E](err)As işinin generic sürümü, (E, bool) döndürürGo 1.26
errors.Unwrap(err)Unwrap() error metodunu bir kez çağırırGo 1.13
errors.Join(errs...)Birden çok hatayı tek hatada birleştirirGo 1.20

Sık yapılan hatalar

Sık yapılan hatalar: Hatayı yok saymak, == ile karşılaştırmak, Doğrudan tip iddiası, nil işaretçiyi error olarak döndürmek
  • Hatayı yok saymak: v, _ := f() yazmak, hata durumunda geçersiz bir v ile devam etmek demektir. Effective Go bunu açıkça kötü uygulama sayar.
  • == ile karşılaştırmak: Sarmalanmış hatalarda err == ErrX false verir; errors.Is kullanın.
  • Doğrudan tip iddiası: err.(*PathError) zincirin içine bakmaz; errors.AsType kullanın.
  • nil işaretçiyi error olarak döndürmek: var e *Hata; return e nil olmayan bir error üretir. FAQ'ın önerisi: imzada error kullanın ve açıkça return nil yazın (bkz. interface yazısı).
  • %w operandına işaretçi vermek: Go 1.27 sürüm notlarına göre go vet'in printf denetimi artık E değer tipi error'u uyguluyorken %w'ye *E verilmesini raporluyor; bu, tip iddiası ve errors.Is denetimlerini sessizce başarısız kılan gizli bir hatadır. Go 1.27.3'te bu durum %w wants operand of error type ZamanAsimi, not pointer type *ZamanAsimi (defeats errors.Is) uyarısını verir.
  • Her katmanda hatayı hem loglayıp hem döndürmek: Aynı hata günlükte defalarca görünür. Hatayı ya ele alın (loglayın, yeniden deneyin) ya da bağlam ekleyip iletin.
  • Her hata için panic kullanmak: Effective Go'ya göre hata bildirmenin olağan yolu ek dönüş değeri olarak error döndürmektir; panik, programın devam edemeyeceği durumlar içindir.

Sık Sorulan Sorular

Go'da try/catch var mı?

Yoktur. Hatalar dönüş değeridir ve if err != nil ile kontrol edilir. panic ve recover mekanizması vardır ama olağan hata yönetimi için değil, kurtarılamaz durumlar için kullanılır (bkz. defer, panic ve recover).

errors.As mı, errors.AsType mı kullanmalıyım?

Go 1.26 veya sonrasını hedefliyorsanız paket belgesi çoğu kullanım için AsType'ı önerir; hedef değişken tanımlamanız gerekmez ve yanlış hedef tipinden kaynaklanan panik riski ortadan kalkar. Projeniz daha eski sürümleri desteklemek zorundaysa errors.As kullanmaya devam edin; go.mod'daki go satırı projenizin gerektirdiği en düşük Go sürümünü gösterir (bkz. go mod yazısı).

Hata metinleri nasıl yazılmalı?

Effective Go, metnin hatanın kaynağını belirtmesini önerir: paket adı veya işlem öneki gibi. Örneklerdeki strconv.Atoi: parsing "iki": invalid syntax ve open /etc/passwx: no such file or directory bu biçimdedir. Bağlam eklerken fmt.Errorf("profil %d: %w", id, err) gibi kısa önekler kullanmak, uzun zincirlerin bile okunaklı kalmasını sağlar.

Testlerde hataları nasıl kontrol ederim?

Metni karşılaştırmak yerine errors.Is ve errors.AsType kullanın; metin değişse de testler kırılmaz. Test yazmanın ayrıntıları testing paketi yazısında.

Marka adları sahiplerine aittir. Bu yazı bağımsız bir teknik rehberdir; adı geçen kuruluşlarla bayilik veya ortaklık ilişkisi bulunduğu anlamına gelmez.

İlgili içerikler: Go öğrenme rehberi · Go defer, panic, recover · Go interface · Go type switch · Go sık karşılaşılan hatalar · Go fmt Printf