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 birUnwrap() erroryöntemi ekler;%veklemez — metin aynı kalsa da zincir%vkullanılan katmanda kopar.errors.Is,errors.Unwrapi hedefi bulana ya danile 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ğinAlangibi 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.