---
title: 'Nöbetçi Hatalar'
source: 'https://academia.sh/tr/kurslar/go-tip-sistemi/nobetci-hatalar'
course: 'Arayüzler, Hatalar ve Genellikler'
language: tr
updated: '2026-08-23T14:25:02+00:00'
license: 'CC BY-SA 4.0'
---

# Nöbetçi Hatalar

Paket düzeyinde bir kez tanımlanan bir hata değerinin kimliği metninde değil kendisinde durur: aynı metni taşıyan başka bir errors.New değeri onunla karşılaştırıldığında eşit çıkmaz, çünkü ikisi de errors.errorString tipinde farklı iki değerdir. errors.Is, aynı sonucu == ile aynı verir. Sınırlayıcı ölçüm: nöbetçi hatanın kimliği değiştiğinde çağıran taraf sessizce yanlış dala düşer, hiçbir uyarı üretilmez.

Önceki ders, değer alıcılı bir hatanın yapısal, işaretçi alıcılı bir hatanın kimlikle
karşılaştırıldığını ölçtü. Bu ders o farkı bir adım ileri taşıyor: bir çağıran, belirli bir
hatayı **tanımak** istediğinde neye güveniyor? Yanıt Go'da yaygın bir kalıba dayanıyor —
**nöbetçi hata**: paket düzeyinde bir kez tanımlanan, dışa aktarılan bir hata değeri. Bu dersin
sorusu şu: bu değerin kimliği nerede duruyor, ve aynı metni taşıyan başka bir hata onunla neden
karıştırılmıyor?

Bu isim, Algoritmalar kursundaki **nöbetçi değişkeni** ve Veri Yapıları kursundaki **nöbetçi
değer** ile aynı aileden geliyor: orada bir dizinin sonunu ya da özel bir konumu işaretleyen
tek bir değer, burada bir başarısızlık **durumunu** işaretliyor. Kavram tekrar kurulmuyor;
ortak olan yalnızca fikir — bir arama ya da denetim döngüsünü, her adımda ayrı bir kural
yazmak yerine tek bir karşılaştırmaya indirgeyen özel bir değer.

## Aynı Metin, Ayrı Kimlik

`errors.New`, çağrıldığı her seferde yeni bir değer üretir. Aşağıdaki `araBirinci` paket
düzeyinde tanımlanan `ErrBulunamadi` nöbetçisini döndürüyor; `araIkinci` ise aynı metni taşıyan
ama `errors.New` ile **ayrıca** üretilmiş bir değer döndürüyor. İkisi de kullanıcıya aynı
şeyi söylüyormuş gibi görünüyor.

Bu ikisi gerçek kodda genellikle iki ayrı paketten geliyor: bir depo katmanı `ErrBulunamadi`
gibi bir nöbetçi tanımlar, çağıran bu nöbetçiyle karşılaştırarak "kayıt yoktu" durumunu özel
olarak ele alır. Sorun, bir başka katmanın — belki aynı anlamı taşımak isteyen bir üçüncü
paketin — kendi `errors.New("kayit bulunamadi")` çağrısını yazmasıyla başlıyor; metin
okuyucuya aynı görünse de, iki değer birbirinden habersiz iki ayrı kimlik taşıyor.

```go
// 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("%-42s %-9s %s\n", ad, yer, kanit)
}

func ozet() {
	fmt.Printf("bag: %d kullanim yeri, %d deger\n", kullanimYeri, deger)
}
```

```go
// nobetci.go — paket duzeyinde tanimlanan bir hata degeri, ayni metinli baska bir degerden kimlikle ayrilir
package main

import (
	"errors"
	"fmt"
)

var ErrBulunamadi = errors.New("kayit bulunamadi")

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

func araIkinci(id int) error {
	if id != 1 {
		return errors.New("kayit bulunamadi")
	}
	return nil
}

func main() {
	h1 := araBirinci(9)
	h2 := araIkinci(9)

	gozle("iki degerin dinamik tipi", "deger", fmt.Sprintf("%T, %T", h1, h2))
	gozle("nobetciyle == karsilastirmasi", "deger", fmt.Sprint(h1 == ErrBulunamadi))
	gozle("nobetciyle errors.Is karsilastirmasi", "deger", fmt.Sprint(errors.Is(h1, ErrBulunamadi)))
	gozle("ayni metinli farkli degerin == sonucu", "deger", fmt.Sprint(h2 == ErrBulunamadi))
	gozle("ayni metinli farkli degerin errors.Is sonucu", "deger", fmt.Sprint(errors.Is(h2, ErrBulunamadi)))
	gozle("iki farkli degerin metin karsilastirmasi", "deger", fmt.Sprint(h1.Error() == h2.Error()))
	ozet()
}
```

```
iki degerin dinamik tipi                   deger     *errors.errorString, *errors.errorString
nobetciyle == karsilastirmasi              deger     true
nobetciyle errors.Is karsilastirmasi       deger     true
ayni metinli farkli degerin == sonucu      deger     false
ayni metinli farkli degerin errors.Is sonucu deger     false
iki farkli degerin metin karsilastirmasi   deger     true
bag: 0 kullanim yeri, 6 deger
```

**HT7.** İlk satır önceki dersin dinamik tip ölçümünü burada tekrarlıyor ve şaşırtıcı bir
sonuç veriyor: `h1` ve `h2` **aynı** dinamik tipi taşıyor, `*errors.errorString` — `errors.New`
standart kitaplıkta bu tek tipin bir işaretçisini döndürüyor, hangi metni verirsen ver. Yani
aynı tip olmak, aynı değer olmak anlamına gelmiyor. `h1 == ErrBulunamadi` `true` dönüyor çünkü
`araBirinci` gerçekten paket düzeyindeki aynı değişkeni döndürdü — aynı adresi taşıyan aynı
işaretçi. `h2 == ErrBulunamadi` ise `false` dönüyor, çünkü `araIkinci` kendi `errors.New`
çağrısıyla **yeni bir değer** üretti; metin birebir aynı olsa da bu, farklı bir bellek
konumunda duran farklı bir yapı demek.

**HT8.** `errors.Is(h1, ErrBulunamadi)` ve `errors.Is(h2, ErrBulunamadi)`, sarmalanmamış bu iki
hatada `==` ile birebir aynı sonucu veriyor. Bunun nedeni `errors.Is`in davranışı: hedef hata
sarmalanmadıysa, karşılaştırma doğrudan `==`e iniyor. Bu eşdeğerlik yalnız bu derste geçerli —
sıradaki ders bir hata sarmalandığında `==`in neden yetersiz kaldığını ve `errors.Is`in neden
zorunlu hâle geldiğini ölçüyor. Son satır ise metin karşılaştırmasının (`Error() == Error()`)
kimlik karşılaştırmasından tamamen ayrı bir soruya yanıt verdiğini gösteriyor: iki farklı
değer, metni birebir aynı olduğu için `true` dönüyor — kimlik sorusuna yanlış yanıt verecek
bir kısayol.

Bu ölçüm, nöbetçi hata kalıbının neden `const` değil `var` ile kurulduğunu da açıklıyor.
`error` bir arayüz olduğu için bir sabite bağlanamaz — sabitler yalnız temel tiplerde
(sayı, dizgi, bool) tanımlanabilir. Bir nöbetçi hatanın "sabit" gibi davranması, dilin bir
kısıtlamasından değil, paket düzeyinde bir kez oluşturulup bir daha hiç değiştirilmeyen tek bir
değişkene bütün paketin aynı işaretçiyle erişmesinden geliyor. Kimliğin korunması, bu tek
değişkenin **paylaşılmasına** bağlı; her çağıran kendi `errors.New` kopyasını üretirse, kimlik
karşılaştırması daha en baştan anlamını yitiriyor.

## Özel Kimlik: `Is` Yöntemiyle Kurulan Karşılaştırma

`errors.Is`in bu derste ölçülen davranışı — sarmalanmamış bir hatada `==`e inmesi — tek
kuralı değil. `errors` paketi bir hataya, karşılaştırmayı kendi belirlediği bir yönteme
devretme imkânı veriyor: `Is(hedef error) bool` yazan bir tip, `errors.Is` tarafından
çağrıldığında işaretçi eşitliğine değil, kendi mantığına göre yanıt veriyor. Bu, kimliğin
her zaman "aynı adres" anlamına gelmediğini, bir tipin kendi kimlik tanımını yazabildiğini
gösteriyor.

```go
// kodhatasi.go — ozel Is yontemiyle kurulan bir kimlik: isaretci esitligi olmadan da kimlik tanimlanabilir
package main

import (
	"errors"
	"fmt"
)

type KodHatasi struct{ Kod int }

func (k *KodHatasi) Error() string { return fmt.Sprintf("hata kodu %d", k.Kod) }

func (k *KodHatasi) Is(hedef error) bool {
	h, tamam := hedef.(*KodHatasi)
	return tamam && h.Kod == k.Kod
}

func main() {
	a := &KodHatasi{Kod: 404}
	b := &KodHatasi{Kod: 404}
	c := &KodHatasi{Kod: 500}

	gozle("iki ayri isaretcinin == sonucu", "deger", fmt.Sprint(a == b))
	gozle("Is yontemiyle ayni kodun karsilastirmasi", "deger", fmt.Sprint(errors.Is(a, b)))
	gozle("Is yontemiyle farkli kodun karsilastirmasi", "deger", fmt.Sprint(errors.Is(a, c)))
	ozet()
}
```

```
iki ayri isaretcinin == sonucu             deger     false
Is yontemiyle ayni kodun karsilastirmasi   deger     true
Is yontemiyle farkli kodun karsilastirmasi deger     false
bag: 0 kullanim yeri, 3 deger
```

**HT9.** `a` ve `b`, `KodHatasi` tipinden iki ayrı işaretçi; `==` bunları adres bazında
karşılaştırıyor ve `false` dönüyor, tıpkı önceki dersteki `ErisimHatasi` örneğinde olduğu gibi.
Ama `errors.Is(a, b)` çağrıldığında sonuç `true` — çünkü `KodHatasi`, `Is(hedef error) bool`
yöntemini yazmış ve `errors.Is`, hedefi `a`nın `Is` yöntemine soruyor. `a.Is(b)`, `b`nin de bir
`*KodHatasi` olduğunu ve `Kod` alanının eşit olduğunu doğruluyor; adres hiç devreye girmiyor.

**HT10.** `errors.Is(a, c)` ise `false` dönüyor, çünkü `c`nin `Kod` alanı farklı — özel `Is`
yöntemi hâlâ çalışıyor, yalnız bu kez kendi mantığına göre "eşit değil" diyor. Bu, kimliğin
Go'da tek bir mekanizmaya bağlı olmadığını gösteriyor: paket düzeyinde paylaşılan bir
değişkenin adresi bir kimlik biçimiyse, bir tipin kendi yazdığı `Is` yöntemi de başka bir
kimlik biçimi. `errors.Is` ikisini de kabul ediyor — önce `==` dener, sonra hedefin ya da
kaynağın bir `Is` yöntemi olup olmadığına bakıyor. Sıradaki bölümdeki nöbetçi hata ölçümü ilk
biçime, yani paylaşılan değişkene dayanıyor.

Bu iki biçimin seçimi, hatanın taşıdığı bilgiye bağlı. `ErrBulunamadi` gibi hiçbir ek veri
taşımayan bir durumda tek bir paylaşılan değer yeterli — her çağıran aynı adrese bakabiliyor.
Ama `KodHatasi` gibi kendi içinde değişen bir veri (`Kod`) taşıyan bir hata türünde, olası her
kod için önceden ayrı bir paylaşılan değişken tanımlamak pratik değil; `Is` yöntemi, "aynı
tipten ve aynı koddan mı" sorusunu tek bir karşılaştırmaya indiriyor ve her kod için yeni bir
nöbetçi üretme ihtiyacını ortadan kaldırıyor.

## Sınırlayıcı Ölçüm: Kimlik Bir API Sözüdür

Bir nöbetçi hata dışa aktarıldığında (`ErrBulunamadi` gibi büyük harfle başlayan bir ad),
paketin kendisi bir söz veriyor: bu değer, bu paketten dönen belirli bir hata durumunu her
zaman **aynı işaretçiyle** temsil edecek. Bu söz bozulduğunda — aynı durumu farklı bir
`errors.New` çağrısıyla üreten bir değişiklik yapıldığında — kaynakta hiçbir uyarı çıkmıyor.

Bu söz genellikle kod tabanı büyüdükçe bozuluyor, kötü niyetten değil dikkatsizlikten. Bir
paketin ilk sürümü tek bir yerden `ErrYetkisiz` döndürürken, zamanla aynı durumu bildiren
ikinci bir işlev eklenir; yazan kişi paket düzeyindeki nöbetçiyi içe aktarmak yerine kendi
`errors.New` çağrısını yazarsa — belki dosyalar arasında geçiş yaparken, belki nöbetçinin
varlığından habersiz olduğu için — iki işlev artık aynı metni ama farklı kimliği taşıyan iki
ayrı hata üretir. Aşağıdaki ölçüm bu senaryoyu birebir kuruyor: `erisimA` nöbetçiyi
paylaşıyor, `erisimB` paylaşmıyor.

```go
// versiyon.go — nobetci hatanin kimligi degisirse cagiran sessizce yanlis dala duser
package main

import "errors"

var ErrYetkisiz = errors.New("yetkisiz erisim")

func erisimA(rutbe int) error {
	if rutbe < 2 {
		return ErrYetkisiz
	}
	return nil
}

func erisimB(rutbe int) error {
	if rutbe < 2 {
		return errors.New("yetkisiz erisim")
	}
	return nil
}

func isle(hata error) string {
	if hata == ErrYetkisiz {
		return "yetkisizlik dalina dusuldu, ozel islem yapildi"
	}
	if hata != nil {
		return "bilinmeyen hata dalina dusuldu, genel islem yapildi"
	}
	return "hata yok"
}

func main() {
	gozle("erisimA'nin nobetci hatasiyla isle", "deger", isle(erisimA(0)))
	gozle("erisimB'nin ayni metinli hatasiyla isle", "deger", isle(erisimB(0)))
	ozet()
}
```

```
erisimA'nin nobetci hatasiyla isle         deger     yetkisizlik dalina dusuldu, ozel islem yapildi
erisimB'nin ayni metinli hatasiyla isle    deger     bilinmeyen hata dalina dusuldu, genel islem yapildi
bag: 0 kullanim yeri, 2 deger
```

**HT11.** `erisimA` ve `erisimB`, kullanıcı için görünüşte aynı durumu bildiriyor — "yetkisiz
erişim" — ama yalnız `erisimA` gerçek `ErrYetkisiz` nöbetçisini döndürüyor. `isle` işlevi
`hata == ErrYetkisiz` ile özel bir dal ayırmış: `erisimA`nın hatası bu dala düşüyor, `erisimB`
ise düşmüyor ve genel dala kayıyor. Kaynağı okuyan biri için `erisimB`nin metni `erisimA`nınkiyle
birebir aynı olduğundan, bu ayrımın var olduğunu görmek mümkün değil — yalnız çalıştırıp
sonuçları karşılaştırmak gösteriyor.

**HT12.** Bu, nöbetçi hatanın kimliğinin neden bir **API sözü** sayıldığını gösteriyor:
`erisimB` gibi bir işlev, aynı durumu bildirdiğini düşünse de nöbetçiyi paylaşmadığı için sözü
tutmuyor. Derleme hiçbir şey söylemiyor, çünkü ikisi de geçerli bir `error` döndürüyor;
çalışma zamanında da hiçbir panik ya da hata mesajı çıkmıyor — `isle` yalnız beklenmeyen dala
sessizce düşüyor. Bir paketin bir nöbetçi hatayı bir kez yayımladıktan sonra, o hatayı üreten
her yol aynı değişkeni döndürmek zorunda; aksi hâlde çağıran tarafın kimlik karşılaştırması
gizlice yanlış sonuç vermeye başlıyor.

Bunun bir sınırı da var: kimlik yalnızca **aynı çalışan süreç** içinde anlamlı. `ErrYetkisiz`in
adresi, programın o çalıştırılışına özgü; bir hatayı diske yazıp sonra okumak, ya da bir ağ
çağrısıyla başka bir sürece taşımak, `error` değerini değil onun `Error()` metnini taşıyor —
kimlik bu geçişte kayboluyor. Bu yüzden nöbetçi hata karşılaştırması yalnızca aynı ikili
dosyanın (aynı derlemenin) içindeki çağrılar arasında güvenilir; sınır ötesinde (bir HTTP
yanıtında, bir günlük dosyasında) hata metninin ya da ayrı bir hata kodu alanının taşınması
gerekiyor.

Bu derste ölçülen üç bulgu birleştiğinde konunun ölçütü hata değerlerinde ilk kez tümüyle
**değer** sınıfına oturuyor. Örtük karşılama önceki dersin de gösterdiği gibi kullanım yerinde
kuruluyordu; ama bir hatayı **tanımak**, yani onu başka bir hatadan ayırt etmek, tamamen
değerin kendi kimliğine dayanıyor — ne kaynakta bir bildirim, ne derleyicinin baktığı bir
denetim var. Kimlik ya paylaşılan bir adrese (`ErrBulunamadi`) ya da tipin kendi yazdığı bir
`Is` yöntemine dayanıyor; ikisi de yalnızca çalışma zamanında, değere sorularak öğreniliyor.

## Özet

- Bir nöbetçi hata, paket düzeyinde bir kez tanımlanan ve dışa aktarılan bir `error`
  değeridir; kimliği metninde değil, kendi değerinde durur.
- `errors.New`in her çağrısı yeni bir `*errors.errorString` değeri üretir — aynı metin bile
  versin, iki çağrı iki farklı değer, dolayısıyla iki farklı kimlik demektir.
- Sarmalanmamış bir hatada `errors.Is` ile `==` aynı sonucu verir; bu eşdeğerlik yalnız hata
  hiç sarmalanmadığında geçerlidir.
- Metin karşılaştırması (`Error() == Error()`) kimlik karşılaştırmasının yerini tutmaz: farklı
  kimlikli iki hata birebir aynı metni taşıyabilir.
- Nöbetçi hatanın kimliği bir API sözüdür; bir durumu bildiren her yol aynı paylaşılan
  değişkeni döndürmezse, çağıranın kimlik denetimi hiçbir uyarı vermeden yanlış sonuç üretir.
- Kimlik yalnız paylaşılan bir adrese değil, bir tipin kendi yazdığı `Is(hedef error) bool`
  yöntemine de dayanabilir; `errors.Is` ikisini de tanır, `==` yalnız birincisini. Kimlik
  yalnızca aynı çalışan süreç içinde geçerlidir, bir sınırı (diske yazma, ağ üzerinden
  taşıma) aşarken kaybolur.

## Sonraki Adım

Bu derste ölçülen hatalar hep tek katmanlıydı: bir işlev nöbetçiyi ya doğrudan döndürdü ya da
onun yerine yeni bir değer üretti. Ama 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. Bir hata bu şekilde sarmalandığında, `h1 == ErrBulunamadi` artık işe yaramıyor;
dıştaki değer artık nöbetçinin kendisi değil, onu içinde taşıyan başka bir değer. `errors.Is`in
bu derste sarmalanmamış hatalarda `==`e indiği gözlemi de burada sona eriyor: sıradaki derste
zincir eklendiğinde, `errors.Is`in neden artık `==`den farklı bir sonuç ürettiği ve zincirin
dibindeki nöbetçinin kaç katman sonra hâlâ bulunabildiği ölçülüyor. Aynı soru, sarmalayan
metnin kendisi için de sorulacak: bir katmanın eklediği bağlam, zincirin geri kalanında nasıl
korunuyor?
