İçeriğe geç
academia.sh

Ders 08 / 13

Hata Sarmalama ve Çözme

%w ile sarmalanan bir hata, dış hatanın içinde bir zincir olarak durur ve errors.Is bu zinciri Unwrap ile katman katman gezerek nöbetçiyi bulur; %v kullanıldığında aynı metin üretilse de zincir kopuyor ve errors.Is artık bulamıyor. errors.As zincirden belirli bir hedef tipi çıkarır. Sınırlayıcı ölçüm: sarmalayan bağlamın metni yanlış olsa bile zincirin kimliği bozulmuyor, hiçbir uyarı üretilmiyor.

İçindekiler

Önceki ders, nöbetçi hatanın kimliğinin == ile ya da bir Is yöntemiyle nasıl korunduğunu ölçtü — ama hep tek katmanlı çağrılarda. Gerçek çağrı zincirleri genellikle birden çok katmandan geçiyor ve her katman kendi bağlamını eklemek istiyor: “şu işlemi yaparken böyle bir hata oluştu” gibi. Bu ders bu bağlamın nasıl eklendiğini ve zincirin dibindeki nöbetçinin kaç katman sonra hâlâ bulunabildiğini ölçüyor.

Sarmalama, aynı zamanda konunun ölçütü için de yeni bir gözlem sunuyor. Önceki ders bir hatanın kimliğinin nasıl paylaşıldığını ölçtü; ama bir çağrı zinciri büyüdükçe, dıştaki hata artık nöbetçinin kendisi değil, onu içeren başka bir değer oluyor. h1 == ErrYok gibi bir karşılaştırma böyle bir zincirde işe yaramıyor — ne dıştaki değer nöbetçiyle aynı adresi taşıyor, ne de metni aynı kalıyor. Bu ders, kimliğin bir zincire gömüldüğünde nasıl hâlâ sorgulanabildiğini gösteriyor.

Zincirin Dibi: %w ile Taşınan Kimlik

fmt.Errorf, %w biçim fiiliyle çağrıldığında yeni bir hata üretiyor ve bu yeni hata, verilen hatayı içinde tutuyor. Aşağıdaki üç katmanlı zincir bunu iki biçimde kuruyor: Saglam sürümü her katmanda %w kullanıyor, Kirik sürümü ise ortada bir katmanda %v kullanıyor — ikisi de aynı metni üretiyor.

Üç katman burada bilerek seçildi: gerçek bir kod tabanında bir depo katmanı, onu çağıran bir servis katmanı ve o servisi çağıran bir işleyici katmanı benzer bir zincir kurar. Her katman kendi bakış açısından bir cümle ekler (“kayıt bulunamadı” değil “kullanıcı doğrulanamadı: kayıt bulunamadı” gibi), ama en dipteki nedenin ne olduğu sorusu değişmiyor. Ölçüm, bu sorunun yanıtının zincir boyunca korunup korunmadığını sınıyor.

// bag.go — hata bagi kullanim yerinde mi kuruluyor, degerin icinde mi
package main

import "fmt"

var kullanimYeri, deger int

func gozle(ad, yer, kanit string) {
	switch yer {
	case "kullanim":
		kullanimYeri++
	default:
		deger++
	}
	fmt.Printf("%-46s %-9s %s\n", ad, yer, kanit)
}

func ozet() {
	fmt.Printf("bag: %d kullanim yeri, %d deger\n", kullanimYeri, deger)
}
// zincir.go — uc katmanli sarmalama: nobetci hata zincirin dibinde bile bulunuyor, %v zinciri kesiyor
package main

import (
	"errors"
	"fmt"
)

var ErrYok = errors.New("kayit yok")

func katman1() error {
	return ErrYok
}

func katman2Saglam() error {
	if hata := katman1(); hata != nil {
		return fmt.Errorf("katman2 basarisiz: %w", hata)
	}
	return nil
}

func katman3Saglam() error {
	if hata := katman2Saglam(); hata != nil {
		return fmt.Errorf("katman3 basarisiz: %w", hata)
	}
	return nil
}

func katman2Kirik() error {
	if hata := katman1(); hata != nil {
		return fmt.Errorf("katman2 basarisiz: %v", hata)
	}
	return nil
}

func katman3Kirik() error {
	if hata := katman2Kirik(); hata != nil {
		return fmt.Errorf("katman3 basarisiz: %w", hata)
	}
	return nil
}

func main() {
	saglam := katman3Saglam()
	kirik := katman3Kirik()

	gozle("saglam zincirin disaridaki metni", "deger", saglam.Error())
	gozle("kirik zincirin disaridaki metni", "deger", kirik.Error())
	gozle("saglam zincirde nobetci bulunuyor mu", "deger", fmt.Sprint(errors.Is(saglam, ErrYok)))
	gozle("kirik zincirde nobetci bulunuyor mu", "deger", fmt.Sprint(errors.Is(kirik, ErrYok)))
	ozet()
}
saglam zincirin disaridaki metni               deger     katman3 basarisiz: katman2 basarisiz: kayit yok
kirik zincirin disaridaki metni                deger     katman3 basarisiz: katman2 basarisiz: kayit yok
saglam zincirde nobetci bulunuyor mu           deger     true
kirik zincirde nobetci bulunuyor mu            deger     false
bag: 0 kullanim yeri, 4 deger

HT13. İlk iki satır kursun en net gösterimlerinden biri: iki zincirin metni birebir aynı, çünkü %w ve %v ikisi de aynı Error() string çıktısını fmt.Errorfin ürettiği yeni metnin içine yerleştiriyor. Metne bakan biri iki hatayı ayırt edemez. Ama üçüncü ve dördüncü satır bambaşka bir sonuç veriyor: errors.Is(saglam, ErrYok) true, errors.Is(kirik, ErrYok) false. Metin aynı kalırken sorgulama sonucu değişiyor — çünkü errors.Isin sorduğu soru metne değil zincire bakıyor.

HT14. Fark, %w ile %v arasındaki tek bir davranışta: %w, fmt.Errorfin ürettiği yeni hataya bir Unwrap() error yöntemi kazandırıyor ve bu yöntem verilen hatayı geri döndürüyor; %v yalnızca metni gömüyor, hiçbir yöntem eklemiyor. katman2Kirikin ürettiği hatanın Unwrapi yok — zincir orada, ErrYoke ulaşamadan kesiliyor. katman3Kirikin kendi %w kullanması bu kesintiyi telafi etmiyor, çünkü zaten kestiği yer bir katman altında.

Bu, hata sarmalamanın tek yönlü bir zincir olduğunu gösteriyor: bir üst katman, altındaki bir katmanın zinciri kırmasını düzeltemiyor. katman3Kirik, katman2Kirikin ürettiği hatayı %w ile sarmalıyor ve kendi Unwrap yöntemini doğru kuruyor — ama sarmaladığı değerin kendisi zaten dipteki ErrYoke bir bağ taşımıyor. Zincirin sağlamlığı, en zayıf katmanına bağlı; tek bir %v kullanımı, üstündeki bütün katmanların doğru davranmasını anlamsız kılıyor.

Zincirin İçinde Gezinme: errors.Unwrap

errors.Isin yaptığı iş aslında tek bir ilkel üzerine kurulu: errors.Unwrap. Bu işlev bir hatayı alıp, o hata bir Unwrap() error yöntemi yazmışsa bir alt katmanı döndürüyor; yazmamışsa nil döndürüyor. errors.Is, hedefi bulana ya da nile varana kadar bunu döngüyle çağırıyor. Aşağıdaki ölçüm bu döngüyü elle kuruyor.

Bu, errors.Isin “sihirli” bir davranış olmadığını, sıradan bir döngü ve sıradan bir yöntem çağrısı üzerine kurulduğunu gösteriyor. %w bir hataya Unwrap yöntemi kazandırdığı için zincir kurulabiliyor; bu yöntem olmasaydı, errors.Unwrap ilk çağrıda nil dönerdi ve zincirin dibine hiç inilemezdi. Önceki bölümdeki katman2Kirikin ürettiği hata tam olarak bu durumda: %v ile üretildiği için Unwrap yöntemi yok, errors.Unwrap onu çağırdığında nil dönüyor.

// adimadim.go — errors.Is'in gezdigi zinciri Unwrap ile elle adim adim izleme
package main

import (
	"errors"
	"fmt"
)

var ErrYok = errors.New("kayit yok")

func main() {
	sarmali := fmt.Errorf("dis katman: %w", fmt.Errorf("orta katman: %w", ErrYok))

	adim := 0
	guncel := sarmali
	for guncel != nil {
		gozle(fmt.Sprintf("zincirin %d. adimi", adim), "deger", guncel.Error())
		guncel = errors.Unwrap(guncel)
		adim++
	}
	gozle("errors.Is ayni zinciri tek cagrida bulur mu", "deger", fmt.Sprint(errors.Is(sarmali, ErrYok)))
	ozet()
}
zincirin 0. adimi                              deger     dis katman: orta katman: kayit yok
zincirin 1. adimi                              deger     orta katman: kayit yok
zincirin 2. adimi                              deger     kayit yok
errors.Is ayni zinciri tek cagrida bulur mu    deger     true
bag: 0 kullanim yeri, 4 deger

HT15. Döngü üç adımda duruyor: guncel sırasıyla dış hatayı, orta hatayı ve en dipteki ErrYoki taşıyor; dördüncü çağrıda errors.Unwrap(ErrYok) nil dönüyor çünkü ErrYok errors.New ile üretildi ve hiçbir Unwrap yöntemi yok — döngü orada duruyor. Her adımın metni bir öncekinin son parçası; bu, zincirin gerçekten iç içe durduğunu, tek bir uzun dizgi olarak değil, birbirine bağlı ayrı hata değerleri olarak taşındığını gösteriyor. errors.Is(sarmali, ErrYok) bu üç adımı tek bir çağrıda yapıyor ve true dönüyor — önceki bölümdeki saglam zinciriyle aynı mekanizma.

Bu adım adım gezinme, errors.Isin maliyetini de gösteriyor: zincir ne kadar uzunsa, errors.Unwrap o kadar çok kez çağrılıyor. Pratikte bu maliyet, birkaç katmanlık bir çağrı zincirinde göz ardı edilebilir düzeyde kalıyor — ama zincirin sonsuz döngüye girmemesi de Unwrapin her adımda gerçekten farklı bir değer döndürmesine bağlı. Bir hatanın kendi kendisini sarmalaması (Unwrapin aynı değeri döndürmesi) burada ölçülmüyor, ama zincirin neden hep ilerleyen, hiç geri dönmeyen bir yapı olarak tasarlandığını açıklıyor.

Zincirden Tip Çıkarma: errors.As

errors.Is bir hedef değerin zincirde olup olmadığını soruyor; errors.As ise zincirde belirli bir tipte bir hata olup olmadığını soruyor ve varsa onu bir değişkene yazıyor. Bu, zincirin yalnız kimlik değil, taşıdığı veriyi de koruduğunu gösteriyor.

Bu ayrım, önceki dersteki iki kimlik biçimiyle de örtüşüyor. ErrYok gibi veri taşımayan bir nöbetçi için soru hep “bu değer mi” — errors.Is yeterli. Ama AlanHatasi gibi kendi alanı olan bir hata türünde asıl soru genellikle “bu türden bir hata mı, ve alanı ne” — errors.As bu soruyu yanıtlıyor. errors.Asin ikinci argümanı bir işaretçiye işaretçi almasının nedeni de bu: bulduğu değeri doğrudan çağıranın değişkenine yazabilmesi gerekiyor.

// hedef.go — zincirden hedef tipi cikarma; yanlis baglam zincirin kimligini bozmuyor
package main

import (
	"errors"
	"fmt"
)

var ErrYok = errors.New("kayit yok")

type AlanHatasi struct{ Alan string }

func (a *AlanHatasi) Error() string { return "alan gecersiz: " + a.Alan }

func dogrula() error {
	return fmt.Errorf("kayit dogrulanamadi: %w", &AlanHatasi{Alan: "ad"})
}

func katman1() error {
	return ErrYok
}

func katmanYanlisBaglam() error {
	if hata := katman1(); hata != nil {
		return fmt.Errorf("beklenmeyen ag hatasi: %w", hata)
	}
	return nil
}

func main() {
	var hedef *AlanHatasi
	bulunduMu := errors.As(dogrula(), &hedef)
	gozle("zincirden AlanHatasi tipini cikarma", "deger", fmt.Sprint(bulunduMu, " ", hedef.Alan))

	yanlis := katmanYanlisBaglam()
	gozle("yanlis baglamla sarmalanan hatanin metni", "deger", yanlis.Error())
	gozle("yanlis baglama ragmen kimlik hala bulunuyor mu", "deger", fmt.Sprint(errors.Is(yanlis, ErrYok)))
	ozet()
}
zincirden AlanHatasi tipini cikarma            deger     true ad
yanlis baglamla sarmalanan hatanin metni       deger     beklenmeyen ag hatasi: kayit yok
yanlis baglama ragmen kimlik hala bulunuyor mu deger     true
bag: 0 kullanim yeri, 3 deger

HT16. dogrula, zincirin tepesine bir *AlanHatasi sarmalıyor. errors.As(dogrula(), &hedef), zinciri errors.Unwrap ile gezip *AlanHatasi tipinde bir hata arıyor, buluyor ve hedefe atıyor — sonrasında hedef.Alan doğrudan okunabiliyor. Bu, errors.Isin veremeyeceği bir şey: errors.Is yalnız “bu değer zincirde var mı” diye soruyor, errors.As “bu tipte bir değer var mı, varsa onu bana ver” diyor. İkisi de aynı Unwrap zincirini geziyor, farklı bir soru soruyor.

dogrula işlevinin dönüş tipi burada da dikkat çekici: imzası yalnız error yazıyor, *AlanHatasi değil — tıpkı Hata Arayüzü dersinde ölçülen tuzağın tersini yaparak. Somut tip imzada görünmüyor, yalnız zincirin içine gömülüyor; çağıran onu bilmek istiyorsa errors.As ile zinciri sorgulamak zorunda. İmzanın kendisi, zincirin içinde hangi tiplerin durduğu konusunda hiçbir söz vermiyor.

HT17. İkinci ölçüm, dersin sınırlayıcı gözlemini kuruyor. katmanYanlisBaglam, gerçek neden ErrYok olsa da metne “beklenmeyen ag hatasi” yazıyor — bağlam yanlış, hatta yanıltıcı. Ama errors.Is(yanlis, ErrYok) yine de true dönüyor: zincir hâlâ sağlam, çünkü %w kullanıldı ve Unwrap yöntemi hâlâ ErrYoke ulaşıyor. Zincir kimliği korur, ama sarmalayan metnin doğruluğunu hiç denetlemiyor — yanlış bir bağlam yazmak ne derlemede ne çalışma zamanında hiçbir uyarı üretmiyor, çünkü fmt.Errorfin ilk argümanı sıradan bir dizgi ve dilin ona bakan bir denetimi yok.

Bu gözlemin pratik sonucu açık: errors.Is ve errors.As ile yazılan bir denetim, sarmalayan metnin ne söylediğine bakmadan doğru çalışmaya devam ediyor — bu bir avantaj, çünkü metin insan okuması için serbestçe değiştirilebiliyor, kimlik denetimi bundan etkilenmiyor. Ama aynı özellik bir risk de taşıyor: bir geliştirici zincire yanlış ya da yanıltıcı bir bağlam eklediğinde, bunu fark etmenin tek yolu metni okumak; ne errors.Is ne errors.As bir uyarı veriyor, çünkü ikisi de yalnızca kimliğe ve tipe bakıyor.

Bu üç ölçüm konunun ortak ölçütüne aynı biçimde bağlanıyor: nöbetçi hatanın kimliği (önceki ders), hata sarmalamanın taşıdığı metin ve sarmalı zincirden tip çıkarma — üçü de değerin içinde taşınıyor, kaynakta hiçbir bildirimle görünmüyor. Bu, on altı bağın sekizinin değerde taşındığı ortak ölçümün hata zincirlerindeki karşılığı.

Bu üç dersin (Hata Arayüzü, Nöbetçi Hatalar, Hata Sarmalama ve Çözme) birlikte kurduğu resim şöyle: error bir arayüz olarak örtük karşılanıyor, karşılayan tip kaynakta hiç anılmıyor (kullanım yeri); bir hatanın kimliği bir adrese ya da bir Is yöntemine dayanıyor (değer); ve bu kimlik, katmanlar arasında sarmalanarak taşınsa bile, Unwrap zinciri sayesinde kayıp gitmiyor (yine değer). Kaynağı okuyan biri, üç katmanlı bir zincirin dibinde hangi nöbetçinin durduğunu görmek için ne bir bildirime ne bir imzaya bakabilir — yalnızca zinciri gerçekten gezmek (ya da errors.Ise bu gezintiyi yaptırmak) yeterli oluyor.

Özet

  • %w, fmt.Errorfin ürettiği hataya bir Unwrap() error yöntemi ekler; %v eklemez — metin aynı kalsa da zincir %v kullanılan katmanda kopar.
  • errors.Is, errors.Unwrapi hedefi bulana ya da nile varana kadar döngüyle çağırır; zincir üç katmansa üç adımda geziliyor.
  • errors.As, zincirde belirli bir tipte bir hata arar ve bulduğunda onu bir değişkene yazar; errors.Isin veremeyeceği veriyi (örneğin Alan gibi bir alanı) böyle taşır.
  • Zincirin kimliği (errors.Is, errors.As) korunur, ama sarmalayan metnin doğruluğu hiç denetlenmez — yanlış bir bağlam yazmak hiçbir uyarı üretmez.
  • Nöbetçi hatanın kimliği, hata sarmalamanın taşıdığı metin ve zincirden tip çıkarma, üçü de değerin içinde taşınır; kaynakta hiçbir bildirim bu bağı göstermez.
  • errors.Unwrapin sonsuz döngüye girmemesi, her katmanın altında farklı bir değer taşımasına bağlıdır; zincir hep ilerler, hiç kendi başına dönmez.

Sonraki Adım

Bu derste ölçülen her katman, kendi bağlamını sarmalayarak bir error döndürdü — dönüş tipi hep aynı kaldı, imza değişmedi. Zincir ne kadar uzarsa uzasın, her işlevin imzasında hâlâ tek bir sözleşme vardı: son değer bir error. Ama Go’da bir işlevin başarısız olmasının bir de imzada hiç görünmeyen bir yolu var: panik. Bir işlev panic çağırdığında dönüş tipinde hiçbir iz kalmıyor — imza func F() int ile func F() int arasında hiçbir fark yok, biri her zaman bir tamsayı döndürürken öbürü çalışma zamanında çökebiliyor. Sıradaki ders bu çıkışı ölçüyor: bir işlev hata döndürmek yerine panikliyorsa, bu seçim imzada nerede görünüyor, ve çağıran bunu önceden nasıl bilebilir?

İlerlemeni kaydetmek ve not almak için Giriş yap

Notlarım

Not almak için giriş yapmalısın.

Aramak için yazmaya başlayın.

↑↓ Esc gezin · aç · kapat