---
title: 'Üretim Go: Test, Araçlar ve Başarım'
source: 'https://academia.sh/tr/kurslar/go-uretim'
language: tr
updated: '2026-09-13T05:08:46+00:00'
license: 'CC BY-SA 4.0'
lessons: 16
outcomes:
    - 'Tablo güdümlü testler ve karşılaştırmalı ölçümler yazar'
    - 'Bellek ayırma davranışını profil çıkararak iyileştirir'
    - 'Derleme ve dağıtım seçeneklerini hedef ortama göre yapılandırır'
    - 'Bir bilginin kaynaktan mı araçtan mı koşumdan mı geldiğini ayırt eder'
---

# Üretim Go: Test, Araçlar ve Başarım

Bu kurs programın kendisini değil, program hakkında bilinenleri konu alır: testin verdiği karar, kapsanan satırlar, ayırma sayısı, derleyicinin kaçış kararı, derlemeye giren dosyalar, ikiliye gömülen sürüm. Hepsi tek bir soruyla ölçülür — bu bilgiyi kim söylüyor: kaynak mı, araç mı, koşum mu, ve söylediği şey her seferinde aynı mı?

## Test ve Ölçüm

Test işlevlerinin ve yardımcılarının dilin kendi paketiyle kurulması, vaka listesiyle kapsamın artırılması, sunucu ve istemci tarafı test yardımcıları, sahtelemenin arayüz ayrımına dayanması, karşılaştırmalı ölçümün verdiği iki sayıdan yalnız birinin tekrarlanabilir olması, ve kapsam verisinin ne söyleyip ne söylemediği.

### Test Paketi Temelleri

Source: https://academia.sh/tr/kurslar/go-uretim/test-paketi-temelleri

Aynı test dosyası, kaynağın kaç test işlevi içerdiğini metinden sayarak, testin verdiği kararı ise yalnız koşturarak söylüyor: doğru bir kaynakta iki test de geçiyor, bozuk bir kaynakta test sayısı değişmeden karar tersine dönüyor. Sınırlayıcı ölçüm, hiçbir iddia taşımayan bir test işlevinin de aynı biçimde geçtiğini koşturarak gösteriyor.

Önceki kurs bir değerin sınırı geçerken tip bilgisinin çoğunlukla kaybolduğunu ölçtü:
kaynağa bakan biri, bir sınırın öte yanındaki değerin hangi tipte olduğunu çoğu zaman
göremiyordu. Bu kurs aynı soruyu bir düzey yukarı taşıyor — artık tek bir değerin değil,
bütün bir programın hakkında bilinenler konu. Bir programın doğru çalıştığını, hangi
satırlarının koştuğunu, nerede bellek ayırdığını ve hangi platforma derlendiğini kaynağa
bakarak göremezsiniz. Öyleyse bu bilgiyi kim söylüyor — kaynağın kendisi mi, koşum
olmadan bir aracın türettiği bir yanıt mı, yoksa yalnız çalıştırarak öğrenilen bir sonuç mu?

Bu ilk ders soruyu en dar biçimiyle soruyor: bir test işlevi geçti mi, kaldı mı? Ölçüm
boyunca konu içinde sürecek küçük bir paket kullanılıyor — bir depo kalemini ve kalem
listesinin toplam değerini tutan `envanter` paketi. Kalemin kendisi basit: bir ad, bir
adet, kuruş cinsinden bir birim fiyat. Toplam değer de basit bir çarpım-toplam. Bu dersin
sorusu paketin ne yaptığı değil, paketin **doğru** yaptığını kimin söylediği.

Bu soru üretim ortamında çalışan bir programda özel bir ağırlık taşıyor. Bir kaynak
gözden geçirmesi, bir dosyanın satırlarını okuyup mantığın doğru göründüğüne karar
verebilir — ama "doğru göründü" ile "doğru çalıştığı sınandı" arasında bir uçurum var
ve bu uçurumu kapatan tek şey kaynağın yanına yazılmış, gerçekten koşturulmuş bir test.
Konunun geri kalan beş dersi bu uçurumun farklı köşelerini ölçüyor: vaka listesinin
kendisi kaynakta mı yazılı, ağ katmanı gerçekten devreye girmeden nasıl sınanıyor, bir
bağımlılığın yerine geçen sahte nesne neyi eksik bırakıyor, iki yazımın hangi sayısı
tekrarlanabilir ve kapsama oranının söylediği şeyin sınırı nerede. Hepsi aynı kökten
başlıyor: bir testin verdiği kararın kaynaktan değil, koşumdan geldiği.

#### Test İşlevi ve Yardımcı Dilin Kendi Kuralına Uyar

Test paketi, dilin standart kitaplığının bir parçası ve kendi kuralını dayatıyor: bir işlev test
paketi tarafından test olarak tanınmak istiyorsa adı `Test` ile başlamalı ve `Test`ten sonrası
küçük harfle başlamamalı (`TestToplam` tanınır, `Testtoplam` tanınmaz), tek bir `*testing.T`
parametresi almalı ve dosyanın adı `_test` ile bitmeli. Bu üç kural derleyicinin denetlediği bir
sözdizimi değil — test paketinin kendi tarama kuralı ve kaynağı okuyan biri bu kuralı bildiğinde,
hangi işlevlerin test olarak çalışacağını **metinden** çıkarabiliyor. Bu kuralın kendisi bir kayıt
adımı gerektirmiyor: bir test işlevini ayrı bir listeye eklemek ya da bir çağrıyla bildirmek
gerekmiyor, yalnız adı ve imzası doğru olduğunda test paketi onu kendiliğinden buluyor.

Aynı dosyada bir de `orneklerVer` işlevi var, ama bu bir test değil — adı `Test` ile
başlamıyor, bu yüzden test paketi onu hiç çalıştırmıyor. Bunun yerine iki test işlevi
tarafından çağrılan bir yardımcı: aynı örnek kalem listesini tekrar tekrar yazmamak için
bir kez tanımlanmış ve gövdesindeki `t.Helper()` çağrısı test paketine "bir hata
buradan değil, beni çağıran satırdan bildirilsin" diyor. Bu ayrım — hangi işlevin test,
hangisinin yardımcı olduğu — da kaynaktan okunabiliyor; test paketi bu kararı vermek
için hiçbir işlevi çalıştırmak zorunda kalmıyor, yalnız adına ve imzasına bakıyor.

**TS1.** Aşağıdaki test dosyası iki işlev tanımlıyor, `TestToplamDeger` ve
`TestBosIddia`; ikisi de adları ve imzalarıyla test paketinin kuralına uyuyor ve
kaç tane olduğu kaynaktaki `func Test` geçişleri sayılarak — hiçbir araç çağrılmadan —
öğrenilebiliyor.

```go
// araclar/araclar.go — go komut ailesini gecici bir modulde program icinden cagiran ortak yordamlar
package araclar

import (
	"os"
	"os/exec"
	"path/filepath"
)

// Kur verilen dosyalarla gecici bir modul olusturur ve dizinini dondurur; yol hicbir yerde basilmaz.
func Kur(dosyalar map[string]string) string {
	dizin, _ := os.MkdirTemp("", "sinama")
	os.WriteFile(filepath.Join(dizin, "go.mod"), []byte("module sinama\n\ngo 1.24\n"), 0o644)
	for ad, icerik := range dosyalar {
		os.WriteFile(filepath.Join(dizin, ad), []byte(icerik), 0o644)
	}
	return dizin
}

// Cagir dilin kendi komutunu gecici modulde kosturur; ciktinin kendisi degil basari donuyor.
func Cagir(dizin string, ekOrtam []string, arg ...string) (string, bool) {
	cmd := exec.Command("go", arg...)
	cmd.Dir = dizin
	cmd.Env = append(append(os.Environ(), "GOTOOLCHAIN=local"), ekOrtam...)
	cikti, err := cmd.CombinedOutput()
	return string(cikti), err == nil
}
```

`Kur`, ölçümün her seferinde tamamen boş bir modülden başlaması için gerekli; aksi
takdirde bir önceki koşumun bıraktığı bir dosya sonucu etkileyebilir. `Cagir` ise dilin
komut ailesini her zaman aynı yoldan çağırıyor: alt süreç olarak, sabit bir ortamla
ve yalnız çıktıyla başarı bilgisini geri döndürerek. Bu iki yordam konunun altı dersi
boyunca aynı biçimde kullanılıyor; hiçbir ders komut adını düzyazıda anmıyor, yalnız
kod bloğunun içinde bir çağrının argümanı olarak geçiyor.

`araclar` işlevleri kendi paketinde duruyor — Go Temelleri kursunda kurulan paket ve
görünürlük ayrımı burada aynen işliyor: `Kur` ve `Cagir` büyük harfle başladığı için
`main` paketinden `ders/araclar` yolunu içe aktararak çağrılabiliyor. Ölçümün asıl gövdesi
— hangi kaynağın sınandığı, hangi sorunun sorulduğu — her zaman `main` paketinde kalıyor;
paket sınırı yalnız tekrarlanan kurulum ve çağrı mantığını bir kez yazıp altı derste de
kullanmak için çiziliyor.

#### Karar Yalnız Koşarak Geliyor

Test dosyasının kaç test içerdiği kaynaktan okunabiliyor, ama bu testlerin **geçip
geçmediği** aynı biçimde okunamıyor. `go test` çağrıldığında test paketi önce paketteki
bütün dosyaları derliyor, sonra adı `Test` ile başlayan işlevleri bulup her birini
sırayla çalıştırıyor ve her birinin sonunda kesilip kesilmediğine bakıyor. Bu iki adım —
derleme ve çalıştırma — birbirinden ayrı: bir test dosyası derlenebilir olduğu hâlde
içindeki testlerin hiçbiri geçmeyebilir, ya da tam tersi. Derleme adımı da bir bilgi
kaynağı, ama farklı bir sınıftan: paket derlenmiyorsa bu, testleri hiç çalıştırmadan,
yalnız kaynağı derleyiciden geçirerek öğrenilebiliyor — o hâlde bu bir araç bilgisi.
Derlenen bir paketin testlerinin geçip geçmediği ise derleme bittikten sonra, testler
fiilen çalıştırıldıktan sonra ortaya çıkıyor; bu da bir koşum bilgisi. Aşağıdaki ölçüm
ikisini de karıştırmadan ayırıyor: `envanter`
paketinin doğru bir sürümüne karşı iki testi de koşturuyor, sonra aynı test dosyasını
hiç değiştirmeden paketin bozuk bir sürümüne karşı yeniden koşturuyor.

```go
// olcum.go — testin karari kaynaktan degil kosturarak okunuyor
package main

import (
	"fmt"
	"strings"

	"ders/araclar"
)

const envanterMetni = `package sinama

type Kalem struct {
	Ad    string
	Adet  int
	Fiyat int
}

func ToplamDeger(kalemler []Kalem) int {
	toplam := 0
	for _, k := range kalemler {
		toplam += k.Adet * k.Fiyat
	}
	return toplam
}
`

const bozukEnvanterMetni = `package sinama

type Kalem struct {
	Ad    string
	Adet  int
	Fiyat int
}

func ToplamDeger(kalemler []Kalem) int {
	toplam := 0
	for _, k := range kalemler {
		toplam += k.Adet
	}
	return toplam
}
`

const testMetni = `package sinama

import "testing"

func orneklerVer(t *testing.T) []Kalem {
	t.Helper()
	return []Kalem{{"kalem", 3, 250}, {"defter", 2, 990}}
}

func TestToplamDeger(t *testing.T) {
	kalemler := orneklerVer(t)
	if got := ToplamDeger(kalemler); got != 2730 {
		t.Errorf("ToplamDeger = %d, beklenen 2730", got)
	}
}

func TestBosIddia(t *testing.T) {
	orneklerVer(t)
}
`

func testSayisi(kod string) int {
	return strings.Count(kod, "\nfunc Test")
}

func main() {
	fmt.Println("-- kaynak: test dosyasindaki test sayisi --")
	fmt.Println("dosyadaki test islevi sayisi:", testSayisi(testMetni))

	fmt.Println()
	fmt.Println("-- kosum: dogru kaynaga karsi karar --")
	dogru := araclar.Kur(map[string]string{"envanter.go": envanterMetni, "envanter_test.go": testMetni})
	ciktiDogru, _ := araclar.Cagir(dogru, nil, "test", "-v", "./...")
	fmt.Println("TestToplamDeger gecti mi:", strings.Contains(ciktiDogru, "--- PASS: TestToplamDeger"))
	fmt.Println("TestBosIddia gecti mi:", strings.Contains(ciktiDogru, "--- PASS: TestBosIddia"))

	fmt.Println()
	fmt.Println("-- kosum: ayni test dosyasi, bozuk kaynaga karsi karar --")
	fmt.Println("dosyadaki test islevi sayisi:", testSayisi(testMetni))
	bozuk := araclar.Kur(map[string]string{"envanter.go": bozukEnvanterMetni, "envanter_test.go": testMetni})
	ciktiBozuk, _ := araclar.Cagir(bozuk, nil, "test", "-v", "./...")
	fmt.Println("TestToplamDeger gecti mi:", strings.Contains(ciktiBozuk, "--- PASS: TestToplamDeger"))
}
```

```
-- kaynak: test dosyasindaki test sayisi --
dosyadaki test islevi sayisi: 2

-- kosum: dogru kaynaga karsi karar --
TestToplamDeger gecti mi: true
TestBosIddia gecti mi: true

-- kosum: ayni test dosyasi, bozuk kaynaga karsi karar --
dosyadaki test islevi sayisi: 2
TestToplamDeger gecti mi: false
```

**TS2.** İki koşum arasında değişen tek şey `envanter.go`'nun içeriği; test dosyası bayt
bayt aynı ve kaynaktan sayılan test sayısı da ikisinde de aynı: 2. Değişen şey, bu iki
testin **koşturulduğunda** verdiği karar — `TestToplamDeger`, birim fiyatı çarpmayı
unutan bozuk sürüme karşı çalıştığında `kaldı` diyor. Kaynağı okuyarak bu farkı
öngörmenin tek yolu, iki `ToplamDeger` gerçekleştirimini elle karşılaştırıp hangisinin
doğru olduğuna kendiniz karar vermek — test paketinin kendisi bu kararı kaynağı okuyarak
değil, `go test` çağrısını gerçekten çalıştırarak veriyor. Yukarıdaki ölçüm de tam olarak
bunu yapıyor: `Cagir` yordamı `go test -v ./...` komutunu alt süreç olarak koşturuyor ve
program yalnız çıktıdaki `--- PASS: <ad>` satırının varlığına bakarak bir doğruluk
değeri okuyor. Ne hata metni ne de testin ürettiği ham çıktı hiçbir yerde basılıyor —
yalnız sorulan tek şey döndürülüyor: bu test geçti mi.

Ölçümün iki koşumu da aynı `Kur` yordamıyla, aynı test dosyasıyla ve aynı komutla
çalışıyor; tek fark hangi `envanter.go` içeriğinin gecici modüle yazıldığı. Bu, sonucun
test çerçevesinden değil, sınanan kaynaktan geldiğini gösteriyor — ölçümün kendisi bir
kez yazılıp iki kez çağrılıyor ve iki çağrı arasındaki fark yalnız girdide.

Ölçüm `-v` bayrağını kullanıyor ve bu seçim gelişigüzel değil: bayrak verilmeden
`go test` yalnız paketin tamamı için tek bir karar veriyor — paketteki testlerin
tamamı geçtiyse başarı, herhangi biri kaldıysa başarısızlık. `TestToplamDeger` ve
`TestBosIddia`'nın **ayrı ayrı** hangi kararı verdiğini okumak için ayrıntılı çıktı
gerekiyor, çünkü test paketi bu bilgiyi yalnız `-v` istendiğinde her test için ayrı bir
satırda yazıyor. Paket düzeyindeki tek karar da bir koşum bilgisi, ama daha kaba bir
taneciklikte: hangi testin kaldığını değil, yalnız hiç kalan olup olmadığını söylüyor.
Üretim bir kod tabanında yüzlerce test tek bir pakette birikince, bu iki taneciklik
arasındaki fark — bir şeyin bozulduğunu bilmekle **neyin** bozulduğunu bilmek arasındaki
fark — pratikte büyük bir zaman farkına dönüşüyor.

#### Sınırlayıcı Ölçüm: Geçen Test Ne Söylüyor

Yukarıdaki koşumda `TestBosIddia` de geçti — hem doğru kaynağa karşı hem de (dosyaya
bakılırsa) bozuk kaynağa karşı geçmeye devam ederdi, çünkü bu işlev hiçbir şeyi
sınamıyor. `orneklerVer` yardımcısını çağırıyor, dönen değeri hiç kullanmadan bırakıyor
ve hiçbir `t.Error` ya da `t.Fatal` çağrısı içermiyor. Test paketinin gözünde bir test
işlevi, çalışması bittiğinde `t.Error`, `t.Fatal` ya da bir panikle kesilmemişse
**geçmiş** sayılıyor — işlevin gövdesinde bir iddia bulunup bulunmadığı bu kararı hiç
etkilemiyor.

**TS3.** Bu, konunun ilk sınırlayıcı ölçümü: geçen bir test **doğru olduğunu**
söylemiyor, yalnız **sınadığı şeyin tuttuğunu** söylüyor — ve hiçbir şey sınamayan bir
test de aynı biçimde geçiyor, çünkü test paketinin ölçtüğü şey iddianın var olup
olmadığı değil, var olan iddianın kesilip kesilmediği. `TestBosIddia` bu farkın en yalın
görünümü: adı bir sınamayı vaat ediyor, ama gövdesi hiçbir şeyi doğrulamıyor ve test
paketinin kendisi bu boşluğu fark edecek bir mekanizma taşımıyor. Bir test paketinin bu
soruyu sormaması bir eksiklik değil, sınırının kendisi — test paketi yalnız "çalışırken
bir şey kesildi mi" sorusuna yanıt veriyor, "bu iddia yeterli mi" sorusuna değil. Bu
sınır konunun ilerleyen derslerinde de geçerli kalıyor: bir sonraki ders vaka listesini
kaynaktan sayarken, koşan alt test sayısının kaynaktaki vaka sayısıyla eşleşip
eşleşmediğini soruyor — ama hiçbir zaman "bu vakalar yeterli mi" sorusunu sormuyor,
çünkü test paketi bu soruyu hiç yanıtlamıyor.

Bu sınırın üretim kodunda somut bir karşılığı var: bir işlevin gövdesi tamamen boş
bırakılıp yalnız bir yorum satırıyla ileride tamamlanacağı not edilse bile, o işlevi
çağıran ve hiçbir şeyi doğrulamayan bir test hâlâ geçiyor. Bir sınama takımının yeşil
görünmesi — bütün testlerin geçmiş görünmesi — bu yüzden tek başına yeterli bir kanıt
değil; kaç iddianın gerçekten çalıştığı da ayrı bir sorudur ve konunun kapanış dersi
tam olarak bu ayrımı, kapsama oranı üzerinden yeniden ölçüyor.

Bu sınır test paketinin bir kusuru değil, sınırlarının nerede çizildiğinin bir sonucu.
Test paketi tek bir soruyu yanıtlamak için tasarlandı: bir çalışma kesintiye uğradı mı,
uğramadı mı. Bir iddianın **anlamlı** olup olmadığı — yanlış bir girdide de aynı sonucu
verip vermeyeceği, yeterince geniş bir aralığı kapsayıp kapsamadığı — bambaşka bir soru
ve bu soruyu yanıtlamak yazan kişinin işi, aracın işi değil. `TestBosIddia` bu farkı
en yalın biçimiyle gösteriyor çünkü hiçbir iddia içermiyor; ama aynı boşluk, yanlışlıkla
her zaman doğru çıkan bir koşulla yazılmış bir iddiada da ortaya çıkabiliyor ve o durumda
fark kaynaktan bakan bir göze çok daha az belirgin.

`envanter` paketinin kendisi de bu dersin geri kalanında sabit kalıyor: `Kalem` ve
`ToplamDeger`, konu boyunca gelişen aynı örnek üzerinde çalışıyor. Sonraki ders aynı
paketin bir fonksiyonunu tablo güdümlü bir testle sınıyor ve vaka listesinin kendisinin
nerede yazılı olduğunu soruyor.

#### Özet

- Bir işlevin test paketi tarafından test sayılması, adının `Test` ile başlaması, tek
  bir `*testing.T` parametresi alması ve dosya adının `_test` ile bitmesi kuralına
  bağlı; bu kural kaynaktan okunabiliyor.
- Kaynaktaki test sayısı metinden sayılabiliyor, ama bir testin geçip geçmediği yalnız
  `go test` çalıştırılarak öğrenilebiliyor — aynı test dosyası, paketin doğru ve bozuk
  sürümlerine karşı farklı kararlar veriyor.
- Ölçüm aracı program içinden çağrılıyor ve yalnız istenen doğruluk değeri okunuyor;
  testin ham çıktısı hiçbir yerde basılmıyor.
- Sınırlayıcı ölçüm: geçen bir test doğru olduğunu değil, sınadığı şeyin tuttuğunu
  söylüyor — hiçbir iddia içermeyen bir test de aynı biçimde geçiyor.
- `envanter` paketi konu boyunca aynı örnek olarak sürüyor; bu derste yalnız
  `ToplamDeger` kullanıldı.

#### Sonraki Adım

Bu derste bir testin kararının yalnız koşturarak öğrenilebildiği ve geçen bir testin "doğru" değil
yalnız "kesilmedi" dediği ölçüldü. Ölçümün kendisi de konunun geri kalanı boyunca değişmeyecek iki
yordama — `araclar.Kur` ve `araclar.Cagir` — dayandı; sonraki dersler aynı iki yordamı kendi test
dosyasıyla, kendi kaynağıyla ve kendi sorusuyla yeniden çağıracak — tek istisna, işleyiciyi
doğrudan çağırdığı için `go test`i hiç alt süreç olarak koşturmayan HTTP dersi. Sıradaki ders aynı
paketin başka bir işlevini tablo güdümlü bir testle sınıyor — bu kez soru testin kararı değil,
vaka listesinin kendisi: kaç vaka yazılı, kaç tanesi gerçekten farklı bir satırı koşturuyor ve
vaka eklemenin her zaman daha fazla şey sınamak anlamına gelip gelmediği.

### Tablo Güdümlü Testler

Source: https://academia.sh/tr/kurslar/go-uretim/tablo-gudumlu-testler

Beş vakalık bir tablonun vaka sayısı kaynaktan sayılıyor, koşan alt test sayısı ise yalnız go test -v ile koşturularak okunuyor ve ikisi 5'te eşleşiyor. Sınırlayıcı ölçüm, tabloya aynı satırları koşturan altıncı bir vaka eklendiğinde kapsama oranının 100.0% olarak değişmeden kaldığını gösteriyor.

Önceki ders bir testin verdiği kararın kaynaktan değil koşumdan geldiğini ölçtü; kaç test
işlevi yazıldığı kaynaktan sayılabiliyordu, ama hangisinin geçtiği yalnız `go test`
çalıştırılarak öğrenilebiliyordu. Bu ders aynı ayrımı bir düzey daha ayrıntılı bir yere
taşıyor: tek bir test işlevinin içinde, birden çok **vaka**nın nasıl sınandığına. Vaka
listesinin kendisi bu kez kaynakta yazılı — konunun iki "kaynak" satırından biri — ama
o listenin koştuğunda kaç ayrı alt test doğurduğu yine yalnız koşumdan okunabiliyor.

Bu kalıba tablo güdümlü test deniyor ve Go'ya özgü değil; aynı kalıp başka dillerin test
kütüphanelerinde parametreli test adıyla geçiyor; burada taksonomi tekrar anlatılmıyor. Bu dersin
eklediği soru dar: test paketinin kendi `t.Run` yordamı bir tabloyu alt testlere nasıl bölüyor ve
vaka sayısıyla koşan alt test sayısı arasındaki ilişki gerçekten birebir mi. Bu soruyu yanıtlamak,
kaynaktan sayılabilen bir bilgiyle koşumdan okunan bir bilgiyi aynı ölçümde yan yana koymayı
gerektiriyor.

Üretim kod tabanlarında tek bir işlevi onlarca girdiyle sınamak sık görülen bir ihtiyaç
ve bu ihtiyacı her girdi için ayrı bir `func Test...` yazarak karşılamak hem tekrarlı
hem de okunması zor bir dosya doğuruyor. Tablo güdümlü biçim bu tekrarı bir kez yazılan
bir gövdeye indirgiyor; asıl soru burada, o tek gövdenin arkasında duran vaka sayısının
kaynaktan mı yoksa yalnız koşumdan mı okunabildiği.

`envanter` paketi bu derste de aynı yol dizesi altında kalıyor; yalnız içine bir işlev
daha ekleniyor. Önceki dersteki `ToplamDeger` bir sayı üretiyordu, `Gecerli` ise bir
doğruluk değeri üretiyor — ikisi de girdi olarak aynı `Kalem` tipini alıyor ve bu
süreklilik konunun altı dersi boyunca aynı paketin farklı köşelerinin sınandığını
gösteriyor.

#### Vaka Listesi Kaynakta Yazılı

`envanter` paketine bir işlev daha ekleniyor: `Gecerli`, bir kalemin adının boş
olmadığını, adedinin ve fiyatının pozitif olduğunu sırayla denetliyor ve ilk başarısız
denetimde `false` döndürüyor. Bu işlevi sınayan tablo, her satırı bir vakaya karşılık
gelen bir dilim değişkeni olarak yazılıyor.

`Gecerli`nin üç denetimi de aynı biçimde yazıldı: bir koşul, bir erken dönüş. Bu yapı tabloyu
tasarlamayı kolaylaştırıyor, çünkü her denetim için ayrı bir vaka yazmak yeterli — bir vakanın
hangi denetimi tetiklediğini görmek için işlevin gövdesine bakmak gerekmiyor; vakanın kendi adı
(`"bos ad"`, `"sifir adet"`, `"negatif fiyat"`, `"negatif adet"`) hangi denetimi hedeflediğini
zaten söylüyor. Vaka adının kendisi de bir kaynak bilgisi: hangi dalın sınandığını, o dalı yazan
kişi tabloya yazarken bildiriyor.

**TS4.** Aşağıdaki test dosyasındaki `vakalar` dilimi beş satır içeriyor; bu sayı hiçbir
şey çalıştırmadan, yalnız kaynaktaki `{"` ile başlayan satırları sayarak okunabiliyor —
tabloyu kim yazdıysa, kaç vaka eklediği de aynı kişinin elinde, kaynağın kendisinde.

Önceki dersteki `araclar.Kur` ve `araclar.Cagir` yordamları burada da aynen kullanılıyor;
tanımları değişmedi.

```go
// araclar/araclar.go — go komut ailesini gecici bir modulde program icinden cagiran ortak yordamlar
package araclar

import (
	"os"
	"os/exec"
	"path/filepath"
)

func Kur(dosyalar map[string]string) string {
	dizin, _ := os.MkdirTemp("", "sinama")
	os.WriteFile(filepath.Join(dizin, "go.mod"), []byte("module sinama\n\ngo 1.24\n"), 0o644)
	for ad, icerik := range dosyalar {
		os.WriteFile(filepath.Join(dizin, ad), []byte(icerik), 0o644)
	}
	return dizin
}

func Cagir(dizin string, ekOrtam []string, arg ...string) (string, bool) {
	cmd := exec.Command("go", arg...)
	cmd.Dir = dizin
	cmd.Env = append(append(os.Environ(), "GOTOOLCHAIN=local"), ekOrtam...)
	cikti, err := cmd.CombinedOutput()
	return string(cikti), err == nil
}
```

```go
// olcum.go — vaka sayisi kaynaktan sayiliyor, kosan alt test sayisi kosumdan okunuyor
package main

import (
	"fmt"
	"strings"

	"ders/araclar"
)

const envanterMetni = `package sinama

type Kalem struct {
	Ad    string
	Adet  int
	Fiyat int
}

func Gecerli(k Kalem) bool {
	if k.Ad == "" {
		return false
	}
	if k.Adet <= 0 {
		return false
	}
	if k.Fiyat <= 0 {
		return false
	}
	return true
}
`

const testMetniBes = `package sinama

import "testing"

var vakalar = []struct {
	ad      string
	kalem   Kalem
	istenen bool
}{
	{"gecerli", Kalem{"kalem", 3, 250}, true},
	{"bos ad", Kalem{"", 3, 250}, false},
	{"sifir adet", Kalem{"kalem", 0, 250}, false},
	{"negatif fiyat", Kalem{"kalem", 3, -10}, false},
	{"negatif adet", Kalem{"kalem", -1, 250}, false},
}

func TestGecerli(t *testing.T) {
	for _, v := range vakalar {
		t.Run(v.ad, func(t *testing.T) {
			if got := Gecerli(v.kalem); got != v.istenen {
				t.Errorf("Gecerli(%v) = %v, istenen %v", v.kalem, got, v.istenen)
			}
		})
	}
}
`

func vakaSayisi(kod string) int {
	return strings.Count(kod, "\n\t{\"")
}

func main() {
	fmt.Println("-- kaynak: vaka sayisi --")
	fmt.Println("bes vakali tabloda:", vakaSayisi(testMetniBes))

	fmt.Println()
	fmt.Println("-- kosum: kosan alt test sayisi --")
	dizin := araclar.Kur(map[string]string{"envanter.go": envanterMetni, "envanter_test.go": testMetniBes})
	cikti, _ := araclar.Cagir(dizin, nil, "test", "-v", "./...")
	fmt.Println("kosan alt test sayisi:", strings.Count(cikti, "--- PASS: TestGecerli/"))
}
```

```
-- kaynak: vaka sayisi --
bes vakali tabloda: 5

-- kosum: kosan alt test sayisi --
kosan alt test sayisi: 5
```

**TS5.** Kaynaktan sayılan vaka sayısı ile koşumdan okunan alt test sayısı bu ölçümde
eşleşiyor: 5. Bu eşleşme rastlantı değil, `t.Run`'ın kendi kuralının sonucu — döngü
`vakalar` dilimini gezerken her turda `t.Run(v.ad, ...)` çağrısı bir alt test açıyor ve
test paketi bu alt testi çağırana verdiği adla (`v.ad`) raporluyor: çıktıda
`TestGecerli/gecerli`, `TestGecerli/bos_ad` gibi satırlar beliriyor. `strings.Count`
çağrısı bu satırların kaç tanesinin `PASS` ile bittiğini sayıyor ve tablo doğru
yazıldığı sürece bu sayı her zaman `vakalar`ın uzunluğuna eşit çıkıyor — vaka sayısı ile
alt test sayısı arasındaki bağ, döngünün kendisinde kurulu.

Bu eşleşme, tek bir `TestGecerli` işlevinin aslında beş ayrı sınama yürüttüğünü ama
kaynakta beş ayrı işlev olarak görünmediğini gösteriyor. Önceki derste kaynaktan
sayılan şey `func Test` geçişleriydi ve test işlevi sayısını veriyordu; burada sayılan
şey aynı işlevin içindeki veri satırları ve verdiği şey alt test sayısı. İkisi de
kaynaktan okunabilen bir bilgi, ama iki ayrı düzeyde: bir dosyada kaç test işlevi var
ve bir test işlevi kaç alt test açıyor.

Alt test adları da kendi başına küçük bir dönüşümden geçiyor: `v.ad` içindeki boşluk
karakterleri test paketi tarafından alt çizgiye çevriliyor, bu yüzden `"bos ad"` vakası
çıktıda `TestGecerli/bos_ad` olarak görünüyor. Bu dönüşüm ölçümü etkilemiyor çünkü
`strings.Count` yalnız `--- PASS: TestGecerli/` ön ekini arıyor, tam adı değil — ama
kaynaktaki bir vaka adını okuyup çıktıda birebir aynı dizgiyi aramak isteyen biri bu
dönüşümü bilmek zorunda, çünkü kaynaktaki `"bos ad"` ile koşumdaki `bos_ad` aynı vakaya
işaret etse de aynı bayt dizisi değil.

#### Sınırlayıcı Ölçüm: Vaka Eklemek Kapsamayı Artırmayabilir

Tabloya bir vaka eklemek sezgisel olarak "daha fazla sınamak" gibi görünüyor. Aşağıdaki
ölçüm bunun her zaman doğru olmadığını gösteriyor: beş vakalık tabloya, `Gecerli`nin
zaten sınadığı bir satırı yeniden koşturan altıncı bir vaka ekleniyor — ad yine boş, ama
adet ve fiyat farklı — ve kapsama oranı iki tabloda da ölçülüyor.

```go
// kapsam.go — ayni satiri kosturan yeni bir vaka kapsama oranini degistirmiyor
package main

import (
	"fmt"
	"strings"

	"ders/araclar"
)

const envanterMetni = `package sinama

type Kalem struct {
	Ad    string
	Adet  int
	Fiyat int
}

func Gecerli(k Kalem) bool {
	if k.Ad == "" {
		return false
	}
	if k.Adet <= 0 {
		return false
	}
	if k.Fiyat <= 0 {
		return false
	}
	return true
}
`

const testMetniBes = `package sinama

import "testing"

var vakalar = []struct {
	ad      string
	kalem   Kalem
	istenen bool
}{
	{"gecerli", Kalem{"kalem", 3, 250}, true},
	{"bos ad", Kalem{"", 3, 250}, false},
	{"sifir adet", Kalem{"kalem", 0, 250}, false},
	{"negatif fiyat", Kalem{"kalem", 3, -10}, false},
	{"negatif adet", Kalem{"kalem", -1, 250}, false},
}

func TestGecerli(t *testing.T) {
	for _, v := range vakalar {
		t.Run(v.ad, func(t *testing.T) {
			if got := Gecerli(v.kalem); got != v.istenen {
				t.Errorf("Gecerli(%v) = %v, istenen %v", v.kalem, got, v.istenen)
			}
		})
	}
}
`

const testMetniAlti = `package sinama

import "testing"

var vakalar = []struct {
	ad      string
	kalem   Kalem
	istenen bool
}{
	{"gecerli", Kalem{"kalem", 3, 250}, true},
	{"bos ad", Kalem{"", 3, 250}, false},
	{"sifir adet", Kalem{"kalem", 0, 250}, false},
	{"negatif fiyat", Kalem{"kalem", 3, -10}, false},
	{"negatif adet", Kalem{"kalem", -1, 250}, false},
	{"bos ad, farkli miktar", Kalem{"", 9, 500}, false},
}

func TestGecerli(t *testing.T) {
	for _, v := range vakalar {
		t.Run(v.ad, func(t *testing.T) {
			if got := Gecerli(v.kalem); got != v.istenen {
				t.Errorf("Gecerli(%v) = %v, istenen %v", v.kalem, got, v.istenen)
			}
		})
	}
}
`

func vakaSayisi(kod string) int {
	return strings.Count(kod, "\n\t{\"")
}

func kapsamOrani(cikti string) string {
	for _, s := range strings.Split(cikti, "\n") {
		if i := strings.Index(s, "coverage: "); i >= 0 {
			return strings.TrimSpace(s[i+len("coverage: "):])
		}
	}
	return "yok"
}

func main() {
	dizinBes := araclar.Kur(map[string]string{"envanter.go": envanterMetni, "envanter_test.go": testMetniBes})
	ciktiBes, _ := araclar.Cagir(dizinBes, nil, "test", "-cover", "./...")
	fmt.Println("bes vakali tabloda vaka sayisi:", vakaSayisi(testMetniBes))
	fmt.Println("bes vakali tabloda kapsama orani:", kapsamOrani(ciktiBes))

	dizinAlti := araclar.Kur(map[string]string{"envanter.go": envanterMetni, "envanter_test.go": testMetniAlti})
	ciktiAlti, _ := araclar.Cagir(dizinAlti, nil, "test", "-cover", "./...")
	fmt.Println("alti vakali tabloda vaka sayisi:", vakaSayisi(testMetniAlti))
	fmt.Println("alti vakali tabloda kapsama orani:", kapsamOrani(ciktiAlti))
}
```

```
bes vakali tabloda vaka sayisi: 5
bes vakali tabloda kapsama orani: 100.0% of statements
alti vakali tabloda vaka sayisi: 6
alti vakali tabloda kapsama orani: 100.0% of statements
```

**TS6.** Vaka sayısı 5'ten 6'ya çıkıyor, ama kapsama oranı ikisinde de aynı: `100.0% of
statements`. Bunun nedeni kapsama aracının **satır** düzeyinde ölçüm yapması: altıncı vaka,
`Gecerli`nin ilk `if k.Ad == "" { return false }` satırını yeniden koşturuyor — ikinci vaka (`"bos
ad"`) zaten bu satırı bir kez koşturmuştu ve aracın gözünde bir satırın iki kez mi yoksa bir kez
mi koştuğu fark etmiyor, yalnız koşup koşmadığı önemli. Yeni vaka farklı bir `Adet` ve `Fiyat`
taşısa bile, `Gecerli` bu değerlere hiç bakmadan ilk denetimde dönüyor; kapsama aracı bu farkı
göremiyor çünkü onun ölçtüğü şey hangi **satırların** çalıştığı, hangi **değerlerin** denendiği
değil.

Beş vakalık tabloda hangi vakanın hangi satırı koşturduğu tek tek izlenebilir:
`"gecerli"` üç denetimi de geçip son `return true` satırına ulaşıyor, `"bos ad"` ilk
`return false` satırında duruyor, `"sifir adet"` ikinci `return false`'a,
`"negatif fiyat"` üçüncü `return false`'a kadar ilerleyip orada duruyor ve `"negatif adet"` yine
ikinci `return false`'da duruyor — `"sifir adet"` ile aynı satırda, farklı bir vaka
olmasına rağmen. Bu, altı vakalı tabloda neyin tekrarlandığını da netleştiriyor: yeni
vaka `"bos ad, farkli miktar"`, tıpkı `"negatif adet"`in ikinci satırı `"sifir adet"`
ile paylaşması gibi, ilk satırı `"bos ad"` ile paylaşıyor — kapsama aracının gözünde bu
paylaşım hiçbir fark yaratmıyor, ikinci bir dokunuş sayılmıyor.

Bu, konunun ölçü ekseninin bu derse özgü sınırı: kapsama aracı bir koşum bilgisi
üretiyor (kaynağa bakarak bilinemez, yalnız çalıştırılarak öğrenilir), ama ürettiği
bilginin kendisi kaba — bir satırın bir kez mi on kez mi koştuğunu, hangi girdilerle
koştuğunu ayırt etmiyor. Beş vakalık tablo zaten `Gecerli`nin dört erken dönüşünü ve
son `true` dönüşünü birer kez koşturduğu için kapsama zaten en yüksek noktasında;
altıncı vaka bu noktanın üstüne çıkamıyor, çünkü koşacak yeni bir satır kalmamış. Konunun
kapanış dersi bu sınırı genişletiyor ve kapsamayı tamamen farklı bir vaka kümesiyle
yeniden ölçüyor.

Bu ölçümün üretim tarafındaki karşılığı somut: bir gözden geçirme sırasında "kapsama
oranı yüzde yüze çıktı" ifadesi, tabloya son eklenen vakanın gerçekten yeni bir davranışı
sınadığı anlamına gelmiyor. Aynı satırı farklı bir girdiyle yeniden koşturan bir vaka,
kapsama raporunda hiçbir iz bırakmadan tabloyu büyütüyor — dosya uzuyor, koşum süresi
artıyor, ama aracın döndürdüğü sayı aynı kalıyor. Bu yüzden kapsama oranı, bir vakanın
**değerli** olup olmadığına karar vermek için tek başına yeterli bir ölçüt değil; hangi
satırın hâlâ hiç koşmadığını görmek için kapsama aracının satır satır döktüğü ayrıntılı
rapora bakmak gerekiyor — konunun kapanış dersi tam olarak bu ayrıntıyı okuyor.

Kapsama aracının bu kabalığı bir kusur değil, ne ölçtüğünün doğal bir sonucu. Araç
kaynak metnin hangi satırlarının derlenmiş ikilide karşılık bulduğunu ve koşum sırasında
kaç kez tetiklendiğini izliyor; bir satırın **hangi değerle** tetiklendiğini izlemek,
aracın tuttuğu veriyi bambaşka bir düzeye taşırdı. `Gecerli` gibi kısa, dallanması az bir
işlevde bu kabalık beş vakayla bile en yüksek orana ulaşmayı kolaylaştırıyor; daha çok
dallanan bir işlevde aynı oran çok daha fazla vaka gerektirebilir, ama aracın kendisi
her durumda aynı soruyu soruyor: bu satır en az bir kez koştu mu.

#### Özet

- Bir tablo güdümlü test dosyasındaki vaka sayısı kaynaktan sayılabiliyor; bu derste
  `vakalar` dilimindeki beş satır bir araç çağrılmadan okundu.
- `t.Run` bir tabloyu alt testlere bölüyor ve her alt testi verilen adla raporluyor; koşan
  alt test sayısı bu derste vaka sayısıyla birebir eşleşti.
- Vaka sayısı kaynaktan, alt test sayısı ise yalnız `go test -v` çalıştırılarak
  okunabiliyor — ikisi aynı sayıya çıksa da geldikleri yol farklı.
- Sınırlayıcı ölçüm: aynı satırı koşturan bir vaka eklemek kapsama oranını değiştirmedi;
  kapsama aracı satırın kaç kez ve hangi değerle koştuğunu değil, yalnız koşup
  koşmadığını sayıyor.
- Sahte bağımlılık ailesi ve kapsama ölçümü Birim Testi ve Test Odaklı Geliştirme
  kursunda kuruldu; burada yeniden anlatılmadı, yalnız Go'nun kendi düzeneği üzerinden
  ölçüldü.

#### Sonraki Adım

Bu derste bir tablonun vaka sayısıyla koşan alt test sayısının nasıl eşleştiği ve vaka eklemenin
kapsamayı her zaman artırmadığı ölçüldü. İki ölçüm de aynı `araclar.Kur` ve `araclar.Cagir`
yordamlarına dayandı; bu yordamlar konunun geri kalanında da değişmeden sürüyor — işleyiciyi
doğrudan çağıran HTTP dersi dışında. Sıradaki ders aynı soruyu ağ katmanına taşıyor: bir sunucunun
isteğe verdiği yanıt da yalnız koşturularak öğrenilebilen bir bilgi, ama bu kez gerçek bir
bağlantı noktası hiç açılmadan — sunucu ve istemci tarafı test yardımcıları aynı süreç içinde, ağ
hiç devreye girmeden nasıl kurulabiliyor?

### HTTP Testleri

Source: https://academia.sh/tr/kurslar/go-uretim/http-testleri

Aynı işleyici, gerçek bir bağlantı noktası hiç açılmadan üç istekle sınanıyor: var olan bir kalem 200, olmayan bir kalem 404, parametresiz bir istek 400 döndürüyor — üçü de yalnız isleyici.ServeHTTP çağrılarak öğrenilen bir koşum bilgisi. Sınırlayıcı ölçüm, isteğin adresinin RFC 5737'nin belge amaçlı ayırdığı sabit bir adres olduğunu okuyarak testin ağı değil işleyiciyi sınadığını gösteriyor.

Önceki ders bir tablonun vaka sayısıyla koşan alt test sayısının nasıl eşleştiğini ve bu
eşleşmenin kaynaktan mı koşumdan mı geldiğini ölçtü. Bu ders aynı soruyu ağ katmanına
taşıyor: bir sunucunun bir isteğe verdiği yanıt da yalnız koşturularak öğrenilebilen bir
bilgi — ama "koşturmak" burada gerçek bir bağlantı noktası açmak, bir soket dinlemek, bir
istemcinin o soketi çağırması anlamına gelmiyor. Test paketinin `net/http/httptest` alt
paketi bu ikisini ayırıyor: bir işleyicinin davranışını sınamak, o işleyiciyi ağın
üzerinden değil, doğrudan bir işlev çağrısıyla sınamak anlamına gelebiliyor.

Üretim ortamında çalışan bir HTTP sunucusunun davranışı, doğrudan sunucuya bağlanıp
isteği elle göndermeden sınanabiliyorsa, bu sınama sunucunun çalıştığı makinenin ağ
ayarlarına, boş bir bağlantı noktası bulunmasına ya da bir zaman aşımına **bağlı
kalmıyor** — yani sonucu ortamdan bağımsız oluyor. Bu ders bu iddiayı iki adımda ölçüyor: önce bir
işleyicinin gerçekten bir bağlantı olmadan nasıl çağrıldığı, sonra bu çağrının ürettiği
isteğin gerçek bir ağdan gelmediğinin kendisinin nasıl doğrulanabildiği.

#### İşleyici Doğrudan Çağrılıyor, Bağlantı Noktası Hiç Açılmıyor

`envanter` paketi bu derste HTTP katmanına taşınıyor: `kalemIsleyici`, bir depo
(`ad`'dan `Kalem`'e bir sözlük) alıp bir `http.HandlerFunc` döndürüyor. İşleyici üç
adımda karar veriyor: önce isteğin sorgu dizgisinden `ad` parametresini okuyor, sonra bu adın depoda
olup olmadığına bakıyor, son olarak varsa kalemin fiyatını düz metin olarak yazıyor.
İstek bir `ad` sorgu parametresi taşımıyorsa `400`, taşıdığı ad depoda yoksa `404`, varsa
kalemin fiyatını gövdede döndürüp `200` veriyor. Bu işleyicinin sıradan bir Go HTTP
sunucusuna bağlanabilmesi için tek gereken, onu bir `http.ServeMux`e kaydetmek — ama bu
ders onu hiç sunucuya bağlamıyor.

İşleyicinin imzası da bu ayrımı kolaylaştıran bir seçim: `kalemIsleyici` depoyu bir
parametre olarak alıyor ve bir `http.HandlerFunc` döndürüyor, deponun kendisi paket
düzeyinde bir değişken olarak sabitlenmiyor. Bu, aynı işleyicinin farklı bir depoyla
— örneğin bu derste kullanılan tek kalemlik sabit depoyla, ya da başka bir ölçümde boş
bir depoyla — yeniden kurulabilmesini sağlıyor; işleyicinin kendisi hangi depoyla
çalıştığını hiç bilmiyor, yalnız kendisine verilen `map[string]Kalem` değerine bakıyor.

Bu yapı bir kapanış (closure) — `kalemIsleyici`'nin döndürdüğü işlev, kendisini
oluşturan çağrıda verilen `depo` değişkenini gövdesinde tutmaya devam ediyor, tıpkı bu
konudan önceki dersin `orneklerVer` yardımcısının kendi çağıranına bağlı kalması gibi.
Test yardımcıları bu kapanışın içine hiç girmiyor; yalnız dışarıdan görünen
`http.HandlerFunc` imzasını çağırıyor ve kapanışın içinde hangi deponun tutulduğu ölçümün
kendisi için görünmez kalıyor.

İsteği kuran çağrı `http.MethodGet` sabitini kullanıyor, `"GET"` dizgisini elle
yazmak yerine — standart kitaplığın kendi tanımladığı bu sabitler, bir yöntem adının
yanlış yazılmasını derleme zamanında değil ama en azından tek bir kaynaktan yakalıyor
ve bu ders boyunca kullanılan bütün istekler aynı sabiti paylaşıyor.

`httptest` paketinin iki yordamı bu bağlantıyı gereksiz kılıyor: `httptest.NewRequest`,
bir istemcinin ağdan göndereceği bir isteğin Go karşılığını üretiyor — istemci tarafı bir
yardımcı, çünkü normalde bu isteği bir istemci bir bağlantı üzerinden yollar.
`httptest.NewRecorder` ise bir yanıtı toplayan, bir `http.ResponseWriter` gerçekleştiren bir
yapı — sunucu tarafı bir yardımcı, çünkü normalde bir yanıtı bir bağlantının karşı ucuna
yazan şey budur. İkisi birlikte kullanıldığında, `isleyici.ServeHTTP(kayit, istek)`
çağrısı işleyiciyi gerçek bir ağ yığıtına hiç dokunmadan, aynı süreç içinde, bir işlev
çağrısı gibi çalıştırıyor.

`httptest` paketinin bir de `NewServer` yordamı var ve o gerçekten bir bağlantı noktası
açıyor — verilen işleyiciyi yerel makinede geçici, boşta bulunan bir bağlantı noktasına
bağlıyor, bir adres döndürüyor ve o adrese gerçek bir istemci isteği gönderilmesini
bekliyor. Bu yordam gerçek bir ağ yolunu (bağlantı kurma, veri gönderme, bağlantıyı
kapatma) sınamak istendiğinde gerekli, ama bu derste kullanılmıyor: `kalemIsleyici`'nin
mantığını sınamak için ağ yoluna hiç ihtiyaç yok ve gerçek bir bağlantı noktası açmak
hem ölçümü yavaşlatır hem de hangi bağlantı noktasının boş olduğuna bağlı, koşumdan
koşuma değişebilecek bir bilgi ekler — tam olarak bu konunun yazılmayan yasağı. `Recorder`
ve `NewRequest` ikilisi bu maliyeti hiç doğurmadan aynı mantığı sınıyor.

#### Üç İsteğin Kararı

**TS7.** İşleyicinin kaynağında kaç ayrı durum kodu yazılı olduğu kaynaktan sayılabiliyor:
`http.StatusBadRequest` ve `http.StatusNotFound` açıkça yazılı, üçüncü durum (`http.StatusOK`,
hiçbir durum kodu yazılmadığında `net/http`'nin kendi varsayımı) hiçbir sabitle anılmadan örtük
duruyor — toplam üç ayrı sonuç kaynaktan çıkarılabiliyor. Hangi isteğin **hangi** sonucu
tetikleyeceği ise kaynaktan değil, işleyiciyi gerçekten çağırarak öğreniliyor.

`durumKoduSayisiKaynakta` bu sayımı gerçekten kod bloğunu bir araç ya da bir çalıştırma
kullanmadan yapıyor: işleyicinin gövdesini bir dizgi olarak alıp içinde `StatusBadRequest`
ve `StatusNotFound` sabitlerinin geçip geçmediğine bakıyor, `StatusOK`'in kendisi hiç
yazılmadığı için onu örtük bir varsayım olarak sayıyor. Bu, önceki iki derste kurulan
kaynak sayımı yordamının (`func Test` geçişlerini saymak, `{"` ile başlayan satırları
saymak) bir başka biçimi — burada sayılan şey bir işlev adı ya da bir vaka satırı değil,
bir sabitin adı, ama yöntem aynı: kaynağı bir dizgi olarak okuyup içinde aranan bir
kalıbın kaç kez geçtiğini saymak.

```go
// olcum.go — ayni isleyici uc istekle sinaniyor, gercek baglanti noktasi acilmiyor
package main

import (
	"fmt"
	"net/http"
	"net/http/httptest"
	"strings"
)

type Kalem struct {
	Ad    string
	Adet  int
	Fiyat int
}

func kalemIsleyici(depo map[string]Kalem) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		ad := r.URL.Query().Get("ad")
		if ad == "" {
			http.Error(w, "ad gerekli", http.StatusBadRequest)
			return
		}
		k, bulundu := depo[ad]
		if !bulundu {
			http.Error(w, "bulunamadi", http.StatusNotFound)
			return
		}
		fmt.Fprintf(w, "%d", k.Fiyat)
	}
}

const isleyiciKaynagi = `
		ad := r.URL.Query().Get("ad")
		if ad == "" {
			http.Error(w, "ad gerekli", http.StatusBadRequest)
			return
		}
		k, bulundu := depo[ad]
		if !bulundu {
			http.Error(w, "bulunamadi", http.StatusNotFound)
			return
		}
		fmt.Fprintf(w, "%d", k.Fiyat)
`

func durumKoduSayisiKaynakta(kod string) int {
	n := 1 // yazili sabiti olmayan varsayilan basarili donus
	for _, s := range []string{"StatusBadRequest", "StatusNotFound"} {
		if strings.Contains(kod, s) {
			n++
		}
	}
	return n
}

func main() {
	fmt.Println("-- kaynak: kac ayri durum kodu yazili --")
	fmt.Println("isleyicide yazili durum kodu sayisi:", durumKoduSayisiKaynakta(isleyiciKaynagi))

	depo := map[string]Kalem{"kalem": {"kalem", 3, 250}}
	isleyici := kalemIsleyici(depo)

	fmt.Println()
	fmt.Println("-- kosum: uc istegin karari --")
	istek1 := httptest.NewRequest(http.MethodGet, "/kalem?ad=kalem", nil)
	kayit1 := httptest.NewRecorder()
	isleyici.ServeHTTP(kayit1, istek1)
	fmt.Println("var olan kalem, durum kodu:", kayit1.Code, "govde:", kayit1.Body.String())

	istek2 := httptest.NewRequest(http.MethodGet, "/kalem?ad=olmayan", nil)
	kayit2 := httptest.NewRecorder()
	isleyici.ServeHTTP(kayit2, istek2)
	fmt.Println("olmayan kalem, durum kodu:", kayit2.Code)

	istek3 := httptest.NewRequest(http.MethodGet, "/kalem", nil)
	kayit3 := httptest.NewRecorder()
	isleyici.ServeHTTP(kayit3, istek3)
	fmt.Println("parametresiz istek, durum kodu:", kayit3.Code)
}
```

```
-- kaynak: kac ayri durum kodu yazili --
isleyicide yazili durum kodu sayisi: 3

-- kosum: uc istegin karari --
var olan kalem, durum kodu: 200 govde: 250
olmayan kalem, durum kodu: 404
parametresiz istek, durum kodu: 400
```

**TS8.** Kaynakta üç ayrı durum kodu yazılı olduğu bilinse de, hangi isteğin hangi kodu
tetikleyeceği ancak `ServeHTTP` çağrıldıktan sonra okunuyor — üç `httptest.NewRequest` çağrısı
birer istemci isteği kuruyor; üç `httptest.NewRecorder` bu isteklerin yanıtlarını ayrı ayrı
topluyor ve `kayit.Code` her seferinde işleyicinin fiilen yazdığı durum kodunu döndürüyor. Bu,
önceki iki derste kurulan aynı ayrımın ağ katmanındaki karşılığı: kaç sonuç **olabileceği**
kaynaktan sayılabiliyor, hangi sonucun **gerçekleştiği** yalnız koşumdan okunabiliyor. Test Paketi
Temelleri'nde bu ayrım bir işlevin geçip geçmediğiydi, Tablo Güdümlü Testler'de bir vakanın kaç
alt teste dönüştüğüydü; burada aynı ayrım bir isteğin hangi durum koduna dönüştüğü sorusuna
uygulanıyor — üç ders de aynı iskeleti farklı bir yüzeyde koşturuyor.

Üç isteğin de aynı `isleyici` değişkeni üzerinden geçmesi önemli — işleyici bir kez
kuruluyor, sonra üç ayrı girdiyle üç kez çağrılıyor, tıpkı bir sunucunun gerçek
çalışmasında aynı işleyicinin ardışık isteklere yanıt vermesi gibi. Aradaki fark, burada
bu ardışık çağrıların bir dinleyici soketten değil, doğrudan bir döngüden geldiği.

İlk isteğin gövdesi de ayrıca okunuyor: `kayit1.Body.String()` yalnız durum kodunu değil,
işleyicinin `fmt.Fprintf` ile yazdığı `250` dizgisini de döndürüyor — önceki derslerdeki
`envanter` paketinin kuruş cinsinden fiyat kararı burada da aynen sürüyor. Diğer iki
istekte gövde okunmuyor, çünkü `http.Error` çağrısının yazdığı metin bu ölçümün sorusuyla
ilgisiz; sorulan tek şey durum kodu ve `http.Error`'ın ürettiği metin sürüme bağlı,
kaynakta yazılı olmayan bir ayrıntı. Bu, ikinci ve üçüncü ölçümde araç çıktısının
kendisini basmama kuralının doğal bir uzantısı: yalnız sorulan ölçü okunuyor, geri kalan
her şey görmezden geliniyor.

#### Sınırlayıcı Ölçüm: Sınanan Şey Ağ Değil İşleyici

**TS9.** `httptest.NewRequest`'in ürettiği isteğin `RemoteAddr` alanı hep aynı sabit
değeri taşıyor: `192.0.2.1:1234`. Bu adres rastgele değil — RFC 5737'nin belgeleme ve
örnek amaçlı ayırdığı, hiçbir gerçek ağda yönlendirilmeyen bir blok. `httptest` paketi
bu adresi her çağrıda aynen kullanıyor, çünkü ürettiği istek hiçbir zaman gerçek bir
bağlantıdan gelmiyor; bir bağlantı hiç kurulmadığı için gerçek bir uzak adres de yok.

Gerçek bir HTTP sunucusunda `RemoteAddr` alanı, bağlantıyı açan istemcinin gerçek
adresini taşır ve her istekte, her koşumda değişebilir — tam olarak bu konunun
başından beri basılmaması söylenen türden bir bilgi. `httptest.NewRequest`'in bu alana
sabit bir değer koyması, aracın kendi tasarımının bir sonucu: bir istek gerçek bir
bağlantıdan gelmediği için, dolduracak gerçek bir adres de yok ve paket bu boşluğu
rastgele ya da boş bırakmak yerine, hiçbir gerçek ağda karşılığı olmayan sabit bir
değerle dolduruyor. Bu seçim, bir işleyicinin `RemoteAddr` alanına bakan bir mantığı
(örneğin bir erişim kısıtlaması) varsa, bu mantığın `httptest` altında hep aynı sabit
adresle karşılaşacağını da ima ediyor — bu ölçümde `kalemIsleyici`'nin böyle bir mantığı
yok, ama bu sınır ileride benzer bir işleyici sınanırken hatırlanması gereken bir
ayrıntı — bir erişim kısıtlaması gerçek bir dağıtımda doğru çalışsa bile, bu test
yardımcısıyla yazılmış bir sınama o kısıtlamayı hiçbir zaman gerçekten sınamaz, çünkü
sınamanın kendisi sabit bir adresle çalışır.

```go
// sinir.go — istegin adresi belge amacli ayrilmis sabit bir blok, gercek baglanti degil
package main

import (
	"fmt"
	"net/http"
	"net/http/httptest"
	"strings"
)

func main() {
	istek := httptest.NewRequest(http.MethodGet, "/kalem?ad=kalem", nil)
	belgeAmacliMi := strings.HasPrefix(istek.RemoteAddr, "192.0.2.")
	fmt.Println("istegin adresi belge amacli ayrilmis bir blokta mi:", belgeAmacliMi)
}
```

```
istegin adresi belge amacli ayrilmis bir blokta mi: true
```

Bu derste kullanılan test yardımcısı, dolayısıyla, işleyicinin **mantığını** sınıyor —
girdiye göre doğru durum kodunu üretip üretmediğini — ağ katmanının kendisini değil.
Gerçek bir ağ bağlantısı burada hiç kurulmadı, hiç dinlenmedi, hiç kapatılmadı; sınama
tamamen aynı sürecin içinde, bir işlev çağrısı zincirinde kaldı. Bu bir eksiklik değil,
bilinçli bir sınır: bir işleyicinin mantığını sınamak için gerçek bir soket açmak
gereksiz bir maliyet ve gereksiz bir kırılganlık kaynağı — bir bağlantı noktasının
başka bir süreç tarafından kullanılıyor olması gibi, işleyicinin kendi mantığıyla hiç
ilgisi olmayan bir başarısızlık nedeni ekliyor. Ama bu sınırın bir bedeli de var: bu
test, gerçek bir dinleyicinin kurulumunda, zaman aşımlarında ya da eşzamanlı bağlantı
yönetiminde çıkabilecek bir kusuru hiç yakalayamıyor, çünkü o katman bu ölçümde hiç
çalışmadı.

Bu ayrım, işleyicinin kendi imzasında da görünür duruyor: `http.HandlerFunc`, bir
`http.ResponseWriter` ile bir `*http.Request` alan sıradan bir Go işlevi — ne bir soket
tanıtıcısı alıyor ne de bir ağ bağlamı. İşleyicinin gördüğü tek şey bu iki değer ve test
yardımcıları da tam olarak bu iki değeri, gerçek bir bağlantı kurmadan üretmenin bir
yolu. Bu yüzden bir işleyicinin ağdan mı yoksa `httptest`'ten mi çağrıldığını, işleyicinin
kendi gövdesinden anlamak mümkün değil — işleyici ikisi arasındaki farkı hiç görmüyor,
yalnız kendisine verilen `http.ResponseWriter` ve `*http.Request` çiftiyle çalışıyor. Bu,
işleyicinin test edilebilirliğinin kaynağı: arayüzün kendisi (bu iki tip) sabit kaldığı
sürece, arkasındaki gerçekleştirim (gerçek bir bağlantı mı, bir kayıt yapısı mı)
işleyicinin gözünde fark etmiyor. Sıradaki ders aynı gözlemi bir adım öteye taşıyor —
işleyicinin kendisi değil, işleyicinin **çağırdığı** bir bağımlılık bu kez arayüzün
arkasına alınıyor.

#### Özet

- `httptest.NewRequest` istemci tarafı, `httptest.NewRecorder` sunucu tarafı bir yardımcı;
  ikisi birlikte bir işleyiciyi gerçek bir bağlantı noktası açmadan, doğrudan bir işlev
  çağrısıyla sınıyor.
- İşleyicideki durum kodu sayısı (üç) kaynaktan sayılabiliyor, ama hangi isteğin hangi
  kodu tetikleyeceği yalnız `ServeHTTP` çağrılarak öğrenilebiliyor.
- Aynı `isleyici` değişkeni üç farklı istekle art arda çağrıldı; üçünün de kararı bir
  koşum bilgisi olarak okundu, adres ve bağlantı noktası hiçbir yerde basılmadı.
- Sınırlayıcı ölçüm: `httptest.NewRequest`'in ürettiği isteğin adresi RFC 5737'nin belge
  amaçlı ayırdığı sabit bir blokta — bu, sınanan şeyin ağ değil işleyicinin kendisi
  olduğunun kanıtı.
- Bu sınır bir eksiklik değil bilinçli bir seçim, ama bedeli de var: gerçek bir
  dinleyicinin davranışına bağlı kusurlar bu testte hiç ortaya çıkmıyor.
- `httptest.NewServer` gerçek bir bağlantı noktası açabiliyor, ama bu ders onu kullanmadı;
  işleyicinin mantığını sınamak için gerçek bir ağ yoluna hiç ihtiyaç olmadığı için bu
  maliyet baştan dışlandı.

#### Sonraki Adım

Bu derste bir işleyicinin davranışının gerçek bir bağlantı hiç kurulmadan, doğrudan
çağrılarak sınandığı ölçüldü: kaynakta kaç ayrı sonuç yazılı olduğu sayılabildi, hangi
isteğin hangi sonucu doğurduğu yalnız koşarak öğrenildi ve isteğin kendisinin gerçek bir
ağdan gelmediği isteğin adresi okunarak doğrulandı. Ama işleyicinin kendisi hâlâ, `main`
işlevinin içinde elle kurulan sabit bir sözlük kullanıyordu — gerçek bir üretim
programında bu veri bir dış bağımlılıktan, örneğin bir depolama katmanından geliyor
olurdu ve o katman her sınamada gerçekten çalıştırılamayacak kadar yavaş ya da
kararsız olabilirdi. Sıradaki ders bu bağımlılığı bir arayüzün arkasına alıyor ve aynı
test gövdesinin gerçek ve sahte bir gerçekleştirimle nasıl koştuğunu ölçüyor — sahteyi
mümkün kılan şeyin, arayüzün kaynakta yazılı olması olduğunu göstererek.

### Sahteleme ve Arayüz Ayrımı

Source: https://academia.sh/tr/kurslar/go-uretim/sahteleme-ve-arayuz-ayrimi

Aynı StokAzalt gövdesi hem kayıtlı hem casus bir Bildirici gerçekleştirmesiyle koşturuluyor: normal bir azaltmada ikisi de aynı kararı veriyor ve casus nesnenin çağrı sayısı 1 okunuyor. Sınırlayıcı ölçüm, stoktan fazlasını azaltan bir çağrıda gerçek gerçekleştirmenin negatif kalanı reddettiğini ama casus nesnenin aynı çağrıyı sessizce kaydettiğini gösteriyor.

Önceki ders bir HTTP işleyicisinin gerçek bir bağlantı hiç kurulmadan sınanabildiğini
ölçtü, ama işleyicinin kendisi hâlâ elle kurulan sabit bir depoya bakıyordu. Bu ders o
depoyu, işleyicinin arkasındaki bir başka bağımlılığa — bir bildirim gönderen taraf —
taşıyor ve aynı soruyu soruyor: bu bağımlılık gerçek dünyada yavaş, kararsız ya da
yan etkili bir şeyse (bir dış sisteme yazmak gibi), bu bağımlılığa dokunmadan aynı
mantığı nasıl sınarsınız?

Önceki derste bu soru bir bakıma zaten çözülmüştü — `httptest.NewRecorder`, standart
kitaplığın kendi `http.ResponseWriter` arayüzünü gerçekleştiren, önceden yazılmış bir
sahteydi. Bu ders aynı deseni, standart kitaplığın hazır bir sahtesi olmayan bir
bağımlılığa uyguluyor: `Bildirici`'yi ve onun iki gerçekleştirmesini bu paketin kendisi
yazıyor.

Sahte bağımlılık ailesi — sahte nesne, güdük, taklit nesne, casus nesne, kukla nesne — Birim Testi
ve Test Odaklı Geliştirme kursunda taksonomisiyle birlikte kuruldu ve burada yeniden tanıtılmıyor;
ne taksonomi yeniden anlatılıyor ne de her üye bu derste ayrı ayrı gerçekleştiriliyor. Bu dersin
eklediği soru dar: sahtelemeyi mümkün kılan şey nedir ve bu şey kaynakta nerede görünüyor?

Üretim kod tabanlarında bir işlevin çağırdığı her şeyi gerçek hâliyle sınamak çoğu zaman
mümkün değil: bir bildirim gönderen taraf gerçek bir dış sisteme yazabilir, bir ödeme
sağlayıcısı gerçek parayı hareket ettirebilir, bir depolama katmanı gerçek bir sunucuya
bağlanabilir. Bu bağımlılıkların yerine geçen bir sahte, sınamayı bu maliyetlerden
kurtarıyor — ama bu kurtuluşun kendi bedeli var ve bu dersin sınırlayıcı ölçümü tam
olarak bu bedeli sayıyor. Bedel görünmez kaldığı sürece bir risk taşımıyor; bu dersin
amacı bedeli görünür kılmak, sahtelemeyi terk ettirmek değil.

#### Sahteyi Mümkün Kılan Şey Arayüz Ayrımı

`envanter` paketine bir işlev daha ekleniyor: `StokAzalt`, bir kalemin adedini azaltıyor
ve kalan miktar belirli bir eşiğin altına düştüğünde bir bildirim gönderiyor — eşik bu
derste sabit olarak 2 seçildi. Bildirimi gönderen tarafı `StokAzalt` doğrudan çağırmıyor
— bir `Bildirici` **arayüzü** üzerinden çağırıyor.

```go
// envanter.go — StokAzalt somut bir bildirim gonderen tarafa degil bir arayuze bagli
package main

import (
	"errors"
	"fmt"
)

type Kalem struct {
	Ad    string
	Adet  int
	Fiyat int
}

type Bildirici interface {
	Bildir(ad string, kalanAdet int) error
}

func StokAzalt(depo map[string]Kalem, ad string, miktar int, b Bildirici) error {
	k := depo[ad]
	k.Adet -= miktar
	depo[ad] = k
	if k.Adet < 2 {
		return b.Bildir(ad, k.Adet)
	}
	return nil
}

// kayitliBildirici gercek gerceklestirim: negatif kalani reddediyor.
type kayitliBildirici struct {
	kayitlar []string
}

func (k *kayitliBildirici) Bildir(ad string, kalanAdet int) error {
	if kalanAdet < 0 {
		return errors.New("negatif kalan bildirilemez")
	}
	k.kayitlar = append(k.kayitlar, fmt.Sprintf("%s:%d", ad, kalanAdet))
	return nil
}

// casusBildirici sahte gerceklestirim: dogrulama yapmadan cagriyi kaydediyor.
type casusBildirici struct {
	cagrilar int
}

func (c *casusBildirici) Bildir(ad string, kalanAdet int) error {
	c.cagrilar++
	return nil
}
```

**TS10.** `StokAzalt`'ın son parametresi `Bildirici` — bir yöntem imzasından ibaret bir
arayüz, `kayitliBildirici` ya da `casusBildirici` gibi somut bir tip değil. Bu, kaynaktan
doğrudan okunabilen bir karar: `StokAzalt`'ı çağıran kod, `Bildir` yöntemini taşıyan
**herhangi bir** tipi geçirebiliyor ve `StokAzalt`'ın gövdesi hangi somut tipin
geçirildiğini hiç bilmiyor. Sahtelemeyi mümkün kılan tam olarak bu: eğer `StokAzalt`
`*kayitliBildirici`'yi doğrudan alsaydı, onun yerine başka bir tip geçirmenin hiçbir
yolu olmazdı.

`envanter` paketi bu ders boyunca tek bir yeni bağımlılık kazandı ve bu bağımlılık
paketin kendi içinde tanımlandı — `Bildirici` gerçek bir dış paketten içe aktarılan bir
tip değil, `StokAzalt`'ın ihtiyaç duyduğu tek yöntemi tanımlayan, `envanter` paketinin
kendi arayüzü. Bu, arayüzü tüketen tarafın (burada `StokAzalt`) kendi ihtiyacına göre
küçük bir arayüz tanımlamasının bir örneği: arayüz, bir kitaplığın dışarıya sunduğu geniş
bir sözleşme değil, çağıranın gerçekten kullandığı kadarını kapsayan dar bir sözleşme.

`Bildirici` arayüzü tek bir yöntem taşıyor ve bu darlık kasıtlı: arayüz ne kadar dar
olursa, onu gerçekleştiren bir tip yazmak o kadar az iş gerektiriyor. `kayitliBildirici`
ve `casusBildirici` ikisi de yalnız bu tek yöntemi (`Bildir`) tanımlayarak arayüzü
sağlıyor — Go'da bir tipin bir arayüzü sağladığını ayrıca bildirmek gerekmiyor, yöntem
kümesi arayüzün istediği kümeyi kapsıyorsa tip o arayüzü kendiliğinden sağlamış oluyor.
Bu, `casusBildirici`'nin `envanter` paketinin geri kalanıyla hiçbir bağı olmadan,
yalnız `Bildir` yöntemini taşıdığı için `StokAzalt`'a geçirilebilmesinin nedeni.

Arayüzün kendisi, gerçek ve sahte gerçekleştirmenin **aynı sözleşmeyi** taşıdığını
garanti ediyor: ikisi de aynı imzayla (`Bildir(ad string, kalanAdet int) error`)
çağrılabiliyor, `StokAzalt`'ın gövdesi hangisiyle çalıştığını ayırt etmeden aynı satırı
(`b.Bildir(ad, k.Adet)`) çalıştırıyor. Ama bu sözleşme yalnız **imzayı** garanti ediyor —
imzanın arkasındaki **davranışı** değil. İki gerçekleştirmenin aynı girdiye aynı yanıtı
verip vermeyeceği, arayüzün kendisinden hiç okunamıyor; bu sorunun yanıtı aşağıdaki
ölçümde ortaya çıkıyor.

#### Aynı Gövde, İki Gerçekleştirme

`StokAzalt`'ın gövdesinde `Bildirici` tipiyle ilgili tek bir satır var: `b.Bildir(ad, k.Adet)`. Bu
satırın hangi somut tiple çalıştığı, çağıranın `StokAzalt`'a hangi değeri geçirdiğine bağlı —
işlevin kendisi bu kararı hiç vermiyor. Aşağıdaki ölçüm bu kararı çağıran tarafına bırakarak, aynı
işlevi iki kez, iki farklı üçüncü argümanla çağırıyor.

**TS11.** Aynı `StokAzalt` çağrısı, önce gerçek gerçekleştirme (`kayitliBildirici`),
sonra sahte gerçekleştirme (`casusBildirici`) ile koşturuluyor; üç adetlik bir kalemden
iki adet azaltılıyor, kalan (1) eşiğin altında olduğu için bildirim tetikleniyor.

```go
// olcum.go — ayni govde gercek ve sahte gerceklestirmeyle kosuyor
package main

import "fmt"

func main() {
	fmt.Println("-- kosum: ayni govde gercek ve sahte ile --")
	depo1 := map[string]Kalem{"kalem": {"kalem", 3, 250}}
	gercek := &kayitliBildirici{}
	hataGercek := StokAzalt(depo1, "kalem", 2, gercek)
	fmt.Println("gercek: hata var mi:", hataGercek != nil, "kayit sayisi:", len(gercek.kayitlar))

	depo2 := map[string]Kalem{"kalem": {"kalem", 3, 250}}
	sahte := &casusBildirici{}
	hataSahte := StokAzalt(depo2, "kalem", 2, sahte)
	fmt.Println("sahte: hata var mi:", hataSahte != nil, "casus cagri sayisi:", sahte.cagrilar)
}
```

```
-- kosum: ayni govde gercek ve sahte ile --
gercek: hata var mi: false kayit sayisi: 1
sahte: hata var mi: false casus cagri sayisi: 1
```

Ölçüm iki ayrı `depo` değişkeni kuruyor (`depo1`, `depo2`), aynı içerikle ama ayrı
haritalar olarak — `StokAzalt` deponun kendisini değiştirdiği için, tek bir depo iki kez
kullanılsaydı ikinci çağrı ilk çağrının bıraktığı değişmiş durumdan başlardı ve iki
gerçekleştirmenin kararı artık aynı girdiye dayanmazdı. Bu yalıtım, önceki derslerdeki
`araclar.Kur`'un her koşum için tamamen boş bir modül kurmasıyla aynı gerekçeye
dayanıyor: bir ölçümün önceki bir ölçümün izini taşımaması.

İki koşumda da `StokAzalt`'ın gövdesi bayt bayt aynı; değişen tek şey son argümana
geçirilen değer. Gerçek gerçekleştirmede kayıt listesinin uzunluğu 1'e çıkıyor — bu
gerçek bir yan etkinin izini tutuyor. Sahte gerçekleştirmede aynı çağrı `cagrilar`
sayacını 1'e çıkarıyor — casus nesnenin ölçtüğü tek şey bu: **kaç kez** çağrıldığı, **ne**
yaptığı değil. İki ölçüm de aynı sonuca (`hata var mi: false`) varıyor ve bu ölçümün
bu kadarıyla sahtenin gerçeğin yerini tuttuğu söylenebilir.

`casusBildirici` burada özellikle casus nesne — sahte bağımlılık ailesinin, çağrıları
kaydedip sonradan sorgulanmasına izin veren üyesi. `Bildir` çağrıldığında hiçbir iş
yapmıyor, yalnız bir sayacı artırıyor; bu, `StokAzalt`'ın bildirim tetikleyip
tetiklemediğini sınamak için yeterli, çünkü ölçümün sorusu "bildirim gönderildi mi",
"bildirim doğru içerikte mi" değil. Aynı senaryo, çağrı sayısı yerine çağrının
argümanlarını da tutan bir casus ile genişletilebilirdi — `casusBildirici` bu derste
en dar hâliyle, yalnız bir sayaçla, tutuluyor çünkü ölçümün sorduğu tek şey bu.

`kayitliBildirici` adının kendisi de bir seçim: bu tip yalnızca çağrıları kaydeden bir
kukla değil, gerçekten karar veren bir gerçekleştirme — negatif bir kalan miktarı
reddetme kararı, sahte olmayan, üretim koduna gidecek bir mantık. Bu ders onu "gerçek"
diye anıyor çünkü `StokAzalt`'ın üretimde birlikte çalışacağı asıl gerçekleştirmenin
küçük bir modelini kuruyor — kendisi de bu derste hiçbir dış sisteme yazmıyor, ama
taşıdığı denetim kuralı gerçek bir dış sistemin taşıyabileceği türden.

#### Sınırlayıcı Ölçüm: Sahte Gerçekleştirme Daha Az Şey Sınıyor

`StokAzalt`'ın kendisi, adedi negatife düşürecek bir çağrıyı reddetmiyor — bu bir
tasarım kararı: stoktan fazla azaltmanın nasıl ele alınacağı `StokAzalt`'ın değil,
`Bildirici`'nin sorumluluğuna bırakılmış. Bu, önceki ölçümde görünmeyen bir dal;
üç adetlik bir kalemden iki adet azaltıldığında kalan hep pozitif kalıyordu.

**TS12.** Aynı ölçüm bu kez üç adetlik bir kalemden beş adet azaltarak koşturuluyor —
kalan adet negatife düşüyor (-2) ve `Bildir` bu negatif değerle çağrılıyor.

```go
// sinir.go — gercek gerceklestirmede tutan bir varsayim sahtede hic sinanmiyor
package main

import "fmt"

func main() {
	fmt.Println("-- sinirlayici olcum: stoktan fazla azaltma --")
	depo3 := map[string]Kalem{"kalem": {"kalem", 3, 250}}
	gercek2 := &kayitliBildirici{}
	hataGercek2 := StokAzalt(depo3, "kalem", 5, gercek2)
	fmt.Println("gercek: hata var mi:", hataGercek2 != nil)

	depo4 := map[string]Kalem{"kalem": {"kalem", 3, 250}}
	sahte2 := &casusBildirici{}
	hataSahte2 := StokAzalt(depo4, "kalem", 5, sahte2)
	fmt.Println("sahte: hata var mi:", hataSahte2 != nil, "casus cagri sayisi:", sahte2.cagrilar)
}
```

```
-- sinirlayici olcum: stoktan fazla azaltma --
gercek: hata var mi: true
sahte: hata var mi: false casus cagri sayisi: 1
```

Çağrı sayısı iki gerçekleştirmede de aynı çıkıyor — `sahte2.cagrilar` yine 1, çünkü
`Bildir` yine tam olarak bir kez çağrılıyor. Değişen şey çağrının **sonucu**, çağrının
kendisi değil; casus nesne çağrının olup olmadığını doğru sayıyor, ama çağrının
taşıdığı değerin geçerli olup olmadığına hiç bakmıyor. Bu kez iki gerçekleştirme
**farklı** karar veriyor. `kayitliBildirici`, gerçek dünyada
bir dış sistemin taşıyabileceği bir varsayımı kodluyor: negatif bir kalan miktar
bildirilemez, çünkü böyle bir durum dış sistemin gözünde anlamsız. Bu varsayım ihlal
edildiğinde gerçek gerçekleştirme bir hata döndürüyor ve `StokAzalt`'ın kendisi de bu
hatayı olduğu gibi çağırana taşıyor. `casusBildirici` ise hiçbir varsayım taşımıyor —
yalnız çağrıldığını kaydediyor ve her zaman başarı bildiriyor; aynı negatif değerle
çağrıldığında sessizce `cagrilar`ı 1 artırıp `nil` döndürüyor.

Bu, konunun ölçü ekseninin bu derse özgü sınırı: yalnız `casusBildirici` kullanan bir
test takımı, `StokAzalt`'ın stoktan fazla azaltma durumunu doğru işlediğini hiçbir zaman
sınamaz — çünkü casus nesnenin kendisi bu durumu ayırt etmiyor, her çağrıyı aynı biçimde
kabul ediyor. Gerçek gerçekleştirmede tutan varsayım, sahte gerçekleştirmede hiç
sınanmadan kalıyor; sahteleme testi bu yüzden gerçek testten **daha az** şey sınıyor,
daha fazla değil. Bu bir kusur değil, sahtenin doğası: bir sahte, arkasındaki gerçek
sistemin bütün kurallarını değil, yalnız yazan kişinin kodladığı kadarını taşıyor.

Bunun üretim tarafındaki karşılığı somut: yalnız casus nesneyle yazılmış bir test takımı
yeşil görünse bile, gerçek dış sistem devreye girdiğinde `StokAzalt` bu senaryoda bir
hata ile karşılaşacak — ve bu hatayı üreten kural, dış sistemin kendi belgelerinde
yazılı olabilir ama sahte gerçekleştirmeye hiç taşınmamış olabilir. Bu, Test Paketi
Temelleri'nde ölçülen sınırın bir başka biçimi: orada geçen bir test doğru olduğunu
değil sınadığı şeyin tuttuğunu söylüyordu, burada geçen bir sahteleme testi de yalnız
sahtenin **kendi** kurallarının tuttuğunu söylüyor — gerçek sistemin kurallarının değil.
İki sınır da aynı köke iniyor: bir testin geçmesi, sınamanın kapsamı dışında kalan hiçbir
şey hakkında bir iddia taşımıyor. Sahtenin
gerçekleştirdiği yöntem sayısı ile gerçek gerçekleştirmenin yöntem sayısı burada aynı
(ikisi de yalnız `Bildir`), ama **davranış** sayısı aynı değil: gerçek gerçekleştirme
iki davranış taşıyor (kabul etme, reddetme), sahte gerçekleştirme yalnız birini (kabul
etme). Arayüzün kendisi bu farkı hiç göstermiyor, çünkü arayüz yalnız imzayı
zorunlu kılıyor, davranış sayısını değil.

Bu sınırın bir çıkış yolu var, ama bu dersin kapsamı dışında: sahte gerçekleştirmeyi
gerçek gerçekleştirmenin varsayımlarına göre periyodik olarak sınayan ayrı bir sözleşme
testi yazmak, iki tarafın davranışının birbirinden sapmadığını ayrıca doğrulayabilirdi.
Bu derste böyle bir sözleşme testi yok — amaç sahtenin sınırını göstermek, o sınırı
kapatan bir çözüm sunmak değil.

`casusBildirici` bu ölçüm için doğru seçim, çünkü sorulan soru "kaç kez çağrıldı" — sahte
bağımlılık ailesinin öbür üyeleri farklı sorulara yanıt vermek için var. Bir güdük sabit bir değer
döndürüp hiçbir şeyi kaydetmezdi; bir kukla nesne yalnız parametre yerini doldurur, `Bildir` hiç
çağrılmasa bile fark etmezdi. `StokAzalt`'ın burada ölçülen davranışı — bildirimin **tetiklenip
tetiklenmediği** ve **kaç kez** tetiklendiği — tam olarak bir casus nesnenin ölçtüğü tür bir
bilgi; bu yüzden beş üyeden bu ders yalnız onu kullandı.

#### Özet

- `StokAzalt`'ın son parametresi somut bir tip değil bir arayüz (`Bildirici`); bu ayrım
  kaynaktan doğrudan okunabiliyor ve sahtelemeyi mümkün kılan tam olarak bu — sahteyi
  mümkün kılan bilgiyi **kaynak** söylüyor.
- Aynı `StokAzalt` gövdesi gerçek (`kayitliBildirici`) ve sahte (`casusBildirici`)
  gerçekleştirmeyle koşturuldu; normal bir azaltmada ikisi de aynı kararı verdi.
- Casus nesnenin ölçtüğü şey yalnız çağrı sayısı; ne yaptığı değil, kaç kez çağrıldığı.
- Sınırlayıcı ölçüm: stoktan fazla azaltan bir çağrıda gerçek gerçekleştirme negatif
  kalanı reddetti, sahte gerçekleştirme aynı çağrıyı sessizce kabul etti — sahteleme
  testi bu varsayımı hiç sınamadı.
- Sahte bağımlılık taksonomisi Birim Testi ve Test Odaklı Geliştirme kursunda kuruldu;
  burada yeniden anlatılmadı, yalnız Go'nun arayüz düzeneği üzerinden ölçüldü.

#### Sonraki Adım

Bu derste bir arayüzün, bir bağımlılığı gerçek ve sahte gerçekleştirmeler arasında
değiştirilebilir kıldığı, ama sahte gerçekleştirmenin gerçeğin bütün varsayımlarını
taşımadığı ölçüldü. `Bildirici` tek yöntemli, dar bir arayüz olarak kaldı; `kayitliBildirici`
kendi denetim kuralını taşıdı, `casusBildirici` yalnız bir çağrı sayacı tuttu — ve iki
gerçekleştirme normal bir azaltmada aynı, aşırı bir azaltmada farklı karar verdi. Buraya
kadarki dört ders de tek tek geçti ya da kaldı, tek tek hata verdi ya da vermedi — ama
hiçbiri, bu kararların hangi **sayısının** koşumdan koşuma değiştiğini sormadı. Sıradaki
ders bu soruyu soruyor: aynı işin iki yazımı karşılaştırıldığında, hangi sayı beş ayrı
koşumda hep aynı kalıyor, hangisi kalmıyor — ve konunun bağlayıcı sayıları da orada
kuruluyor.

### Karşılaştırmalı Ölçüm

Source: https://academia.sh/tr/kurslar/go-uretim/karsilastirmali-olcum

On bir bilgi bir programda toplanıyor ve üç söyleyene bölünüyor — 2'si kaynakta yazılı, 5'i araçtan koşum olmadan geliyor, 4'ü yalnız koşarak öğreniliyor. Karşılaştırmalı ölçüm aracı bu dört koşum bilgisinden birini üretiyor: aynı işin iki yazımının ayırma sayısı, beş koşumda hep aynı değeri veriyor; sınırlayıcı ölçüm bu sayının da neyi saydığını söylemeden karşılaştırılamayacağını gösteriyor.

Önceki dört ders, bir programa dair tek tek sorular sordu: bir test geçti mi, bir vaka
kaç alt teste döndü, bir istek hangi durum kodunu tetikledi, bir bağımlılığın gerçeği ile
sahtesi aynı kararı verdi mi. Her soru kendi kaynak/araç/koşum sınıfını taşıyordu, ama
hiçbiri bu sınıfların **toplamda** nasıl dağıldığını sormadı. Bu ders bir adım geri
çekiliyor ve aynı soruyu topluca soruyor: bir program hakkında bilinebilecek on bir ayrı
şeyi bir arada toplarsak, kaçı kaynakta yazılı, kaçını bir araç koşum olmadan söylüyor,
kaçı yalnız koşarak ortaya çıkıyor?

Bu derste ayrıca kursun adının işaret ettiği ölçüm — karşılaştırmalı ölçüm — ilk kez
ortaya çıkıyor. Bu ölçüm türü kendi başına iki sayı üretiyor: bir işin ne kadar bellek
ayırdığı ve ne kadar sürdüğü. Bu kursta yalnız birincisi kullanılıyor; ikincisinin neden
dışarıda bırakıldığı bu dersin ikinci yarısının konusu.

#### On Bir Bilgi, Üç Söyleyen

Aşağıdaki ölçüm küçük bir kaynağı — bir yapı, iki işlev, üç vakalık bir test tablosu, bir
derleme etiketiyle korunan fazladan bir dosya — geçici bir modüle yazıyor, sonra bu tek
kaynak üzerinde on bir ayrı soru soruyor. Her soru `gozle` yordamına, sorunun yanıtını
kimin verdiğiyle (kaynak, araç, koşum) birlikte veriliyor; `gozle` bu üç sınıfı sayıyor.
Bu, önceki dört derste ayrı ayrı sorulan soruların hepsini tek bir çatı altında topluyor;
her soru kendi başına daha önce görülmüş bir düzenekle (kaynağı bir dizgi olarak okuyup
saymak, bir aracı program içinden çağırıp yalnız sonucunu okumak) yanıtlanıyor.

```go
// araclar.go — bu bilgiyi kim soyluyor cercevesinin ortak yordamlari ve iki karsilastirma yazimi
package main

import (
	"fmt"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	"testing"
)

var kaynak, arac, kosum int

func gozle(bilgi, soyleyen, kanit string) {
	switch soyleyen {
	case "kaynak":
		kaynak++
	case "arac":
		arac++
	default:
		kosum++
	}
	fmt.Printf("%-34s %-7s %s\n", bilgi, soyleyen, kanit)
}

func paket(dosyalar map[string]string) string {
	dizin, _ := os.MkdirTemp("", "olcum")
	os.WriteFile(filepath.Join(dizin, "go.mod"), []byte("module olcum\n\ngo 1.24\n"), 0o644)
	for ad, icerik := range dosyalar {
		os.WriteFile(filepath.Join(dizin, ad), []byte(icerik), 0o644)
	}
	return dizin
}

func aracCalistir(dizin string, ortam []string, arg ...string) (string, bool) {
	cmd := exec.Command("go", arg...)
	cmd.Dir = dizin
	cmd.Env = append(append(os.Environ(), "GOTOOLCHAIN=local"), ortam...)
	cikti, err := cmd.CombinedOutput()
	return string(cikti), err == nil
}

func kacanSayisi(cikti string) int { return strings.Count(cikti, "escapes to heap") }

func kapsamOrani(cikti string) string {
	for _, s := range strings.Split(cikti, "\n") {
		if i := strings.Index(s, "coverage: "); i >= 0 {
			return strings.TrimSpace(s[i+len("coverage: "):])
		}
	}
	return "yok"
}

// dilimBuyut onceden yer ayirmadan sekiz kez ekliyor.
func dilimBuyut(b *testing.B) {
	for i := 0; i < b.N; i++ {
		var d []int
		for j := 0; j < 8; j++ {
			d = append(d, j)
		}
		_ = d
	}
}

// dilimOnceden ayni isi, sekiz elemanlik yeri once ayirarak yapiyor.
func dilimOnceden(b *testing.B) {
	for i := 0; i < b.N; i++ {
		d := make([]int, 0, 8)
		for j := 0; j < 8; j++ {
			d = append(d, j)
		}
		_ = d
	}
}

// besKosumda ayni karsilastirmali olcumu bes kez kosturur; ilk deger ve kac ayri
// deger gorulduguyle dondurur.
func besKosumda(k func(*testing.B)) (ilk int64, ayriDeger int) {
	gorulen := map[int64]bool{}
	for i := 0; i < 5; i++ {
		r := testing.Benchmark(k)
		gorulen[r.AllocsPerOp()] = true
		if i == 0 {
			ilk = r.AllocsPerOp()
		}
	}
	return ilk, len(gorulen)
}
```

**TS13.** Sorulan on bir sorunun ikisi kaynaktan yanıtlanıyor: test tablosundaki vaka
sayısı ve modülün dil sürümü — ikisi de metnin kendisinde yazılı, hiçbir şey
çalıştırmadan okunabiliyor.

```go
// olcum.go — on bir bilgi, uc soyleyene bolunuyor
package main

import (
	"fmt"
	"os"
	"strings"
)

const kaynakMetni = `package main

import "fmt"

type Kayit struct{ Ad string }

func yerelKalan() int {
	k := Kayit{Ad: "vida"}
	return len(k.Ad)
}

func kacan() *Kayit {
	k := Kayit{Ad: "somun"}
	return &k
}

var surum = "bilinmiyor"

func main() {
	fmt.Println(yerelKalan(), kacan().Ad, surum)
}
`

const testMetni = `package main

import "testing"

var vakalar = []struct {
	ad      string
	girdi   string
	uzunluk int
}{
	{"kisa", "ab", 2},
	{"orta", "abcd", 4},
	{"bos", "", 0},
}

func TestUzunluk(t *testing.T) {
	for _, v := range vakalar {
		if len(v.girdi) != v.uzunluk {
			t.Errorf("%s: %d beklendi", v.ad, v.uzunluk)
		}
	}
	if yerelKalan() != 4 {
		t.Error("yerelKalan")
	}
	if kacan().Ad != "somun" {
		t.Error("kacan")
	}
}
`

const etiketliMetin = `//go:build ek

package main

func ek() int { return 1 }
`

func main() {
	fmt.Println("-- bu bilgiyi kim soyluyor --")

	dizin := paket(map[string]string{"ana.go": kaynakMetni, "ana_test.go": testMetni, "ek.go": etiketliMetin})
	defer os.RemoveAll(dizin)

	gozle("tablo vaka sayisi", "kaynak", fmt.Sprint(strings.Count(testMetni, "\t{\"")))
	gozle("modul dil surumu", "kaynak", "go.mod satirinda yazili")

	kacisCikti, _ := aracCalistir(dizin, nil, "build", "-gcflags=-m", "-o", os.DevNull, ".")
	gozle("obege kacan deger sayisi", "arac", fmt.Sprint(kacanSayisi(kacisCikti)))

	_, vetTemiz := aracCalistir(dizin, nil, "vet", "./...")
	gozle("statik cozumleme bulgusu", "arac", fmt.Sprint("temiz=", vetTemiz))

	_, caprazOldu := aracCalistir(dizin, []string{"GOOS=linux", "GOARCH=arm64"}, "build", "-o", os.DevNull, ".")
	gozle("baska platforma derlenebilirlik", "arac", fmt.Sprint("derlendi=", caprazOldu))

	etiketsiz, _ := aracCalistir(dizin, nil, "list", "-f", "{{len .GoFiles}}", ".")
	etiketli, _ := aracCalistir(dizin, nil, "list", "-tags", "ek", "-f", "{{len .GoFiles}}", ".")
	gozle("etiketle degisen dosya sayisi", "arac",
		fmt.Sprint("etiketsiz=", strings.TrimSpace(etiketsiz), " etiketli=", strings.TrimSpace(etiketli)))

	_, testGecti := aracCalistir(dizin, nil, "test", "./...")
	gozle("testin verdigi karar", "kosum", fmt.Sprint("gecti=", testGecti))

	kapsamCikti, _ := aracCalistir(dizin, nil, "test", "-cover", "./...")
	gozle("kapsanan satir orani", "kosum", kapsamOrani(kapsamCikti))

	bagliCikti, _ := aracCalistir(dizin, nil, "run", "-ldflags=-X main.surum=1", ".")
	alanlar := strings.Fields(strings.TrimSpace(bagliCikti))
	gozle("ikiliye gomulen surum bilgisi", "arac", alanlar[len(alanlar)-1])

	ilkAyirma, ayriDeger := besKosumda(dilimBuyut)
	gozle("ayirma sayisi", "kosum", fmt.Sprint(ilkAyirma, " bes kosumda ayri deger=", ayriDeger))

	suzgecCikti, _ := aracCalistir(dizin, nil, "test", "-run", "TestUzunluk", "-v", "./...")
	gozle("suzgecin sectigi test sayisi", "kosum", fmt.Sprint(strings.Count(suzgecCikti, "--- PASS")))

	fmt.Printf("bilgi: %d kaynak, %d arac, %d kosum\n", kaynak, arac, kosum)
}
```

```
-- bu bilgiyi kim soyluyor --
tablo vaka sayisi                  kaynak  3
modul dil surumu                   kaynak  go.mod satirinda yazili
obege kacan deger sayisi           arac    3
statik cozumleme bulgusu           arac    temiz=true
baska platforma derlenebilirlik    arac    derlendi=true
etiketle degisen dosya sayisi      arac    etiketsiz=1 etiketli=2
testin verdigi karar               kosum   gecti=true
kapsanan satir orani               kosum   80.0% of statements
ikiliye gomulen surum bilgisi      arac    1
ayirma sayisi                      kosum   4 bes kosumda ayri deger=1
suzgecin sectigi test sayisi       kosum   1
bilgi: 2 kaynak, 5 arac, 4 kosum
```

**TS14.** Beş bilgi bir araçtan geliyor: kaçan değer sayısı, statik çözümlemenin bulgusu,
başka platforma derlenebilirlik, etiketle değişen dosya sayısı, ikiliye gömülen sürüm
bilgisi. Bu beşinin ortak özelliği, **koşum gerektirmemeleri** — derleyici, inceleyici,
çözümleyici ve bağlayıcı her çağrıldığında aynı kaynağa aynı yanıtı veriyor.

**TS15.** Geri kalan dört bilgi yalnız koşarak öğreniliyor: testin verdiği karar, kapsanan
satır oranı, ayırma sayısı, süzgecin seçtiği test sayısı. On bir bilginin toplamı bu
derste ilk kez tek bir koşumda birikiyor: **2 kaynak, 5 araç, 4 koşum**. Bu, konunun
bağlayıcı dağılımı — bu sayı bundan sonraki her derste tek bir cümleyle anılıyor, yeniden
üretilmiyor.

Bu on bir satırın her biri, önceki dört dersin sorduğu sorularla aynı biçimde soruluyor
— yalnız bu kez hepsi tek bir koşumda, tek bir kaynağa karşı. `tablo vaka sayisi` satırı
Tablo Güdümlü Testler'in sorusunu, `testin verdigi karar` satırı Test Paketi
Temelleri'nin sorusunu aynen tekrarlıyor; aradaki fark, burada bu soruların hepsinin
aynı koşumda toplanıp bir dağılıma dönüştürülmesi. On bir bilginin **dokuzu** (araç ve
koşum toplamı) koşum ya da en az bir araç çağrısı gerektiriyor; kaynağı tek başına
okumak bu on birden yalnız ikisini veriyor — programın kendisi hakkında bilinebileceklerin
büyük çoğunluğu, metnin dışında duruyor.

#### Karşılaştırmalı Ölçümün İki Sayısından Yalnız Biri Tekrarlanabilir

Yukarıdaki on bir satırın onuncusu — ayırma sayısı — bu dersin asıl konusu ve geri kalan
onundan farklı bir biçimde ölçülüyor: `testing.Benchmark` aynı işi beş kez koşturuyor,
`AllocsPerOp` her koşumdan bir sayı döndürüyor ve bu beş sayının kaç **ayrı** değere
düştüğü ayrıca sayılıyor. Yukarıdaki koşumda bu sayı 1 — beş koşum, tek bir değer.

`dilimBuyut` yukarıdaki tam koşumda zaten bir kez kullanılmıştı — "ayirma sayisi"
satırının kanıtı oydu. Aşağıdaki ölçüm onu yeniden çağırıyor, ama bu kez tek başına değil,
karşılaştıracağı ikinci bir yazımla birlikte.

**TS16.** Aşağıdaki ölçüm aynı işin iki yazımını karşılaştırıyor: `dilimBuyut` sekiz
elemanı önceden yer ayırmadan ekliyor, `dilimOnceden` aynı sekiz elemanı önceden ayrılmış
bir yere ekliyor. İkisi de beşer kez koşturuluyor.

```go
// karsilastirma.go — ayni isin iki yazimi, ayirma sayisiyla karsilastiriliyor
package main

import "fmt"

func main() {
	fmt.Println("-- karsilastirmali olcum: ayirma sayisi ile iki yazim --")

	onceliksizIlk, onceliksizAyri := besKosumda(dilimBuyut)
	fmt.Println("onceden yer ayirmadan, is basina ayirma:", onceliksizIlk,
		"bes kosumda ayri deger:", onceliksizAyri)

	oncedenIlk, oncedenAyri := besKosumda(dilimOnceden)
	fmt.Println("onceden yer ayirarak, is basina ayirma:", oncedenIlk,
		"bes kosumda ayri deger:", oncedenAyri)
}
```

```
-- karsilastirmali olcum: ayirma sayisi ile iki yazim --
onceden yer ayirmadan, is basina ayirma: 4 bes kosumda ayri deger: 1
onceden yer ayirarak, is basina ayirma: 0 bes kosumda ayri deger: 1
```

Ölçüm aracının kendisi burada, tıpkı önceki koşumda olduğu gibi, iki ayrı sayı döndürüyor — iş
başına ayırma sayısı ve geçen süre. Bu ders **süreyi hiçbir yerde basmıyor, ölçmüyor,
karşılaştırmıyor**: `testing.Benchmark`'ın döndürdüğü değerden yalnız `AllocsPerOp` okunuyor;
süreyi taşıyan alan hiç kullanılmıyor. Bunun nedeni bir tercih değil, bir gözlem: süre aynı
makinede, aynı kaynakla, art arda beş kez ölçülse bile koşumdan koşuma değişiyor — işletim
sisteminin o an başka neyi çalıştırdığına, önbelleğin o anki durumuna, hatta ölçümün kendisinin
makineyi ne kadar ısıttığına bağlı. Ayırma sayısı böyle değil: `dilimBuyut` her koşumda tam olarak
dört kez bellek ayırıyor, çünkü dilimin alt dizisi sekiz elemana ulaşana kadar aynı büyüme
kararlarından geçiyor ve bu karar zinciri koşumdan koşuma değişmiyor.

`dilimOnceden` ise sıfır kez ayırıyor — sekiz elemanlık yer `make([]int, 0, 8)` ile bir
kerede ayrılıyor ve derleyicinin kaçış çözümlemesi (bu ayrımın kendisi konunun sonraki
konusunda ölçülüyor) bu diziyi hiç öbeğe taşımadan yığıtta tutabiliyor. Dört ile sıfır
arasındaki fark, önceden yer ayırmanın somut karşılığı: aynı sekiz eleman, aynı sırayla
ekleniyor, ama biri dilimin kendi büyüme mekanizmasına (kapasitesi doldukça iki katına
çıkarma) bırakılırken öbürü baştan yeteri kadar yer istiyor.

#### Sınırlayıcı Ölçüm: Ayırma Sayısı da Neyi Saydığını Söylemeden Karşılaştırılamaz

**TS17.** İki yazımın ayırma sayısı — 4 ve 0 — kendi başına "biri daha iyi" demiyor;
hangi sayının **neyi** saydığı söylenmeden bu iki değer karşılaştırılamıyor.
`AllocsPerOp`'un döndürdüğü sayı **iş başına** ayırma: `testing.Benchmark`, `b.N`'i
kendi belirliyor ve iç döngüyü o kadar kez koşturup toplam ayırmayı `b.N`'e bölerek bu
sayıyı üretiyor. Toplam ayırma sayısı — bütün koşum boyunca kaç kez bellek ayrıldığı —
ayrı bir sayı ve `b.N` büyüdükçe büyüyor; iş başına sayı sabit kalırken toplam sayı
hiç sabit kalmıyor.

Bu ayrım gözden kaçtığında yanıltıcı bir karşılaştırma doğuyor: "birinci yazım ikinci
yazımdan dört kat fazla bellek ayırıyor" cümlesi doğru, ama "birinci yazım toplamda dört
kat fazla bellek ayırdı" cümlesi başka bir iddia — ikinci cümle, iki koşumun `b.N`
değerinin aynı olduğunu varsayıyor ve bu varsayım hiçbir yerde denetlenmedi. Bu dersin
ölçtüğü sayı hep iş başına ayırma; toplam ayırma bu derste hiç hesaplanmadı, çünkü
konunun sorusu "hangi yazım her bir işlemde daha az bellek ayırıyor" — "bu koşum toplamda
ne kadar bellek ayırdı" değil. Bir karşılaştırmalı ölçüm sonucu paylaşılırken bu iki
sayının hangisinin kastedildiği söylenmezse, aynı iki sayı (4 ve 0) tamamen farklı bir
iddiaya kanıt gösterilebilir.

`besKosumda` yordamının kendisi de bu ayrımı örtük olarak taşıyor: döndürdüğü `ilk`
değeri her koşumun `AllocsPerOp`'u — yani zaten iş başına bir sayı, `testing.Benchmark`
tarafından `b.N`'e bölünmüş hâliyle. Bu yordam toplam ayırma sayısını hiç görmüyor,
çünkü `testing.Benchmark`'ın kendisi bu sayıyı dışarı vermiyor; yalnız iş başına ayırma
ve iş başına geçen süre döndürüyor. Bu, ölçüm aracının kendi tasarımının bir sonucu —
karşılaştırmalı ölçüm araçları genellikle iş başına sayıyı tercih ediyor, çünkü farklı
`b.N` değerleriyle koşan iki ölçümü karşılaştırılabilir kılan tek şey bu normalize
edilmiş biçim.

#### Özet

- On bir bilgi bir koşumda toplandı ve üç sınıfa ayrıldı: **2 kaynak, 5 araç, 4 koşum** —
  bu dağılım konunun bağlayıcı sayısı, bundan sonraki derslerde yeniden üretilmiyor.
- Beş araç bilgisi koşum gerektirmiyor, aynı kaynağa her çağrıda aynı yanıtı veriyor; dört
  koşum bilgisi yalnız çalıştırılarak öğreniliyor.
- Karşılaştırmalı ölçüm aracı iki sayı döndürüyor, ama bu ders yalnız birini —
  iş başına ayırma sayısını — okudu; süre hiçbir yerde basılmadı, hiçbir yerde
  karşılaştırılmadı.
- Ayırma sayısı beş koşumda hep aynı değere düştü (1 ayrı değer); bu, sayının
  tekrarlanabilir olduğunun kanıtı.
- Sınırlayıcı ölçüm: iş başına ayırma ile toplam ayırma ayrı sayılar; hangisinin
  kastedildiği söylenmeden bir karşılaştırma okunamaz.

#### Sonraki Adım

Bu derste bir programa dair on bir bilginin üç sınıfa nasıl ayrıldığı ve karşılaştırmalı
ölçümün iki sayısından yalnız birinin tekrarlanabilir olduğu ölçüldü. Beş araç bilgisi
ve dört koşum bilgisi bu dersin verdiği örnekte kaldı; her ikisi de bundan sonraki
derslerde kendi köşesinde yeniden karşımıza çıkıyor, ama on bir satırlık tam tablo bir
daha basılmıyor. Bu on bir bilginin biri — kapsanan satır oranı — bu derste yalnız tek
bir sayı olarak geçti: `80.0% of statements`. Sıradaki ders bu sayının tam olarak neyi
saydığını, hangi satırların kapsama dışında kaldığını ve üç ayrı test kümesinin bu oranı
nasıl değiştirdiğini ölçüyor — konunun kapanış dersi.

### Kapsam Raporlama

Source: https://academia.sh/tr/kurslar/go-uretim/kapsam-raporlama

Aynı işlev üç ayrı test kümesiyle sınandığında kapsama oranı 57.1%, 85.7% ve 100.0% olarak üç ayrı değer alıyor; kapsanmayan blok sayısı da sırasıyla 3, 1 ve 0'a düşüyor. Sınırlayıcı ölçüm, %100 kapsama veren üçüncü kümenin, dönüş değerini hiç denetlemediği bir dalda gerçek bir hatayı hiç yakalamadığını hatalı bir kaynakla koşturarak gösteriyor.

Önceki ders bir programa dair on bir bilgiyi kaynak, araç ve koşum sınıflarına ayırdı;
bu on birin biri — kapsanan satır oranı — o derste yalnız tek bir sayı
(`80.0% of statements`) olarak geçti, ayrıntısına girilmedi. Bu son ders konunun kapanışı ve o
ayrıntıyı açıyor: kapsama oranı tam olarak neyi sayıyor ve bu sayı yükseldiğinde
gerçekte ne değişiyor?

Kapsama oranı üretim kod tabanlarında sık kullanılan bir gösterge: bir gözden geçirme,
bir eşik ("yeni kod en az şu oranda kapsanmalı") ya da bir panoda görünen tek bir yüzde.
Bu göstergenin ne söylediği ve ne söylemediği, bu konunun beş dersi boyunca kurulan
"bu bilgiyi kim söylüyor" sorusunun son sınavı — çünkü kapsama, tek başına
bakıldığında, bir programın sınandığını değil, yalnız çalıştırıldığını kanıtlıyor. Bu
soruyu yanıtlamak için konu boyunca kullanılan `envanter` paketine son bir kez dönülüyor;
bu kez paketin kendisi değil, aynı paketi sınayan üç farklı test kümesi konu.

#### Aynı İşlev, Üç Test Kümesi, Üç Oran

`envanter` paketine son bir işlev ekleniyor: `Etiketle`, bir kalemin adedine bakıp dört
durumdan birini döndürüyor — negatif bir adet `"gecersiz"`, sıfır `"tukendi"`, üçün altı
`"az kaldi"`, geri kalanı `"yeterli"`. Dört durumun her biri kendi `return` satırında
ve dört durum birbirini dışlıyor: bir çağrı yalnız birine düşüyor.

Bu işlev bilinçli olarak dallanma sayısı ayarlanabilir bir örnek olarak kuruldu — dört
ayrı dal, dört ayrı test vakası ve her vakanın kapsamaya kaç ayrı blok eklediği tek tek
izlenebilir. `Gecerli` (Tablo Güdümlü Testler) ve `StokAzalt` (Sahteleme ve Arayüz
Ayrımı) da benzer dallı yapılar taşıyordu; bu ders aynı yapıyı, kapsama aracının bu
dallanmayı nasıl saydığını görmek için yeniden kullanıyor.

Üç test kümesi de aynı `envanterMetni` kaynağına karşı koşuyor — `Etiketle`'nin kendisi
hiç değişmiyor, değişen yalnız hangi durumların çağrıldığı. Bu, kapsama oranının bir
kaynağın özelliği değil, bir **kaynak-test çifti**nin özelliği olduğunu gösteriyor: aynı
kaynağın kapsama oranı, hangi test kümesiyle sorulduğuna göre 57.1% ile 100.0% arasında
herhangi bir değer alabiliyor.

`Kur` ve `Cagir` yordamları konunun ilk dersinden beri değişmedi; bu ders de her test
kümesi için tamamen boş bir geçici modül kuruyor, önceki bir koşumun izini taşımadan.

**TS18.** Aynı `Etiketle` işlevi, üç farklı test kümesiyle sınanıyor: `T1` yalnız
"yeterli" durumunu sınıyor, `T2` üç durumu ("yeterli", "tukendi", "az kaldi") sınıyor,
`T3` dördünü de çağırıyor.

```go
// araclar.go — kurulum, cagri ve kapsama okuma yordamlari, ve konu boyunca kullanilan Etiketle
package main

import (
	"os"
	"os/exec"
	"path/filepath"
	"strings"
)

func Kur(dosyalar map[string]string) string {
	dizin, _ := os.MkdirTemp("", "sinama")
	os.WriteFile(filepath.Join(dizin, "go.mod"), []byte("module sinama\n\ngo 1.24\n"), 0o644)
	for ad, icerik := range dosyalar {
		os.WriteFile(filepath.Join(dizin, ad), []byte(icerik), 0o644)
	}
	return dizin
}

func Cagir(dizin string, ekOrtam []string, arg ...string) (string, bool) {
	cmd := exec.Command("go", arg...)
	cmd.Dir = dizin
	cmd.Env = append(append(os.Environ(), "GOTOOLCHAIN=local"), ekOrtam...)
	cikti, err := cmd.CombinedOutput()
	return string(cikti), err == nil
}

func kapsamOrani(cikti string) string {
	for _, s := range strings.Split(cikti, "\n") {
		if i := strings.Index(s, "coverage: "); i >= 0 {
			return strings.TrimSpace(s[i+len("coverage: "):])
		}
	}
	return "yok"
}

// kapsanmayanBlok, kapsama profilindeki hic kosmamis blok sayisini sayar.
func kapsanmayanBlok(profil []byte) int {
	n := 0
	for _, satir := range strings.Split(string(profil), "\n")[1:] {
		alanlar := strings.Fields(satir)
		if len(alanlar) == 3 && alanlar[2] == "0" {
			n++
		}
	}
	return n
}

const envanterMetni = `package sinama

type Kalem struct {
	Ad    string
	Adet  int
	Fiyat int
}

func Etiketle(k Kalem) string {
	if k.Adet < 0 {
		return "gecersiz"
	}
	if k.Adet == 0 {
		return "tukendi"
	}
	if k.Adet < 3 {
		return "az kaldi"
	}
	return "yeterli"
}
`

const testMetniT3 = `package sinama

import "testing"

func TestEtiketle(t *testing.T) {
	if got := Etiketle(Kalem{"kalem", 5, 250}); got != "yeterli" {
		t.Errorf("got %s", got)
	}
	if got := Etiketle(Kalem{"kalem", 0, 250}); got != "tukendi" {
		t.Errorf("got %s", got)
	}
	if got := Etiketle(Kalem{"kalem", 2, 250}); got != "az kaldi" {
		t.Errorf("got %s", got)
	}
	Etiketle(Kalem{"kalem", -1, 250})
}
`
```

```go
// olcum.go — uc test kumesiyle uc ayri kapsama orani ve kapsanmayan blok sayisi
package main

import (
	"fmt"
	"os"
	"path/filepath"
)

const testMetniT1 = `package sinama

import "testing"

func TestEtiketleYeterli(t *testing.T) {
	if got := Etiketle(Kalem{"kalem", 5, 250}); got != "yeterli" {
		t.Errorf("got %s", got)
	}
}
`

const testMetniT2 = `package sinama

import "testing"

func TestEtiketle(t *testing.T) {
	if got := Etiketle(Kalem{"kalem", 5, 250}); got != "yeterli" {
		t.Errorf("got %s", got)
	}
	if got := Etiketle(Kalem{"kalem", 0, 250}); got != "tukendi" {
		t.Errorf("got %s", got)
	}
	if got := Etiketle(Kalem{"kalem", 2, 250}); got != "az kaldi" {
		t.Errorf("got %s", got)
	}
}
`

func main() {
	fmt.Println("-- kosum: uc test kumesiyle uc ayri oran --")
	kumeler := []struct{ ad, metin string }{
		{"T1", testMetniT1},
		{"T2", testMetniT2},
		{"T3", testMetniT3},
	}
	for _, k := range kumeler {
		dizin := Kur(map[string]string{"envanter.go": envanterMetni, "envanter_test.go": k.metin})
		cikti, _ := Cagir(dizin, nil, "test", "-coverprofile=kapsam.out", "./...")
		profil, _ := os.ReadFile(filepath.Join(dizin, "kapsam.out"))
		fmt.Println(k.ad, "kapsama orani:", kapsamOrani(cikti),
			"kapsanmayan blok sayisi:", kapsanmayanBlok(profil))
	}
}
```

```
-- kosum: uc test kumesiyle uc ayri oran --
T1 kapsama orani: 57.1% of statements kapsanmayan blok sayisi: 3
T2 kapsama orani: 85.7% of statements kapsanmayan blok sayisi: 1
T3 kapsama orani: 100.0% of statements kapsanmayan blok sayisi: 0
```

**TS19.** Vaka sayısı önceki iki derste de kaynaktan sayılabiliyordu; burada da sayılabilir
— `T1` bir çağrı, `T2` üç, `T3` dört. Ama kapsama oranının kendisi kaynaktan hiç
okunamıyor: aynı `Etiketle` işlevi değişmeden kalırken, yalnız hangi test kümesinin onu
çağırdığı değiştiğinde oran 57.1%'den 100.0%'e çıkıyor. `-coverprofile` bayrağıyla
üretilen profil dosyası bu oranın hangi **bloklardan** geldiğini de veriyor:
`kapsanmayanBlok`, profildeki her satırı okuyup son sütunu sıfır olanları sayıyor — bu
sütun, o bloğun koşum boyunca kaç kez çalıştığı. T1'de üç blok hiç çalışmıyor (üç
`return` satırı), T3'te hiçbiri.

Kapsama aracının kendisi bir koşum bilgisi üretiyor, çünkü hangi bloğun çalışıp
çalışmadığı ancak testler fiilen koşturulduktan sonra biliniyor — kaynağı okuyan biri
dört durumun var olduğunu görebilir, ama hangi test kümesinin bu dört durumdan kaçını
tetiklediğini kaynaktan çıkaramaz. Bu, Tablo Güdümlü Testler'de ölçülen ayrımın bu konudaki
son görünümü: bir sayı (vaka sayısı) kaynakta duruyor, bir başka sayı (kapsama oranı)
yalnız koşarak ortaya çıkıyor.

Üç kümenin de aynı yordamla — `Kur`, `Cagir` — koşturulduğuna dikkat etmek gerekiyor;
değişen yalnız `envanter_test.go` dosyasının içeriği. `Cagir` bu kez `-coverprofile`
bayrağıyla çağrılıyor ve bu bayrak `go test`'e yalnız bir oran değil, oranın dayandığı
ham veriyi de bir dosyaya yazmasını söylüyor. `-cover` bayrağı yalnız özet oranı
veriyordu (önceki derste kullanılan biçim); `-coverprofile` aynı bilgiyi blok blok
kaydediyor ve bu ders bu ayrıntıyı `kapsanmayanBlok` yordamıyla okuyor. Profil dosyasının
kendisi de bu programın çalışma dizininde geçici olarak duruyor ve hiçbir yerde yolu
basılmıyor — yalnız içeriğinden türetilen sayı okunuyor.

#### Sınırlayıcı Ölçüm: Yüzde Yüz Kapsama Doğruluk Göstermiyor

`T3` dört durumun dördünü de çağırıyor ve son çağrısı —
`Etiketle(Kalem{"kalem", -1, 250})` — dönüş değerini hiç almıyor, hiç denetlemiyor. Bu satır kapsama aracının gözünde
tam bir sınama: `Etiketle` çağrıldı, ilgili blok çalıştı, oran %100'e çıktı. Ama bu
çağrının **doğru bir sonuç verip vermediği** hiçbir yerde sınanmadı.

Bunun bir yazım hatası olmadığını vurgulamak gerekiyor — `T3`'ün kaynağına bakan biri
dördüncü çağrıyı görüyor, `Etiketle`'nin dördüncü durumunun **çağrıldığını** doğruluyor.
Eksik olan, bir `if got := ...; got != "beklenen"` kalıbının o çağrıya da eklenmemiş
olması; üçüncü göze kadar bu kalıp hep vardı, dördüncüde düşürüldü. Kapsama aracı bu
düşüşü fark etmiyor çünkü onun sorduğu soru "bu satır çalıştı mı", "bu satırın sonucu bir
iddiaya bağlandı mı" değil.

Bu, Sahteleme ve Arayüz Ayrımı'nda ölçülen sınırla da bağlantılı: orada bir sahtenin
gerçeğin bütün varsayımlarını taşımadığı gösterilmişti, burada bir test kümesinin
kendisinin de kendi iddialarının bütün dalları kapsamadığı gösteriliyor — ikisi de aynı
kökten: bir sınamanın "geçmesi", sınamanın kapsamı dışında kalan hiçbir şey hakkında bir
güvence taşımıyor.

**TS20.** Aşağıdaki ölçüm bunu somutlaştırıyor: `Etiketle`'nin hatalı bir sürümü — negatif
adedi de `"tukendi"` diye etiketleyen, `"gecersiz"` durumunu hiç üretmeyen bir sürüm — aynı
`T3` test kümesiyle koşturuluyor.

```go
// sinir.go — T3 tam kapsamayla, hatali bir kaynakta ne diyor
package main

import (
	"fmt"
	"strings"
)

const envanterBozukMetni = `package sinama

type Kalem struct {
	Ad    string
	Adet  int
	Fiyat int
}

func Etiketle(k Kalem) string {
	if k.Adet < 0 {
		return "tukendi"
	}
	if k.Adet == 0 {
		return "tukendi"
	}
	if k.Adet < 3 {
		return "az kaldi"
	}
	return "yeterli"
}
`

func main() {
	fmt.Println("-- sinirlayici olcum: T3 tam kapsamayla, hatali kaynakta ne diyor --")
	dizin := Kur(map[string]string{"envanter.go": envanterBozukMetni, "envanter_test.go": testMetniT3})
	cikti, _ := Cagir(dizin, nil, "test", "-cover", "-v", "./...")
	gecti := strings.Contains(cikti, "--- PASS: TestEtiketle")
	fmt.Println("test gecti mi:", gecti, "kapsama orani:", kapsamOrani(cikti))
}
```

```
-- sinirlayici olcum: T3 tam kapsamayla, hatali kaynakta ne diyor --
test gecti mi: true kapsama orani: 100.0% of statements
```

**TS21.** Hatalı sürümde negatif bir adet artık `"gecersiz"` değil `"tukendi"`
döndürüyor — gerçek bir hata, çünkü bu iki durum `envanter` paketinin geri kalanında
farklı anlamlar taşıyabilir (stoğu biten bir kalemle stok sayımı bozulmuş bir kalem aynı
şey değil). Ama `T3` bu hatayı hiç yakalamıyor: test hâlâ geçiyor, kapsama oranı hâlâ
`100.0%`. Nedeni basit — `T3`'ün son satırı `Etiketle`'yi çağırıyor ama döndürdüğü
değerle hiçbir şey yapmıyor; kapsama aracı bu satırın **çalıştığını** görüyor, çalışan
satırın **doğru bir değer ürettiğini** hiç sormuyor.

İki koşumun (doğru kaynak, hatalı kaynak) kapsama oranı da bayt bayt aynı: ikisinde de
`100.0% of statements`. Kapsama aracı iki kaynak arasındaki farkı hiç görmüyor, çünkü
fark bir satırın çalışıp çalışmadığında değil, çalışan bir satırın hangi değeri
ürettiğinde. Bu, konunun her araç bilgisi için geçerli olan aynı sınırın kapsamaya
uygulanmış hâli: bir araç yalnız **kendi sorduğu soruyu** yanıtlıyor ve kapsama aracının
sorduğu soru hep aynı — bu satır çalıştı mı — hangi değeri ürettiği hiç sorulmuyor.

Bu, konunun altı dersi boyunca tekrar eden aynı sınırın son biçimi: Test Paketi
Temelleri'nde hiçbir şey sınamayan bir test geçiyordu, burada hiçbir şey sınamayan tek
bir **çağrı**, geri kalan üç iddiası sağlam bir test kümesinin kapsama oranını %100'e
taşıyor. Kapsama aracı **koşulan** blokları sayıyor, **doğru sınanan** blokları değil —
ve bu iki sayı, `T3` örneğinde olduğu gibi, aynı görünüp farklı bir şey söyleyebiliyor.
Yüzde yüz kapsamalı bir test kümesi, kaynağın her satırının en az bir kez çalıştığının
garantisi; kaynağın her satırının **doğru çalıştığının** garantisi değil.

Bunun üretim tarafındaki karşılığı somut: bir kod tabanında kapsama oranı %100'e
yaklaştıkça, ekleyen kişiler genellikle bu oranı bir tamamlanmışlık göstergesi olarak
okuyor. Yukarıdaki ölçüm bu okumanın eksik olduğunu gösteriyor — oran, satırların
çalıştığını söylüyor, o satırların **ürettiği değerlerin** doğrulandığını söylemiyor.
Bir kod tabanı yüzde yüz kapsamayla da hatalı davranabiliyor, çünkü kapsama aracının
kendisi bir iddianın var olup olmadığını hiç sormuyor — yalnız bir satırın çalışıp
çalışmadığını sayıyor. Bu, Test Paketi Temelleri'nde kurulan "geçen bir test doğru
olduğunu söylemez" iddiasının, kapsama oranına taşınmış hâli: yüksek bir kapsama oranı da
doğruluğu söylemiyor, yalnız neyin çalıştırıldığını söylüyor.

Bu son ölçüm, kapsamanın kendisinin de bir gösterge olduğunu, hedef olmadığını
gösteriyor: %100'e ulaşmak kolay, ulaşırken hangi soruların hâlâ yanıtsız kaldığını
görmek zor. Kapsama aracı bu zorluğu çözmüyor, yalnız hangi satırların hiç
çalıştırılmadığını gösteriyor — geri kalanı, bir iddianın gerçekten o satırın ürettiği
değeri denetleyip denetlemediği, yazan kişinin elinde kalıyor.

#### Özet

- Aynı `Etiketle` işlevi üç test kümesiyle sınandığında kapsama oranı üç farklı değer
  aldı (57.1%, 85.7%, 100.0%); vaka sayısı kaynaktan sayılabildi, kapsama oranı yalnız
  koşarak öğrenilebildi.
- Kapsama profili hangi bloğun hiç çalışmadığını da veriyor; kapsanmayan blok sayısı test
  kümesi genişledikçe 3'ten 0'a düştü.
- Sınırlayıcı ölçüm: dört durumu da çağıran ama son çağrısının sonucunu denetlemeyen bir
  test kümesi, hatalı bir kaynakla koşturulduğunda hâlâ geçti ve hâlâ %100 kapsama verdi.
- Kapsama aracı koşulan satırları sayıyor, doğru sınandığını söylemiyor — bu ayrım
  konunun altı dersinde tekrar eden aynı sınırın son görünümü.
- Sahte bağımlılık ailesi, tablo güdümlü test kalıbı ve kapsama ölçümü Birim Testi ve
  Test Odaklı Geliştirme kursunda kuruldu; bu ders yalnız Go'nun kendi kapsama aracını
  ölçtü.

#### Sonraki Adım

Bu konu boyunca aynı soru altı kez soruldu: bir testin kararı, bir vaka listesi, bir HTTP
işleyicisinin yanıtı, bir sahte bağımlılığın sınırı, karşılaştırmalı ölçümün hangi sayısı
tekrarlanabilir ve şimdi kapsama oranının neyi saydığı — hepsi kaynak, araç ya da koşum
etiketiyle sınıflandı ve altı dersin her biri kendi sınırlayıcı ölçümünü koşturarak
gösterdi: geçen bir test doğru olduğunu söylemiyor, vaka eklemek kapsamayı artırmıyor,
test yardımcısı ağı değil işleyiciyi sınıyor, sahte gerçekleştirme gerçeğin bütün
varsayımlarını taşımıyor, ayırma sayısı da neyi saydığını söylemeden karşılaştırılamıyor
ve şimdi kapsama koşulan satırları sayar, doğru sınandığını söylemez. Sıradaki konu aynı
soruyu belleğe soruyor: bir profil de tıpkı kapsama gibi koşan yolu görüyor ve nerede
bellek ayrıldığını söylüyor — ama neden yavaş olduğunu söylemiyor.

## Başarım ve Bellek

Profil verisinin hangi sayıyı taşıdığı, derleyicinin bir değeri yığıtta mı öbekte mi ayıracağına karar verirken bunu okunabilir biçimde bildirmesi, ayırma baskısının çöp toplama davranışına etkisi, ve yürütme izlerinin olay sayısıyla çözümlenmesi.

### Profilleme

Source: https://academia.sh/tr/kurslar/go-uretim/profilleme

Profil bir koşum bilgisidir ve bu derste sayıyla okunur: aynı işlevin iki çağrı biçiminde kaç değer ayrıldığı. Sınırlayıcı ölçüm: profil yalnız koşan yolu görüyor — hiç çalışmayan bir dalın ayırdığı değerler profilde hiç görünmüyor, kaynakta yazılı olsalar bile.

Önceki ders kapsama oranının koşulan satırları saydığını, doğru sınandığını söylemediğini
gösterdi. Bu ders aynı soruyu belleğe soruyor: bir program çalışırken nerede ayırma yaptığı
kaynağa bakılarak görülemiyor, ama koşum bittiğinde bir profil bunu **sayıyla** söylüyor.
Profil de kapsama gibi bir koşum bilgisi — kaynağı okuyan biri hangi işlevin kaç değer
ayırdığını göremez, yalnız çalıştırıp sorabilir.

Bu paylaşılan biçim tesadüfi değil: kursun açılışından beri her ders aynı iskeleti
kullanıyor — bir bilginin kaynakta mı, araçta mı, koşumda mı durduğunu önce sınıflandırmak,
sonra o sınıfa özgü sınırı ölçmek. Profilin buradaki yeri nettir: kapsama gibi, yalnız
koşarak öğreniliyor. Ama kapsamadan farklı olarak, profilin okuduğu şey bir doğru/yanlış
kararı değil bir **miktar** — bu farkın ölçüme nasıl yansıdığı bu dersin konusu.

Eşzamanlılık kursu (M09/K03) profil çıkarmayı ve bir ölçüm disiplinini zaten kurdu: bir ölçüm,
neyi saydığını söylemeden karşılaştırılamaz. Bu ders o disiplini Go'nun kendi araçlarıyla
sürdürüyor; kursun ekseni (örtüşen adım) burada kullanılmıyor. Buradaki soru dar: bir profil size
**nereyi** söylüyor, **neyi** söylemiyor?

#### Üç Profil Türü, Bir Sayı

Katalog bu konuyu üç profil türüyle tanımlıyor: işlemci profili, bellek profili ve
engelleme profili. Üçü de dilin kendi ölçüm paketiyle üretilebiliyor ama üçü aynı türde
bilgi taşımıyor. İşlemci profili, programın zamanını hangi işlevde geçirdiğini örnekleyerek
ölçüyor — bu ölçümün doğası gereği **süre**dir ve süre bu kursun hiçbir dersinde
yazılmıyor, basılmıyor, ölçülmüyor. Engelleme profili de benzer bir örnekleme yapıyor: bir
goroutine'in ne kadar süre bir kilit ya da kanal beklediğini ölçüyor, yine süre temelli.

Bellek profili farklı bir soruya yanıt veriyor: **kaç** değer ayrıldığı, **ne kadar süre**
geçtiği değil. Bu, bu kursun sayabildiği tek profil türü — sayısı bir koşumdan diğerine
sabit kalıyor, süre gibi makineye ve o anki yüke bağlı değil. Bu ders bu yüzden yalnız
bellek profiline ve onun da tek bir özetine — işlem başına ayırma sayısına — odaklanıyor.
İşlemci ve engelleme profilleri var olmaya devam ediyor; burada ölçülmüyorlar, çünkü
ölçtükleri şey süre.

#### Profil: Kaç Değer, Hangi Çağrıda

Bu derste "profil" sözcüğü tek bir sayıya indiriliyor: bir çağrının işlem başına kaç değer
ayırdığı. Bu sayı, dilin kendi ölçüm paketiyle **program içinden** okunuyor — bellek baytı,
süre ya da yığıt izi hiç basılmıyor.

```go
// profil.go — ayni islevin iki cagri bicimi, profilde kac deger ayirdigini gosteriyor
package main

import "fmt"

type Kayit struct {
	Ad     string
	Miktar int
}

// bicimle bir kaydi tek satira ceviriyor. ayrintili istendiginde ekstra bir
// alan dizisi olusturuyor; istenmediginde o govde hic calismiyor.
func bicimle(k Kayit, ayrintili bool) string {
	satir := fmt.Sprintf("%s:%d", k.Ad, k.Miktar)
	if ayrintili {
		alanlar := make([]string, 0, 3)
		alanlar = append(alanlar, fmt.Sprintf("ad=%s", k.Ad))
		alanlar = append(alanlar, fmt.Sprintf("miktar=%d", k.Miktar))
		alanlar = append(alanlar, fmt.Sprintf("uzunluk=%d", len(k.Ad)))
		for _, a := range alanlar {
			satir += " " + a
		}
	}
	return satir
}
```

**BS1.** `bicimle` tek bir işlev ama iki ayrı yol taşıyor: `ayrintili` yanlışken yalnız
ilk satır çalışıyor, doğruyken üç ayrı biçimlendirme ve bir birleştirme döngüsü ekleniyor.
Kaynağı okuyan biri iki yolun da var olduğunu görür ama hangisinin ne kadar ayırdığını
göremez — bu, koşturmadan bilinemeyen bir bilgi.

#### İki Çağrı Biçimini Ölçmek

`bicimle`nin kendisi hiçbir şey ölçmüyor — yalnız bir sonuç üretiyor. Ölçüm ayrı bir katmanda
duruyor: dilin kendi karşılaştırmalı ölçüm işlevi (`func(*testing.B)`) çağrıyı `b.N` kez
tekrarlıyor; ölçüm paketi bu tekrarlar sırasında kaç değerin öbeğe taşındığını sayıyor ve sonuçta
işlem başına ortalama bir sayı veriyor. Aşağıdaki dosya iki ayrı karşılaştırmalı ölçüm işlevi
tanımlıyor — biri `ayrintili=false`, öbürü `ayrintili=true` çağırıyor — ve ikisini de beşer kez
koşturuyor.

```go
// olcum.go — ayni islevin iki cagrisi, bes kosumda ayirma sayisi
package main

import (
	"fmt"
	"testing"
)

var ornekKayit = Kayit{Ad: "vida", Miktar: 12}

func profilAyrintisiz(b *testing.B) {
	for i := 0; i < b.N; i++ {
		_ = bicimle(ornekKayit, false)
	}
}

func profilAyrintili(b *testing.B) {
	for i := 0; i < b.N; i++ {
		_ = bicimle(ornekKayit, true)
	}
}

func besKosum(f func(*testing.B)) (int64, int) {
	degerler := map[int64]bool{}
	var ilk int64
	for i := 0; i < 5; i++ {
		r := testing.Benchmark(f)
		degerler[r.AllocsPerOp()] = true
		if i == 0 {
			ilk = r.AllocsPerOp()
		}
	}
	return ilk, len(degerler)
}

func main() {
	fmt.Println("-- profil: kac deger ayrildi --")
	ayrintisiz, ad1 := besKosum(profilAyrintisiz)
	ayrintili, ad2 := besKosum(profilAyrintili)
	fmt.Printf("%-24s ayirma=%-3d bes kosumda ayri deger=%d\n", "ayrintisiz cagri", ayrintisiz, ad1)
	fmt.Printf("%-24s ayirma=%-3d bes kosumda ayri deger=%d\n", "ayrintili cagri", ayrintili, ad2)
	fmt.Println("fark:", ayrintili-ayrintisiz, "ayirma, yalniz ayrintili dala ait")
}
```

```
-- profil: kac deger ayrildi --
ayrintisiz cagri         ayirma=2   bes kosumda ayri deger=1
ayrintili cagri          ayirma=9   bes kosumda ayri deger=1
fark: 7 ayirma, yalniz ayrintili dala ait
```

**BS2.** `besKosum`, kursun her başarım dersinde geçerli olan kalıbı burada da uyguluyor:
aynı ölçüm beş kez tekrarlanıyor ve kaç ayrı değer geldiği sayılıyor. Süre burada hiç
okunmuyor; `testing.Benchmark`in döndürdüğü sonuçtan yalnız `AllocsPerOp` alınıyor. Beş
koşumun beşinde de tek bir değer çıkması, bu sayının koşumdan koşuma sabit olduğunu — süre
gibi ortama bağlı olmadığını — gösteriyor.

`degerler` bir `map[int64]bool` olarak tutuluyor, tek bir sayaç değil; bunun nedeni beş
koşumun aynı değeri verip vermediğini **kanıtlamak**, varsaymamak. Beş koşum aynı sayıyı
verirse `degerler` tek bir anahtar taşır ve `len(degerler)` bir çıkar; farklı bir sayı
gelseydi ikinci bir anahtar eklenir ve bu sayı ikiye çıkardı — tıpkı kursun ortak
çekirdeğinde süre ölçümünün sekiz koşumun ikisinde farklı çıkıp çerçeveden çıkarılması gibi.
Ayırma sayısı için bu hiç olmuyor; aşağıdaki çıktıda iki satırın da `ayri deger=1` vermesi bu
iddiayı doğruluyor.

#### Bir İşlevin İçini Profillemek, Bir Servisi Profillemek

Yukarıdaki ölçüm bir profil aracının küçültülmüş bir biçimi. Üretimde çalışan bir servisi
profillemek, gerçek trafik altında hangi işlevin ne kadar bellek ayırdığını **örnekleyerek**
ölçüyor — servis saatlerce çalışıyor, profil aracı bu süre boyunca ayırmaları topluyor ve
sonunda bir dağılım veriyor. Bu dağılım gerçek kullanıcı girdisini yansıtıyor ama koşumdan
koşuma aynı çıkmıyor: bir gün daha çok ayrıntılı kayıt gelirse profil de değişiyor.

Bu derste kullanılan yöntem farklı bir şey yapıyor: tek bir işlevi, tek bir girdiyle, beş kez
koşturup sabit bir sayı arıyor. Bu, bir servisin profilini çıkarmanın yerini tutmuyor —
küçük, yalıtılmış ve yeniden üretilebilir bir ölçüm. Aradaki fark önemli: üretim profili
"gerçekte ne oluyor" sorusuna yanıtlıyor ve yanıtı trafiğe göre değişiyor; buradaki ölçüm "bu
belirli girdiyle bu işlev ne yapıyor" sorusuna yanıtlıyor ve yanıtı sabit. İkisi de profil
sözcüğünü taşıyor, ikisi de koşum bilgisi, ama biri tekrarlanabilir bir deney, öbürü sürekli
bir gözlem.

Bu daraltmanın bir bedeli var: yalıtılmış bir işlevin ölçümü, o işlevin gerçek bir servis
içindeki payını göstermiyor. `bicimle` bir isteğin yüzde biri kadar zaman alıyor olabilir ya
da yüzde doksanı — bu ders bunu hiç söylemiyor, çünkü ölçüm işlevi bağlamından koparıyor. Bir
üretim profilinin asıl gücü tam olarak burada: hangi işlevin **toplam** ayırmanın ne kadarını
oluşturduğunu, bütün program çalışırken gösteriyor. Bu derste kurulan yöntem, o büyük resmin
içindeki tek bir karoyu büyüteçle incelemenin bir modeli — büyük resmin kendisi değil.

#### Profil ile Karşılaştırmalı Ölçüm Arasındaki Fark

Kursun ilerleyen bir dersi de aynı ölçüm aracını (`testing.Benchmark`, `AllocsPerOp`)
kullanıyor ama farklı bir soru soruyor: aynı işin iki ayrı **yazımı** hangisi daha az
ayırıyor. Bu ders o soruyu sormuyor — burada tek bir işlev var, `bicimle` ve iki çağrı
biçimi onun **iki dalını** ayırıyor. Karşılaştırmalı ölçüm iki ayrı gerçekleştirimi kıyaslar;
profilleme tek bir gerçekleştirimin neresinin ayırdığını gösterir. Araç aynı, soru farklı:
biri "hangisi daha iyi" diye soruyor, öbürü "nerede" diye soruyor. Bu ayrım önemli çünkü aynı
sayı (`AllocsPerOp`) iki bağlamda da çıkıyor ve hangi soruya yanıt verdiği yalnız ölçümün
kurulumundan anlaşılıyor.

#### Sayıların Okunması

`ayrintisiz` çağrı iki değer ayırıyor: `fmt.Sprintf("%s:%d", ...)` çağrısının kendisi bir
dizgi üretiyor ve bunu yaparken en az bir ara değeri öbeğe taşıyor. `ayrintili` çağrı dokuz
değer ayırıyor. Aradaki yedi ayırma tümüyle `if ayrintili` bloğuna ait: üç ayrı
`fmt.Sprintf` çağrısı, bir dilim ayırması ve döngü içindeki birleştirmelerin bir kısmı.
Kaynakta bu iki yol da aynı işlevin gövdesinde yan yana duruyor; profil olmadan hangisinin
ne kadar ayırdığı bilinemez.

Bu, profilin yanıtladığı soruyu netleştiriyor: **nerede** ayrılıyor. Hangi çağrı biçimi
daha çok değer ayırıyor, ölçümden doğrudan okunuyor. Ama profil **neden** yavaş olduğunu
söylemiyor — bu derste hiç ölçülmeyen bir soru, çünkü yanıtı süreye bakmayı gerektiriyor
ve süre bu kursta hiçbir derste yazılmıyor. Ayırma sayısının yüksek olması programın yavaş
çalıştığı anlamına gelmez; yalnız o yolun daha çok bellek istediği anlamına gelir. İkisi
genelde birlikte gider ama profilin kendisi bu ikisini ayırmıyor — ayırma sayısını okuyan
kişi ayırıyor.

`ayrintisiz` çağrının iki değer ayırması da başlı başına bir okuma dersi. Kaynakta tek bir
`fmt.Sprintf` çağrısı görünüyor, ama bu tek satır görünenden fazla adımı gizliyor: `Sprintf`
yeni bir dizgi üretiyor, üstelik `k.Miktar` gibi bir tam sayıyı değişken sayıda argüman alan
parametresine (`...any`) geçirirken onu bir arayüz değerine sarıyor. Kaynakta hiçbir
işaretçi, hiçbir `new`, hiçbir `append` görünmediği hâlde iki ayrı ayırmaya yol açan tam
olarak bu iki adım — dizgi üretimi ve arayüze sarma. Hangi değerin ne zaman böyle sarıldığı,
dilin kendi çağrı biçiminin bir sonucu ve kaçış çözümlemesinin verdiği bir karar. O kararın
kendisi sıradaki dersin konusu; bu ders yalnız kaç değerin ayrıldığını sayıyor, hangi
kararın buna yol açtığını değil.

Kursun açılışı on bir bilgiyi kaynak, araç ve koşum olarak üçe ayırmıştı; bu ders o üç
sınıftan yalnız koşumu kullanıyor, çünkü profil sayısı ancak çalıştırılarak öğreniliyor.

#### Sınırlayıcı Ölçüm: Koşmayan Yol Profilde Yok

Yukarıdaki tabloda `ayrintisiz` satırı iki değer gösteriyor — üç `Sprintf` çağrısının,
dilim ayırmasının ve birleştirme döngüsünün **hiçbiri** bu satıra girmiyor. Bunun nedeni bu
değerlerin küçük olması ya da derleyicinin onları optimize etmesi değil: `ayrintili`
yanlış geçildiğinde `if` bloğunun **gövdesi hiç çalışmıyor**. Kaynakta yedi ayırmaya
karşılık gelen kod duruyor, ama `profilAyrintisiz` onu hiç tetiklemiyor; profil de
tetiklenmeyen hiçbir şeyi göremiyor.

**BS3.** Bu, profil çıkarmanın kapsama oranıyla (önceki ders) paylaştığı sınır: ikisi de
yalnız **koşan** yolu ölçüyor. Kapsama oranı koşulmayan satırı saymıyordu; profil koşulmayan
dalın ayırmasını saymıyor. Bir dalın "az ayırdığı" sonucuna varmadan önce o dalın gerçekten
tetiklendiğinden emin olmak gerekiyor — aksi hâlde ölçülen şey o dalın davranışı değil,
hiç çalışmamış olmasıdır. Fark biçimindeki benzerlik de kayda değer: kapsama oranı
koşulmayan satırı sıfır sayarak görmezden geliyordu, profil de koşulmayan dalın
ayırmasını sıfır sayarak görmezden geliyor — ikisi de "yok" ile "ölçülmedi"yi aynı
sembolle (`0`) yazıyor ve okuyan tarafın bu ikisini birbirinden ayırması gerekiyor.

Bu sınır pratikte iki yönlü çalışıyor. Bir yandan, seyrek çalışan bir dal (örneğin yalnız
hata durumunda giren bir kod yolu) günlük profillerde hiç görünmeyebilir ve gözden kaçabilir.
Öte yandan, iki çağrı biçimini karşılaştırırken kullanılan girdinin her iki dalı da gerçekten
tetiklediğinden emin olunmalı; aksi hâlde karşılaştırma yanlış yola bakıyor demektir. Yukarıdaki
ölçümde `profilAyrintisiz` yalnız `ayrintili=false`, `profilAyrintili` yalnız `ayrintili=true`
çağırıyor — bu seçim bilinçli, çünkü karşılaştırmanın amacı tam olarak bu iki dalı ayrı ayrı
görmek.

Aynı sınır, tek bir çağrı biçiminin içinde de geçerli. `bicimle` yalnız iki durumla
çağrıldı: `ayrintili` ya tümüyle doğru ya tümüyle yanlış. Gerçek bir çağıran kümesi bu
işlevi karma bir dağılımla çağırabilir — kayıtların onda biri ayrıntılı, geri kalanı değil —
ve o dağılımın ürettiği ortalama ayırma sayısı yukarıdaki iki uçtan hiçbirine eşit olmaz.
Profil, ölçüldüğü girdiyi anlatıyor; başka bir girdi dağılımının profili başka bir tablo
üretir. Bu yüzden bir profil sayısı, hangi girdiyle üretildiği yazılmadan aktarılamaz —
tıpkı önceki dersteki kapsama oranının hangi test kümesiyle üretildiği yazılmadan
aktarılamaması gibi.

#### Sayı Neye Karar Verdirmemeli

İki satırlık tablo tek başına bir eylem önermiyor. `ayrintili` çağrının yedi değer daha çok
ayırması, `ayrintili` dalının kaldırılması gerektiği anlamına gelmiyor — o dal muhtemelen
gerçek bir ihtiyacı karşılıyor, üç ayrı alanı ayrı ayrı biçimlendirmek başka bir yerde
gerçekten kullanılıyor olabilir. Profil sayısı yalnız **nerede** ayrıldığını gösteriyor;
o ayırmanın gerekli olup olmadığına, hangi çağıranın hangi sıklıkla hangi dalı kullandığına
bakmadan karar verilmiyor. Bu, sıradaki dersin de tekrar edeceği bir uyarı: bir sayının
küçülmesi otomatik olarak bir iyileşme değil, çünkü sayı tek başına neyin karşılığında
küçüldüğünü söylemiyor.

Bu, kursun ilk dersinden beri süren bir kalıbın da tekrarı: bir aracın söylediği bilgi,
aracın **neyi ölçtüğüyle** sınırlı. Kapsam aracı yalnız hangi satırın koştuğunu söylüyordu,
doğruluğu değil; profil aracı yalnız hangi çağrının kaç değer ayırdığını söylüyor, o
ayırmanın gerekip gerekmediğini değil. Her iki durumda da sınırı aşan yorum, aracın
kendisinden değil, okuyan kişinin aracın sınırını unutmasından geliyor.

#### Özet

- Profil bir koşum bilgisidir: hangi çağrının kaç değer ayırdığı kaynağa bakılarak değil,
  yalnız çalıştırılarak öğreniliyor.
- Aynı işlevin iki çağrı biçimi (`ayrintisiz`, `ayrintili`) `AllocsPerOp` ile ölçüldü;
  ayrıntılı çağrı yedi değer daha çok ayırdı ve bu fark beş koşumda değişmedi.
- Profil **nerede** ayrıldığını yanıtlıyor, **neden yavaş** olduğunu yanıtlamıyor — süre bu
  kursta hiçbir derste ölçülmüyor.
- Koşmayan bir dalın ayırdığı değerler profilde hiç görünmüyor; kaynakta duran kod bunu
  değiştirmiyor, yalnız o yolun gerçekten çalışması değiştiriyor.
- Bu sınır, önceki dersteki kapsama oranının sınırıyla aynı biçimde: ikisi de yalnız koşan
  yolu ölçüyor.

#### Sonraki Adım

Profil bir değerin nereye ayrıldığını koşumdan sonra söylüyor. Ama bir değerin yığıtta mı
öbekte mi duracağı kararı koşumdan çok önce, kaynak daha derlenirken veriliyor — bu ders bu
kararın **sonucunu** sayıyla gördü, kararın kendisini hiç görmedi. Sıradaki ders bu kararın
mekanizmasına bakıyor: derleyici bu kararı nasıl bildiriyor, hangi kalıplar bir değeri
öbeğe taşıyor ve aynı işlevin küçük bir değişiklikle kararı nasıl değiştiği.

### Kaçış Çözümlemesi

Source: https://academia.sh/tr/kurslar/go-uretim/kacis-cozumlemesi

Kaçış çözümlemesi bir araç bilgisidir: derleyici bir değeri yığıtta mı öbekte mi ayıracağına karar veriyor ve bunu koşum olmadan bildiriyor. Dört işlevin üçü öbeğe taşınıyor ve aynı işlev küçük bir değişiklikle bu kararını değiştiriyor. Sınırlayıcı ölçüm: bu karar dilin bir sözü değil, derleyicinin sürümüne bağlı bir gerçekleştirim ayrıntısı.

Profil bir değerin nereye ayrıldığını koşumdan **sonra**, sayıyla söyledi. Ama o değerin
yığıtta mı öbekte mi duracağı kararı koşumdan çok önce veriliyor — kaynak henüz
çalıştırılmadan, yalnız derlenirken. Bu ders o kararın kendisine bakıyor: derleyici bu
kararı nasıl bildiriyor ve aynı işlevin gövdesinde küçük bir değişiklik bu kararı nasıl
değiştiriyor.

Bu, kursun açılışından beri süren kaynak/araç/koşum ayrımının en net **araç** örneği. Kaçış
çözümlemesi kaynakta yazılı değil — hiçbir yerde `öbek` ya da `yığıt` sözcüğü geçmiyor; kod yalnız
bir değişken tanımlıyor ve onu kullanıyor. Ama derleyici bu kararı vermek için programı
**çalıştırmıyor** da; kararı derleme sırasında veriyor ve isteğe bağlı olarak bildiriyor. Bu,
koşum bilgisinden farklı: koşum bilgisi (bir önceki dersteki profil sayısı gibi) yalnız
çalıştırıldığında ortaya çıkıyordu ve her koşumda aynı kalıp kalmadığı ayrıca sınanıyordu. Araç
bilgisi ise koşum gerektirmiyor — aynı kaynak, aynı derleyiciyle, her seferinde aynı kararı
veriyor.

#### Yığıt, Öbek ve Aradaki Karar

Bir değerin yığıtta mı öbekte mi duracağı sorusu bu kursta yeni değil: yığıt ve öbek
Bilgisayarlar Nasıl Çalışır kursunda (M01/K01) bellek düzeninin bir parçası olarak kuruldu
ve burada tekrarlanmıyor. O kursun kurduğu ayrım şuydu — yığıt bir çağrı çerçevesiyle birlikte
açılıp kapanan, hızlı ve sıralı bir alan; öbek ise çağrı çerçevesinden bağımsız, elle (ya da
bu derste görüleceği gibi derleyicinin kararıyla) yönetilen bir alan. Bu ders o iki alanın
**ne olduğunu** değil, bir değerin **hangisine** gideceğine kimin karar verdiğini ölçüyor.

Kararın kendisi basit bir kuralla özetlenebilir: bir değerin ömrü, onu oluşturan çağrı
çerçevesinin ömrünü aşıyorsa değer öbeğe taşınmak zorunda — aksi hâlde çağrı çerçevesi
kapandığında değer de kaybolurdu. Derleyici bunu, kaynak kodun kendisini izleyerek —
işaretçinin nereye gittiğini, hangi arayüze geçtiğini, hangi kapsayıcıya eklendiğini takip
ederek — karar veriyor. Bu izleme işlemi **kaçış çözümlemesi**: bir değerin, onu oluşturan
kapsamdan "kaçıp kaçmadığını" belirleyen derleme adımı.

Yığıt ile öbek arasındaki fark yalnız "nerede durduğu" değil, "kim yönettiği". Yığıttaki bir
değerin ömrü çağrı çerçevesiyle birlikte otomatik kapanıyor — ayrı bir temizleme adımı
gerekmiyor. Öbekteki bir değerin ömrü ise çağrı çerçevesinden bağımsız ve bu değerin ne
zaman kaldırılacağına program değil çalışma zamanı karar veriyor. Bu ikinci soru — öbeğe
taşınan bir değer bir gün nasıl kalkıyor — sıradaki dersin konusu; bu ders yalnız değerin
öbeğe **gidip gitmediğine** bakıyor.

Önceki derste `bicimle` işlevinin `ayrintili` dalı yedi fazladan değer ayırıyordu ve bu
derste o yedi ayırmanın **nereden** geldiği açıklığa kavuşuyor: üç `fmt.Sprintf` çağrısının
her biri, ürettiği dizgiyi ve argümanlarını bir arayüze sararken tam olarak bu derste
görülecek türden bir kaçış kararı veriyordu. Profil o kararların **sonucunu** (kaç değer)
saydı; bu ders kararın **kendisini** okuyor.

#### Kararı Tek Başına Okumak

Kaçış çözümlemesinin kararını okumanın yolu, derleyiciyi **program içinden** çağırmak ve
metnini değil, yalnız kararını okumak. Aşağıdaki yardımcı işlev bunu yapıyor: verilen bir
kaynak parçasını kendi başına, geçici bir modüle yazıyor, derleyiciyi bu modül üzerinde
koşturuyor ve çıktıda öbeğe taşıma bildirimi olup olmadığını arıyor — bildirimin **metni**
hiçbir zaman basılmıyor, yalnız bu bir doğruluk değeri.

```go
// kacis.go — derleyicinin obege tasima kararini program icinden okuyor, metnini basmiyor
package main

import (
	"os"
	"os/exec"
	"strings"
)

// kaciyorMu, verilen govdeyi tek basina bir pakete yaziyor ve derleyiciyi
// bu paket uzerinde kosturuyor. Donen deger yalniz bir dogruluk degeri;
// derleyicinin bildirim metni hicbir zaman okunmuyor ya da basilmiyor.
func kaciyorMu(govde string) bool {
	dizin, err := os.MkdirTemp("", "kacis")
	if err != nil {
		return false
	}
	defer os.RemoveAll(dizin)
	os.WriteFile(dizin+"/go.mod", []byte("module kacis\n\ngo 1.24\n"), 0o644)
	os.WriteFile(dizin+"/deger.go", []byte(govde), 0o644)
	cmd := exec.Command("go", "build", "-gcflags=-m -l", "-o", os.DevNull, ".")
	cmd.Dir = dizin
	cmd.Env = append(os.Environ(), "GOTOOLCHAIN=local")
	cikti, _ := cmd.CombinedOutput()
	metin := string(cikti)
	return strings.Contains(metin, "moved to heap") || strings.Contains(metin, "escapes to heap")
}
```

**BS4.** `kaciyorMu` hedef işlevi hiç çağırmıyor — yalnız tanımlıyor. Derleyici, bir işlevi
çağıran bir kod olmasa bile onun gövdesini derleme sırasında çözümlüyor ve kararını
bildiriyor; bu yüzden `kaciyorMu`nun geçici modülünde `func main() {}` boş kalabiliyor. Bu,
kararın **çağrılmaya değil tanıma** bağlı olduğunu gösteriyor — kaçış çözümlemesi koşum
gerektirmiyor, çünkü koşan bir şey yok; yalnız derlenen bir şey var.

Her kaynağın **kendi başına**, ayrı bir pakette derlenmesi de bilinçli bir seçim. Derleyici
bazı küçük işlevleri çağrıldıkları yere açabiliyor; böyle bir açma olduğunda kaçış kararı
işlevin kendi gövdesine değil, açıldığı çağrı yerine yazılıyor ve okuması güçleşiyor. Her
kaynağı yalnız kendi paketinde, başka hiçbir çağrıya karışmadan derlemek bu karmaşayı
baştan kaldırıyor: kararın nereye yazıldığı, hangi işlevin kendisine ait olduğu belirsizliğe
düşmüyor.

Bu izolasyonun bir bedeli de var: gerçek bir programda `adresDonduren` gibi bir işlev,
çağrıldığı yere açılmışsa, kararı burada ölçülenden **farklı** çıkabilir — çağıran taraf
sonucu hemen kullanıp bırakıyorsa, açma sonrası derleyici değeri yine de yığıtta tutmayı
başarabilir. Bu ders o durumu ölçmüyor; yalnız işlevin **kendi başına**, hiçbir çağrıya
açılmadan verdiği kararı ölçüyor. Gerçek bir programdaki karar bu yüzden burada görülenden
daha iyimser çıkabilir, hiçbir zaman daha kötümser değil — açma yalnız kaçışı **önleyebilir**,
hiçbir zaman yeni bir kaçış eklemiyor.

#### Dört İşlev, Bir Karar Tablosu

Aşağıdaki ölçüm dört ayrı işlev tanımlıyor, dördü de aynı `Kayit` yapısını kullanıyor ama
dördü de değeri farklı bir yolla ele alıyor: biri değeri yalnız yerel olarak okuyor, biri
adresini döndürüyor, biri bir arayüze veriyor, biri bir dilime ekliyor. Beşinci kaynak,
birincinin (`yerelKalan`) neredeyse birebir aynısı — tek fark, son satırın bir değer yerine
bir adres döndürmesi.

```go
// olcum.go — dort islev, derleyicinin obege tasima karari, ve kucuk bir degisiklik
package main

import "fmt"

const yerelKalan = `package main

type Kayit struct{ Ad string }

func yerelKalan() int {
	k := Kayit{Ad: "vida"}
	return len(k.Ad)
}

func main() {}
`

const adresDonduren = `package main

type Kayit struct{ Ad string }

func adresDonduren() *Kayit {
	k := Kayit{Ad: "somun"}
	return &k
}

func main() {}
`

const arayuzeVerilen = `package main

import "fmt"

type Kayit struct{ Ad string }

func arayuzeVerilen(k Kayit) {
	fmt.Println(k)
}

func main() {}
`

const dilimeEklenen = `package main

type Kayit struct{ Ad string }

func dilimeEklenen(hedef []*Kayit) []*Kayit {
	k := Kayit{Ad: "civi"}
	return append(hedef, &k)
}

func main() {}
`

const yerelKalanDegisik = `package main

type Kayit struct{ Ad string }

func yerelKalanDegisik() *string {
	k := Kayit{Ad: "vida"}
	return &k.Ad
}

func main() {}
`

func main() {
	satirlar := []struct {
		ad     string
		kaynak string
	}{
		{"yerelde kalan deger", yerelKalan},
		{"adres donduren deger", adresDonduren},
		{"arayuze verilen deger", arayuzeVerilen},
		{"dilime eklenen deger", dilimeEklenen},
		{"ayni islev, kucuk degisiklikle", yerelKalanDegisik},
	}
	kacan := 0
	fmt.Println("-- derleyicinin obege tasima karari --")
	for i, s := range satirlar {
		tasindi := kaciyorMu(s.kaynak)
		if tasindi && i < 4 {
			kacan++
		}
		fmt.Printf("%-32s obege tasindi=%t\n", s.ad, tasindi)
	}
	fmt.Println("dort islevden obege tasinan:", kacan, "/ 4")
}
```

```
-- derleyicinin obege tasima karari --
yerelde kalan deger              obege tasindi=false
adres donduren deger             obege tasindi=true
arayuze verilen deger            obege tasindi=true
dilime eklenen deger             obege tasindi=true
ayni islev, kucuk degisiklikle   obege tasindi=true
dort islevden obege tasinan: 3 / 4
```

#### Sayıların Okunması

Dört işlevin **üçü** öbeğe taşınıyor, biri kalmıyor. `yerelKalan` değeri yalnız kendi
gövdesinde okuyor (`len(k.Ad)`) ve dışarı hiçbir şey vermiyor — değer, çağrı çerçevesiyle
birlikte kapanabiliyor, yığıtta kalıyor. Öbür üçü, değeri üç ayrı yoldan **dışarı**
taşıyor: `adresDonduren` değerin adresini döndürüyor, `arayuzeVerilen` değeri bir arayüz
parametresine (`fmt.Println`nin değişken sayıda argüman alan `...any` parametresi)
geçiriyor, `dilimeEklenen` değerin adresini bir dilime ekliyor. Üçünün de ortak yanı: değer,
onu oluşturan çağrı çerçevesinden **daha uzun yaşamak zorunda** — çağrı bittikten sonra da
birileri o değere erişebilecek. Derleyici bunu görüyor ve değeri öbeğe taşıyarak çağrı
çerçevesinin ömründen bağımsızlaştırıyor.

Beşinci satır bunu daha keskin gösteriyor: `yerelKalanDegisik`, `yerelKalan`ın neredeyse
aynısı — aynı yapı, aynı alan adı, aynı ilk satır. Tek fark son satırda: `return
len(k.Ad)` yerine `return &k.Ad`. Bu tek değişiklik, dönen değeri bir tam sayıdan bir
**adrese** çeviriyor ve kararı `false`tan `true`ya döndürüyor. Kaynakta hiçbir yeni
işaretçi türü, hiçbir açık bellek ayırma çağrısı yok — yalnız `&` işareti eklendi. Kaçış
çözümlemesi kararı, kodun **niyetine** değil, değerin **erişilebilirliğine** bakıyor;
bir alanın adresini almak, o alanı tutan yapının artık çağrı çerçevesinde kalamayacağı
anlamına geliyor.

Bu asimetri, yığıtta kalmanın neden **varsayılan** değil bir **kazanım** sayıldığını
açıklıyor. Bir değeri yığıtta tutmak için özel bir şey yazmaya gerek yok — yalnız değeri
dışarı taşımamak yeterli. Ama bu "taşımama" kırılgan: `dilimeEklenen` örneğinde olduğu gibi
bir dilime eklemek, `arayuzeVerilen` örneğinde olduğu gibi bir arayüze geçirmek, ya da
`yerelKalanDegisik` örneğinde olduğu gibi tek bir alanın adresini almak, dördü de aynı
yönde çalışıyor: değeri yığıttan öbeğe itiyor. Tersi yönde bir işlem yok — bir değeri
"öbeğe zorlamak" için tek satır yeterken, öbekten kurtarmak için kodun o değeri hiç
dışarı vermemesi gerekiyor. Bu yüzden dört işlevin üçünün kaçması bu örnek kümesine özgü bir
tesadüf değil: dışarı veren her işlem aynı yöne itiyor.

Kursun açılışı on bir bilgiyi kaynak, araç ve koşum diye ayırmıştı; bu ders o üç sınıftan
yalnız aracı kullanıyor — kaçan değer sayısı ne kaynakta yazılı ne yalnız koşarak öğreniliyor,
derleyici bunu **koşum olmadan** türetip bildiriyor.

`kaciyorMu`nun tek bir doğruluk değeri döndürmesi, derleyicinin aslında **iki ayrı türde**
bildirim verdiği gerçeğini gizliyor. `adresDonduren` gibi bir işlevde bildirim yerel bir
değerin taşındığını söylüyor; `arayuzeVerilen` gibi bir işlevde ise bildirim bir
**parametrenin** kendisinin çağıranın kapsamına "sızdığını" söylüyor, çünkü değer zaten
çağıran tarafından üretilip parametre olarak geçiriliyor. İkisi de sonuçta aynı yere
varıyor — değer öbekte duruyor — ama yolu farklı: biri işlevin **içinde** oluşan bir değeri,
öbürü işleve **dışarıdan gelen** bir değeri taşıyor. Bu dersin ölçümü bu ikisini tek bir
`true`/`false` altında birleştiriyor çünkü ikisinin de ortak sonucu — öbeğe taşınma —
kursun sorduğu soru; yolun ayrıntısı derleyicinin iç işleyişine ait ve burada okunmuyor.

#### Sınırlayıcı Ölçüm: Bu Bir Söz Değil, Bir Karar

Yukarıdaki tabloyu okuyan biri hızla "Go'da bir değerin adresi alınırsa öbeğe gider" gibi
bir kurala varabilir. Bu kural bugünkü derleyici için doğru çıkıyor ama dilin **sözü**
değil. Dil belirtimi hiçbir yerde "adresi alınan değer öbeğe gider" demiyor; yalnız bir
değerin ne kadar yaşayacağını garanti ediyor, **nerede** durduğunu değil. Kaçış
çözümlemesi bu garantiyi yerine getirmenin bir **yöntemi** — belirli bir derleyicinin,
belirli bir sürümde seçtiği bir gerçekleştirim kararı.

Bunun somut sonucu şu: daha akıllı bir derleyici, `adresDonduren` gibi bir işlevi de
yığıtta tutmayı başarabilir — örneğin çağıran taraf sonucu hemen kullanıp atıyorsa ve
derleyici bunu kanıtlayabiliyorsa. Bu ders o kanıtlamayı görmedi çünkü bu dersteki dört
işlevin hiçbiri böyle bir kanıtlamaya izin vermiyor — hepsi değeri gerçekten çağrı
çerçevesinin dışına taşıyor. Ama aynı kaynak, farklı bir derleyici sürümüyle derlendiğinde
farklı bir karar verebilir; bu ders bu yüzden karar tablosunu bir **gerçek** olarak değil,
bu derlemenin bir **gözlemi** olarak sunuyor.

Aynı belirsizlik dilin **belirtimi** ile dilin **gerçekleştirimi** arasındaki genel ayrımın
bir örneği. Belirtim bir değerin ne kadar yaşayacağını, hangi işlemlerin geçerli olduğunu
garanti ediyor; gerçekleştirim bu garantiyi hangi bellek alanında, hangi yöntemle tuttuğunu
seçiyor. Aynı ayrım derleyicinin başka kararlarında da geçerli — sıralama garantisi
vermeyen bir sözlüğün gezinme sırası da benzer biçimde bir gerçekleştirim seçimiydi, dilin
sözü değildi. Kaçış çözümlemesi bu genel örüntünün bellek yerleşimine uygulanmış hâli:
gördüğünüz karar doğru, ama o karar dilin değil derleyicinin.

Bu sınır, önceki dersin sınırıyla aynı köke iniyor: bir araç bilgisi, aracın kendisiyle
sınırlı. Profil aracı yalnız koşan yolu görüyordu; kaçış çözümlemesi aracı yalnız **bu
derleyicinin bu kararını** gösteriyor. İkisi de gerçeği değil, aracın o andaki yanıtını
veriyor.

Bunun pratik bir sonucu var: bir kod tabanında "bu değer öbeğe kaçıyor, düzeltilsin" diye
yazılmış bir yorum, derleyici değiştiğinde yanlış çıkabilir. Kaçış kararı kaynakla birlikte
saklanan bir gerçek değil, derleme anında yeniden üretilen bir çıktı. Bu yüzden bu dersteki
tablo da tek seferlik değil — aynı `kaciyorMu` çağrısı her çalıştırıldığında derleyiciyi
yeniden koşturuyor ve kararı yeniden soruyor; hiçbir karar önceki bir koşumdan
saklanmıyor.

#### Özet

- Kaçış çözümlemesi bir araç bilgisidir: derleyici, programı hiç çalıştırmadan, bir değerin
  yığıtta mı öbekte mi ayrılacağına karar veriyor ve bunu koşum olmadan bildiriyor.
- Dört işlevin üçü öbeğe taşındı — değerin adresini döndüren, bir arayüze veren ve bir
  dilime ekleyen. Değeri yalnız kendi gövdesinde tutan işlev yığıtta kaldı.
- Aynı işlevin bir satırlık değişikliği (`len(k.Ad)` yerine `&k.Ad`) kararı `false`tan
  `true`ya çevirdi — kaçış çözümlemesi kodun niyetine değil, değerin erişilebilirliğine
  bakıyor.
- Bu karar dilin bir sözü değil, derleyicinin sürümüne bağlı bir gerçekleştirim ayrıntısı;
  başka bir derleyici sürümü aynı kaynak için farklı bir karar verebilir.
- Yığıt ve öbek kavramı burada tekrar kurulmadı; bu ders yalnız derleyicinin bu ikisi
  arasındaki kararını ölçtü.
- Kaçış kararı yalnız bir yönde çalışıyor: bir değeri dışarı vermek onu öbeğe iten her
  yeni yol, kaçışı artırıyor; hiçbir işlem tek başına bir değeri öbekten geri çağırmıyor.

#### Sonraki Adım

Bu ders bir değerin öbeğe **taşınıp taşınmadığını** sordu — tek seferlik, derleme anında
verilen bir karar. Ama bir programın gerçek çalışma sürecinde bu karar tek başına yeterli
değil: öbeğe taşınan her değer bir gün oradan da kalkması gerekiyor ve bu kalkışın kendisi
derleme anında değil koşum sırasında oluyor. Sıradaki ders bu ikinci yarıya bakıyor — bu
derste kurulan "öbeğe gitme" kararının toplamda ne kadar biriktiği, belirli tekniklerin bu
toplamı nasıl düşürdüğü ve bir sayının düşmesinin her zaman bir iyileşme anlamına
gelmediği.

### Çöp Toplama Davranışı

Source: https://academia.sh/tr/kurslar/go-uretim/cop-toplama-davranisi

Ayırma baskısının azalması sayıyla ölçülüyor: önceden yer ayırma, arabellek yeniden kullanımı ve değer/işaretçi seçimi aynı işin ayırma sayısını dörtten bire, dörtten sıfıra, birden altmış beşe taşıyor. Sınırlayıcı ölçüm: bir sayının düşmesi her zaman bir iyileştirme değil — okunabilirliği bozan bir yazım da aynı sayıya ulaşabiliyor.

Önceki ders bir değerin öbeğe **taşınıp taşınmadığını**, derleme anında verilen tek seferlik
bir karar olarak ölçtü. Ama bir program bir kez değil binlerce kez çalışıyor ve öbeğe giden
her değer bir gün oradan da kalkması gerekiyor. Bu ders o kalkışın yükünü sorguluyor: aynı
işi yapan farklı yazımlar, toplamda kaç değeri öbeğe gönderiyor ve bu sayı belirli
tekniklerle nasıl düşüyor?

Çöp toplamanın kendisi — erişilebilirlik, kök kümesi, işaretle ve süpür adımları,
duraklatmanın yaşayan nesneyle orantılı oluşu — İşletim Sistemi Kavramları kursunda
(M01/K05) kavram olarak kuruldu ve burada tekrarlanmıyor. Bu ders kuramı değil **sonucu**
ölçüyor: öbeğe daha az değer göndermek, toplayıcının daha az iş yapması anlamına geliyor.
Duraklatma süresi, geri kazanılan bellek miktarı ve nesil boyutu bu derste hiç yazılmıyor —
hepsi ortama bağlı; sayılan tek şey **ayırma sayısı**.

Bu daraltmanın gerekçesi önceki kurstan geliyor: o kurs, toplayıcının işinin yaşayan
nesnelerle orantılı olduğunu kurmuştu — ne kadar çok nesne öbekte duruyorsa taraması o kadar
uzun sürüyor. Bu ders o ilişkinin girdi ucuna bakıyor: bir program öbeğe **daha az** nesne
gönderirse, tarayacak daha az şey oluyor. Aradaki bağ dolaylı — bu ders duraklatmayı hiç
ölçmüyor — ama yön açık: ayırma sayısı düşerse toplayıcının işi de düşme eğiliminde.
Bu dersin ölçtüğü, o düşüşün girdi tarafı; toplayıcının bunu nasıl işlediği önceki kursun
konusu.

#### Ayırma Baskısı: Üç Teknik, Bir Ölçüm

"Ayırma baskısı" bir programın birim zamanda öbeğe ne kadar yeni değer gönderdiğini
anlatıyor. Baskı yüksekse toplayıcı daha sık çalışmak zorunda kalıyor; baskı düşükse daha
seyrek. Bu ders baskıyı süre üzerinden değil, **sayı** üzerinden ölçüyor: aynı işin bir
kere çalıştırılması kaç değer ayırıyor. Üç teknik bu sayıyı düşürüyor — önceden yer
ayırma, arabellek yeniden kullanımı, değer/işaretçi seçimi — ve dördüncü bir örnek bu
sayının tek başına yeterli olmadığını gösteriyor.

Dördü de aynı ölçüm kalıbını paylaşıyor: bir işlev tanımlanıyor, o işlevi çağıran bir
karşılaştırmalı ölçüm işlevi yazılıyor ve bu ölçüm beş kez koşturulup kaç ayrı değer
geldiği sayılıyor. Önceki iki dersten farklı olarak burada tek bir değer değil, dört
ayrı karşılaştırma yan yana duruyor — her biri kendi çiftiyle (ham/ayrılmış,
yeni/yeniden, değer/işaretçi) bir öncesi ve sonrasını gösteriyor.

```go
// olcum.go — uc teknigin ayirma sayisina etkisi, bes kosumda
package main

import (
	"fmt"
	"strconv"
	"testing"
)

type Kayit struct {
	Ad     string
	Miktar int
}

const kayitSayisi = 64

var toplamYut, uzunlukYut, kayitYut int

// --- a) onceden yer ayirma ---

//go:noinline
func topluOlustur(n int) []int {
	var d []int
	for i := 0; i < n; i++ {
		d = append(d, i)
	}
	return d
}

//go:noinline
func topluOlusturAyrilmis(n int) []int {
	d := make([]int, 0, n)
	for i := 0; i < n; i++ {
		d = append(d, i)
	}
	return d
}

func benchTopluHam(b *testing.B) {
	for i := 0; i < b.N; i++ {
		toplamYut += len(topluOlustur(kayitSayisi))
	}
}
func benchTopluAyrilmis(b *testing.B) {
	for i := 0; i < b.N; i++ {
		toplamYut += len(topluOlusturAyrilmis(kayitSayisi))
	}
}

// --- b) arabellek yeniden kullanimi ---

//go:noinline
func satirOlustur(n int) []byte {
	var buf []byte
	for i := 0; i < n; i++ {
		buf = append(buf, strconv.Itoa(i)...)
		buf = append(buf, ' ')
	}
	return buf
}

//go:noinline
func satirOlusturYeniden(buf []byte, n int) []byte {
	buf = buf[:0]
	for i := 0; i < n; i++ {
		buf = append(buf, strconv.Itoa(i)...)
		buf = append(buf, ' ')
	}
	return buf
}

var paylasilanArabellek = make([]byte, 0, 8)

func benchArabellekYeni(b *testing.B) {
	for i := 0; i < b.N; i++ {
		uzunlukYut += len(satirOlustur(16))
	}
}
func benchArabellekYeniden(b *testing.B) {
	for i := 0; i < b.N; i++ {
		paylasilanArabellek = satirOlusturYeniden(paylasilanArabellek, 16)
		uzunlukYut += len(paylasilanArabellek)
	}
}

// --- c) deger / isaretci secimi ---

//go:noinline
func degerDilimiOlustur(n int) []Kayit {
	d := make([]Kayit, 0, n)
	for i := 0; i < n; i++ {
		d = append(d, Kayit{Ad: "x", Miktar: i})
	}
	return d
}

//go:noinline
func isaretciDilimiOlustur(n int) []*Kayit {
	d := make([]*Kayit, 0, n)
	for i := 0; i < n; i++ {
		d = append(d, &Kayit{Ad: "x", Miktar: i})
	}
	return d
}

func benchDegerDilimi(b *testing.B) {
	for i := 0; i < b.N; i++ {
		kayitYut += len(degerDilimiOlustur(kayitSayisi))
	}
}
func benchIsaretciDilimi(b *testing.B) {
	for i := 0; i < b.N; i++ {
		kayitYut += len(isaretciDilimiOlustur(kayitSayisi))
	}
}

// --- d) karisik yazim: ayni sayiya baska bir yoldan ulasiyor ---

//go:noinline
func topluOlusturKarisik(n int) []int {
	kapasite := 0
	for sayac := 0; sayac < n; sayac++ {
		kapasite++
	}
	d := make([]int, 0, kapasite)
	for i := 0; i < n; i++ {
		d = append(d, i)
	}
	return d
}

func benchTopluKarisik(b *testing.B) {
	for i := 0; i < b.N; i++ {
		toplamYut += len(topluOlusturKarisik(kayitSayisi))
	}
}

func besKosum(f func(*testing.B)) (int64, int) {
	degerler := map[int64]bool{}
	var ilk int64
	for i := 0; i < 5; i++ {
		r := testing.Benchmark(f)
		degerler[r.AllocsPerOp()] = true
		if i == 0 {
			ilk = r.AllocsPerOp()
		}
	}
	return ilk, len(degerler)
}

func satir(ad string, f func(*testing.B)) {
	ayirma, ayriDeger := besKosum(f)
	fmt.Printf("%-30s ayirma=%-3d bes kosumda ayri deger=%d\n", ad, ayirma, ayriDeger)
}

func main() {
	fmt.Println("-- ayirma baskisi: teknik ayirma sayisini nasil degistiriyor --")
	satir("onceden ayrilmamis dilim", benchTopluHam)
	satir("onceden ayrilmis dilim", benchTopluAyrilmis)
	satir("her cagrida yeni arabellek", benchArabellekYeni)
	satir("yeniden kullanilan arabellek", benchArabellekYeniden)
	satir("deger dilimi", benchDegerDilimi)
	satir("isaretci dilimi", benchIsaretciDilimi)
	satir("karisik yazim (ayni sayi)", benchTopluKarisik)
}
```

```
-- ayirma baskisi: teknik ayirma sayisini nasil degistiriyor --
onceden ayrilmamis dilim       ayirma=4   bes kosumda ayri deger=1
onceden ayrilmis dilim         ayirma=1   bes kosumda ayri deger=1
her cagrida yeni arabellek     ayirma=4   bes kosumda ayri deger=1
yeniden kullanilan arabellek   ayirma=0   bes kosumda ayri deger=1
deger dilimi                   ayirma=1   bes kosumda ayri deger=1
isaretci dilimi                ayirma=65  bes kosumda ayri deger=1
karisik yazim (ayni sayi)      ayirma=1   bes kosumda ayri deger=1
```

**BS5.** Yedi işlevin hepsi `//go:noinline` ile işaretlendi — dilin kendi derleme
yönergesi, derleyiciye bu işlevi çağrıldığı yere açmamasını söylüyor. Bunun nedeni önceki
dersle aynı: bir işlev çağrıldığı yere açılırsa dönen değerin çağıran tarafından hemen
kullanılıp atıldığı görülebiliyor ve bu, karşılaştırmanın ölçmek istediği ayırma davranışını
gizleyebiliyor. Yönerge, dört tekniğin ayırma sayısını **kendi gövdesinde**, herhangi bir
çağırana karışmadan ölçmeyi sağlıyor.

`toplamYut`, `uzunlukYut` ve `kayitYut` paket düzeyinde üç değişken olarak duruyor ve her
karşılaştırmalı ölçüm işlevi kendi döndürdüğü değeri bunlardan birine yazıyor. Bunun
amacı sonucu "kullanmak": bir işlevin dönüş değeri hiçbir yere yazılmazsa derleyici o
çağrının tamamını gereksiz sayıp kaldırabiliyor ve o zaman ölçülen şey işlevin kendisi
değil boş bir döngü olurdu. Sonucu bir pakete yazmak bu riski kaldırıyor — çağrı gerçekten
oluyor ve gerçekten ayırıyor.

#### Sayıların Okunması

**Önceden yer ayırma (a):** `topluOlustur`, boş bir dilime tek tek ekleyerek 64 elemana ulaşıyor;
dilim her kapasitesi dolduğunda büyüyor ve her büyüme bir yeniden ayırma. Bu büyüme dört kez
tetikleniyor. `topluOlusturAyrilmis` aynı 64 elemanı, `make([]int, 0, 64)` ile baştan doğru
kapasitede açılan bir dilime ekliyor — büyüme hiç tetiklenmiyor; tek ayırma yalnız ilk `make`
çağrısına ait. Dört ayırmadan bire düşüş, kapasitenin **önceden bilindiği** durumlarda büyümenin
gereksiz olduğunu gösteriyor.

**Arabellek yeniden kullanımı (b):** `satirOlustur` her çağrıda sıfırdan başlayan bir
`[]byte` açıyor ve 16 parçayı buna ekleyerek dört kez büyütüyor. `satirOlusturYeniden` ise
paket düzeyinde saklanan tek bir arabelleği her çağrıda `buf[:0]` ile sıfır uzunluğa
indirgeyip yeniden dolduruyor; kapasite önceki çağrılardan zaten yeterli boyuta ulaştığı
için yeni bir ayırma hiç gerekmiyor. Dörtten sıfıra düşüş, önceki tekniktekinden daha
keskin — çünkü burada yalnız büyüme sayısı değil, **her çağrıdaki ilk ayırma** da ortadan
kalkıyor.

Bu sıfır sayısının bir önkoşulu var: arabelleğin kapasitesi, art arda gelen çağrıların
hiçbirinde aşılmıyor. `paylasilanArabellek` sekiz baytlık bir kapasiteyle açılıyor ama ilk
birkaç çağrı sırasında kendiliğinden büyüyor, çünkü `satirOlusturYeniden` da `append`
kullanıyor ve kapasite yetmediğinde büyüme kaçınılmaz. Beş koşumun beşinde de sıfır
çıkması, bu büyümenin **ilk çağrıda bir kez** olup sonrasında hiç tekrarlanmadığını
gösteriyor — kapasite bir kere yeterli boyuta ulaştıktan sonra aynı boyuttaki her çağrı
onu yeniden kullanabiliyor. Girdi boyutu değişken olsaydı (bazen 16, bazen 1600 parça)
büyüme tekrar tekrar tetiklenir ve sıfıra düşüş kaybolurdu.

**Değer/işaretçi seçimi (c):** `degerDilimiOlustur`, 64 `Kayit` değerini **tek bir**
bitişik dilimde tutuyor — tüm elemanlar aynı ayırmanın parçası, toplam ayırma sayısı bir.
`isaretciDilimiOlustur` ise her elemanı `&Kayit{...}` ile ayrı ayrı öbeğe gönderiyor: bir
ayırma dilimin kendisi için, altmış dört ayırma da elemanların her biri için — toplam
altmış beş. Aradaki fark iki tekniktekinden çok daha büyük, çünkü burada değişen şey
büyüme kalıbı değil, **kaç ayrı nesnenin** var olduğu.

Bu fark önceki dersle doğrudan bağlanıyor: `&Kayit{...}` yazımı, önceki derste ölçülen
`adresDonduren` kalıbının aynısı — bir değerin adresini almak onu öbeğe itiyor. Buradaki
fark ölçek: önceki derste tek bir değerin kaderi ölçülüyordu, burada aynı karar altmış
dört kez tekrarlanıyor ve etkisi katlanarak büyüyor. Bir yapı dilimi seçmek yerine bir
işaretçi dilimi seçmek, gerçek kod tabanlarında sık görülen bir karar — genelde büyük
yapıları kopyalamaktan kaçınmak için. Bu ölçüm o kazancın bir bedeli olduğunu gösteriyor:
kopyalamaktan kaçınmak, ayırma sayısını artırıyor. Hangi bedelin hangi kazanca değdiği bu
dersin ölçemediği bir soru — yapının boyutu, kopyalama sıklığı ve kalan referans sayısı gibi
etkenlere bağlı ve durumdan duruma değişiyor.

#### Üç Tekniğin Ortak Noktası

Üç tekniğin de dayandığı ortak bir düşünce var: **bir kez yeterince büyük ayırmak, çok
kez küçük küçük ayırmaktan daha az iş çıkarıyor**. Önceden yer ayırma bunu kapasiteyi
baştan biliyorsa uyguluyor; arabellek yeniden kullanımı bunu kapasiteyi bir kez öğrenip
tekrar tekrar kullanarak uyguluyor; değer dilimi bunu elemanları ayrı ayrı değil bitişik
tutarak uyguluyor. Üçü de aynı yöne itiyor — ayrı ayrı ayırma sayısını azaltmak — ama
üçünün önkoşulu farklı: biri kapasitenin **önceden bilinmesini**, biri arabelleğin
**paylaşılabilir olmasını**, biri elemanların **bitişik durabilmesini** gerektiriyor. Bu
önkoşullardan biri sağlanmıyorsa teknik uygulanamıyor — kapasite önceden bilinmiyorsa
`make`e ne yazılacağı belirsiz kalıyor, arabellek eşzamanlı erişiliyorsa paylaşmak yarış
koşuluna yol açıyor, elemanlar farklı yaşam sürelerinde tutuluyorsa bitişik dilim onları
gereksiz yere bir arada tutuyor.

#### Sınırlayıcı Ölçüm: Bir Sayının Düşmesi Otomatik Bir İyileştirme Değil

Dördüncü satır, `topluOlusturKarisik`, `topluOlusturAyrilmis` ile **aynı** sayıya —
bire — ulaşıyor. Ama iki işlevin gövdesi aynı değil: `topluOlusturKarisik` kapasiteyi
doğrudan `n` olarak almak yerine, `n` kadar dönen ayrı bir sayaç döngüsüyle **yeniden
hesaplıyor** ve sonucu `kapasite` adlı bir değişkene yazıyor. Bu döngü hiçbir yeni bilgi
üretmiyor — `kapasite` her zaman `n`e eşit çıkıyor — ama kaynağı okuyan birine "neden
ayrı bir hesaplama var, `n` yetmiyor mu" sorusunu sorduruyor.

**BS6.** Bu, kursun en önemli uyarılarından biri: iki yazım aynı ayırma sayısına ulaştı,
ama biri okunaklı biri değil. Ayırma sayısı tek başına hangi kodun **daha iyi** olduğunu
söylemiyor — yalnız kaç değerin öbeğe gittiğini söylüyor. `topluOlusturKarisik`daki
gereksiz döngü hiçbir ayırma eklemedi, çünkü yalnız bir tam sayı üzerinde çalışıyor
(tam sayılar bu boyutta yığıtta kalıyor); ama okunabilirliği bozdu ve kodu okuyanın zaman
kaybetmesine yol açtı. Bir sayı düştüğünde ya da sabit kaldığında sorulması gereken soru
"bu değişiklik neyin karşılığında geldi" — yalnızca "sayı ne oldu" değil.

Bu sınır, kaçış çözümlemesi dersindeki sınırla da bağlanıyor: bir değerin öbeğe gitmesi
kararı derleyicinin kararıydı, kodun niyeti değildi. Burada da benzer bir ayrım var —
ayırma sayısını düşürmek için yazılan kod, sayıyı gerçekten düşürebilir ama bunun **neden**
düştüğünü açıklamıyor. `topluOlusturKarisik`ı okuyan biri, sayının düşük olmasının
nedeninin `make`in doğru kapasiteyle çağrılması olduğunu, döngünün kendisinin hiçbir işe
yaramadığını yalnız kodu satır satır izleyerek anlayabiliyor — ayırma sayısının kendisi bu
ayrımı yapmıyor.

Bu örnek uydurma değil, gerçek kod tabanlarında görülen bir kalıbın küçültülmüş hâli: bir
geliştirici ayırma sayısını düşürmek için bir değişiklik yapıyor, sayı gerçekten düşüyor
ve değişiklik "başarılı" sayılıyor — ama sayıyı düşüren şeyin değişikliğin **hangi**
parçası olduğu hiç sorgulanmıyor. `topluOlusturKarisik` bu tuzağı somutlaştırıyor:
gereksiz döngü kalsa da, kaldırılsa da ayırma sayısı bir olarak kalıyor, çünkü sayıyı
belirleyen `make`in kapasite argümanı, döngü değil. Ölçüm aracı bu ayrımı yapmıyor; ayrımı
yapan, kodu okuyup hangi satırın hangi sayıya karşılık geldiğini izleyen kişi.

#### Özet

- Önceden yer ayırma, ayırma sayısını dörtten bire düşürdü: kapasite önceden biliniyorsa
  dilimin büyüme adımları gereksiz.
- Arabellek yeniden kullanımı, ayırma sayısını dörtten sıfıra düşürdü: paylaşılan bir
  arabelleği sıfırlayıp yeniden doldurmak, her çağrıda yeni bir ayırma gerektirmiyor.
- Değer dilimi bir ayırmada kaldı, işaretçi dilimi altmış beşe çıktı: eleman sayısı kadar
  ayrı nesne açmak, tek bir bitişik depoda tutmaktan çok daha pahalı.
- Aynı ayırma sayısına (bir) iki farklı yoldan ulaşıldı — biri doğrudan doğru kapasiteyle
  `make` çağıran okunaklı yazım, öbürü gereksiz bir hesaplama ekleyen okunaksız yazım.
- Ayırma sayısının düşmesi ya da sabit kalması tek başına bir iyileştirme kanıtı değil;
  hangi değişikliğin sayıyı etkilediği kodun kendisinden okunmalı. Ayırma sayısı bir
  **koşum** bilgisidir — üç yazımın üçü de yalnız koşturularak ayrıldı.
- Üç tekniğin ortak dayanağı aynı: bir kez yeterince büyük ayırmak, çok kez küçük küçük
  ayırmaktan daha az iş çıkarıyor; her tekniğin kendi önkoşulu (bilinen kapasite,
  paylaşılabilir arabellek, bitişik durabilen eleman) sağlanmadan uygulanamıyor.

#### Sonraki Adım

Bu ders bir işlevin **kaç kez** çağrıldığında kaç değer ayırdığını sayıyor bilgisiyle
sınırlı kaldı — bir programın yalnız bellek yönü. Ama gerçek bir programın çalışma zamanı
bundan daha fazlasını içeriyor: hangi goroutine ne zaman başladı, kaç tanesi eşzamanlı
çalıştı, hangi olay hangi sırayla oldu — bunların hiçbiri bir ayırma sayısıyla
yakalanmıyor. Sıradaki ders bu daha geniş kayda bakıyor — yürütme izinin hangi olayları
saydığı ve iki farklı kurulumun ürettiği olay sayılarının nasıl karşılaştırıldığı.

### İzleme

Source: https://academia.sh/tr/kurslar/go-uretim/izleme

Yürütme izi bir koşum bilgisidir ve burada olay sayısıyla okunuyor: aynı altı kaydı işleyen iki kurulum, biri sıralı biri üç işçiyle dağıtık, aynı sayıda kayıt işleme olayı üretiyor ama farklı sayıda işçi başlangıç olayı üretiyor. Sınırlayıcı ölçüm: iz toplamak programın davranışını değiştiriyor, bu yüzden sayılar buna göre okunuyor.

Önceki ders bir programın **kaç değer** ayırdığını sayarken durdu — bir programın yalnız
bellek yönü. Ama bir programın çalışma zamanı bundan fazlasını içeriyor: hangi goroutine
ne zaman başladı, kaç tanesi eşzamanlı çalıştı. Bu ders bu daha geniş kayda bakıyor: dilin
kendi yürütme izi, bir programın koşumu sırasında olan olayları kaydediyor ve bu ders o
kaydı **olay sayısıyla** okuyor.

Bu kursun dört dersi de aynı biçimde ilerledi: önce bir bilginin nereden geldiğini
sınıflandır (kaynak, araç, koşum), sonra o sınıfa özgü bir sınırı ölç. Profil ve kaçış
kararı iki ayrı sınıftaydı — biri koşum, biri araç. Ayırma sayısı yine koşumdu, ama
tek bir sayıya değil dört karşılaştırmaya bakıyordu. Bu son ders koşuma dönüyor, ama
sorduğu şey daha önce hiç sorulmayan bir tür: **kaç ayrı yürütme biriminin** gerçekten
çalıştığı.

Yürütme izi de bu kursun ilk dersinden beri süren ayrımın bir örneği — koşum bilgisi: kaynağa
bakan biri kaç goroutine'in gerçekten çalıştığını göremez, yalnız çalıştırıp sorabilir. Ama bu
derste okunan sayı, profil ya da ayırma sayısından farklı bir şeyi yakalıyor: bellek değil,
**eşzamanlılığın kendisi**. Bu kursun dört ölçü ekseni (M09/K03) burada kullanılmıyor; bu ders
yalnız iki kurulumun ürettiği olay sayısını karşılaştırıyor.

"İzleme" sözcüğü burada dar bir anlamda kullanılıyor: dilin kendi yürütme izi mekanizması,
bir profil ya da bir günlük kaydı değil. Profil "kaç değer ayrıldı" diye soruyordu; günlük
kaydı (bu kursta ayrıca ele alınmayan bir başka mekanizma) uygulamanın kendi yazdığı
iletileri tutuyor. Yürütme izi ikisinden de farklı: çalışma zamanının kendisinin,
programın hangi anlarda hangi yürütme birimini çalıştırdığını kaydeden bir mekanizma. Bu
ders o mekanizmanın yalnız uygulamaya özgü işaretleri kullanılan kısmını ölçüyor.

#### Neden Ham Sayı Değil, Etiketli Sayı

Bir yürütme izi yalnız programın yazdığı işleri kaydetmiyor; çalışma zamanının kendi
arka plan işlerini de kaydediyor — zamanlayıcının bir iş parçacığını nasıl dağıttığı,
çöp toplayıcının ne zaman bir tarama başlattığı gibi. Bu arka plan etkinliği makinenin o
anki yüküne, işletim sisteminin zamanlamasına bağlı ve **koşumdan koşuma değişiyor**. Bu
yüzden bir izin **toplam** olay sayısı — programın kendi yazdığı her şey dahil arka plan
etkinliği de sayılırsa — aynı kaynak kodla bile iki ayrı koşumda farklı çıkabiliyor.

Bu ders bu değişkenliği ölçmüyor, ondan **kaçınıyor**: toplam sayı yerine, kendi seçtiği
iki etiketin (`kayitBS-isle`, `iscibasBS`) kaç kez geçtiğini sayıyor. Bu etiketler
yalnız bu programın kendi kodunda geçiyor, arka plan etkinliğinde hiç görünmüyor — bu
yüzden bu iki sayı, toplam olay sayısından farklı olarak koşumdan koşuma **sabit**
kalıyor. Kursun kararlılık kalıbı burada da geçerli: ölçülen sayı önce belirlenimci
olduğu kanıtlanmadan tabloya girmiyor.

Bu seçim, önceki üç dersteki tekrarlanabilirlik arayışının doğrudan devamı: profil sayısı beş
koşumda tek değer verdiği için kullanılabilir sayıldı, ayırma sayısı da aynı sınavdan geçti.
Yürütme izi kendi başına bu sınavı geçemiyor — ham hâliyle koşumdan koşuma değişiyor — ama izin
**bir bölümü**, kendi etiketlediği bölüm, geçiyor. Bu ders bu yüzden izin tamamını değil, kararlı
çıkan bölümünü ölçüyor.

#### İzi Etiketlerle Okumak

Bir yürütme izinin ham içeriği, bu kursta basılması yasak olan her şeyi taşıyor: goroutine
numaraları, zaman damgaları, yığıt izleri. Bu ders bu içeriği hiç yazdırmıyor; onun yerine
izin içinde **kendi seçtiği** iki etiketi arıyor ve yalnız bu etiketlerin kaç kez geçtiğini
sayıyor.

```go
// iz.go — iz dosyasini olusturuyor ve icindeki bir etiketin kac kez gectigini sayiyor
package main

import (
	"context"
	"os"
	"os/exec"
	"runtime/trace"
	"strings"
)

const kayitEtiketi = "kayitBS-isle"
const iscibasEtiketi = "iscibasBS"

// izKaydet, verilen isi bir iz dosyasina kaydedip yolunu donduruyor; yol
// hicbir yerde basilmiyor, yalniz bu islevin icinde kullaniliyor.
func izKaydet(f func(ctx context.Context)) string {
	dizin, _ := os.MkdirTemp("", "iz")
	yol := dizin + "/trace.out"
	dosya, _ := os.Create(yol)
	trace.Start(dosya)
	f(context.Background())
	trace.Stop()
	dosya.Close()
	return yol
}

// olaySayisi, iz dosyasini iz aracina veriyor ve yalniz istenen etiketin kac
// kez gectigini sayiyor. Aracin urettigi metin hicbir zaman basilmiyor.
func olaySayisi(izYolu, etiket string) int {
	cmd := exec.Command("go", "tool", "trace", "-d=parsed", izYolu)
	cmd.Env = append(os.Environ(), "GOTOOLCHAIN=local")
	cikti, _ := cmd.CombinedOutput()
	return strings.Count(string(cikti), etiket)
}
```

**BS7.** `olaySayisi`, iz aracının ürettiği metni okuyor ama hiçbir zaman ekrana yazmıyor —
yalnız `strings.Count` ile bir alt dizginin kaç kez geçtiğini sayıyor ve bu sayıyı
döndürüyor. İz aracının kendi çıktısı goroutine numarası, zaman damgası ve tam dosya yolu
taşıyor; bunların hiçbiri programın herhangi bir noktasında bir değişkene bile atanmıyor,
doğrudan sayma işleminin içinde tüketilip atılıyor.

`izKaydet` de benzer bir disiplin taşıyor: iz dosyasının yolu `os.MkdirTemp` ile kurulan
geçici bir dizinde duruyor ve bu yol yalnız `olaySayisi`ye geçirilmek için kullanılıyor —
hiçbir `fmt.Println` çağrısına girmiyor. İşlev imzası da bilinçli: `f func(ctx
context.Context)` alarak, izlenecek işi çağırana bırakıyor. Bu sayede aynı `izKaydet`
işlevi hem sıralı hem havuzlu kurulumu izlemek için kullanılabiliyor — yalnız hangi
işlevin çağrıldığı değişiyor, izleme mekaniği aynı kalıyor.

#### İşçi Sayısı Neden Üç

Havuzlu kurulum üç işçi açıyor, altı kayıt işliyor — işçi sayısı kayıt sayısını tam
bölmüyor bilerek: her işçi ortalama iki kayıt işlemek zorunda kalıyor ama kanaldan
okuma sırası önceden belli değil, bu yüzden hangi işçinin kaç kayıt aldığı koşumdan
koşuma değişebiliyor. Bu ders o dağılımı hiç ölçmüyor — yalnız üç işçinin **başladığını**
sayıyor, kaçının kaç kayıt aldığını değil. Bu, kursun M09/K03'te kurduğu ve burada
kullanılmayan bir eksenle (bir goroutine'in nasıl duracağı) karışmaması için bilinçli bir
sınır: bu ders yalnız kaç yürütme biriminin **var olduğunu** ölçüyor, aralarındaki
paylaşımı değil.

#### İki Kurulum: Sıralı ve Havuzlu

Aşağıdaki ölçüm aynı altı kaydı iki ayrı yoldan işliyor. Sıralı kurulum her kaydı çağıran
goroutine'in kendisinde, sırayla işliyor. Havuzlu kurulum aynı altı kaydı üç işçi
goroutine'e dağıtıyor; her işçi başladığında bir kez etiketli bir kayıt bırakıyor, sonra
kanaldan gelen kayıtları işliyor. Her iki kurulumda da her kayıt, `trace.WithRegion` ile
aynı etiketle işaretleniyor.

```go
// olcum.go — ayni is, iki kurulum: sirali ve uc iscili havuz
package main

import (
	"context"
	"fmt"
	"os"
	"runtime/trace"
	"strconv"
	"sync"
)

type Kayit struct {
	Ad     string
	Miktar int
}

var kayitlar = []Kayit{
	{"vida", 1}, {"somun", 2}, {"pul", 3}, {"civi", 4}, {"civata", 5}, {"rondela", 6},
}

func isle(ctx context.Context, k Kayit) int {
	sonuc := 0
	trace.WithRegion(ctx, kayitEtiketi, func() {
		sonuc = k.Miktar * 2
	})
	return sonuc
}

func sirayla(ctx context.Context) int {
	toplam := 0
	for _, k := range kayitlar {
		toplam += isle(ctx, k)
	}
	return toplam
}

func havuzla(ctx context.Context, iscisi int) int {
	is := make(chan Kayit)
	var mu sync.Mutex
	toplam := 0
	var wg sync.WaitGroup
	for id := 0; id < iscisi; id++ {
		wg.Add(1)
		go func(id int) {
			defer wg.Done()
			trace.Log(ctx, iscibasEtiketi, strconv.Itoa(id))
			for k := range is {
				v := isle(ctx, k)
				mu.Lock()
				toplam += v
				mu.Unlock()
			}
		}(id)
	}
	for _, k := range kayitlar {
		is <- k
	}
	close(is)
	wg.Wait()
	return toplam
}

func main() {
	sYol := izKaydet(func(ctx context.Context) { sirayla(ctx) })
	defer os.RemoveAll(sYol)
	hYol := izKaydet(func(ctx context.Context) { havuzla(ctx, 3) })
	defer os.RemoveAll(hYol)

	fmt.Println("-- iki kurulumda uretilen olay sayisi --")
	fmt.Printf("%-10s %-22s %4d\n", "sirali", "kayit isleme olayi", olaySayisi(sYol, kayitEtiketi))
	fmt.Printf("%-10s %-22s %4d\n", "sirali", "isci baslangic olayi", olaySayisi(sYol, iscibasEtiketi))
	fmt.Printf("%-10s %-22s %4d\n", "havuzlu", "kayit isleme olayi", olaySayisi(hYol, kayitEtiketi))
	fmt.Printf("%-10s %-22s %4d\n", "havuzlu", "isci baslangic olayi", olaySayisi(hYol, iscibasEtiketi))
}
```

```
-- iki kurulumda uretilen olay sayisi --
sirali     kayit isleme olayi       12
sirali     isci baslangic olayi      0
havuzlu    kayit isleme olayi       12
havuzlu    isci baslangic olayi      3
```

**BS8.** `isle`, her iki kurulumda da aynı işlevle çağrılıyor ve her çağrı bir
`trace.WithRegion` bloğuna sarılıyor — bu, izin `kayitEtiketi` ile işaretlenmiş bir
başlangıç ve bir bitiş olayı üretmesine yol açıyor. Altı kayıt işlendiğinde bu on iki
olaya karşılık geliyor (altı başlangıç, altı bitiş) — kurulum ne olursa olsun, çünkü her
iki kurulumda da toplam altı kayıt işleniyor.

`havuzla` işlevinde her işçi, kanaldan okumaya başlamadan **önce** `trace.Log` çağrısını
yapıyor — döngünün içinde değil, döngüden hemen önce, bir kez. Bu sıralama bilinçli: eğer
etiket her kayıt için tekrar bırakılsaydı, işçi başlangıç sayısı da işlenen kayıt sayısına
bağlı çıkardı ve iki farklı bilgi (kaç işçi var, kaç kayıt işlendi) birbirine karışırdı.
Etiketi yalnız bir kez, işçi hayata geçtiğinde bırakmak, bu iki sayıyı **birbirinden
bağımsız** tutuyor.

#### Sayıların Okunması

Tablo iki farklı davranış gösteriyor. `kayit isleme olayi` her iki kurulumda da **on iki** —
sıralı ya da havuzlu, işlenen kayıt sayısı aynı olduğu için üretilen olay sayısı da aynı.
Bu satır, izin **neyi değiştirmediğini** gösteriyor: iş dağıtımının kendisi, yapılan işin
toplam miktarını değiştirmiyor.

`isci baslangic olayi` ise sıralı kurulumda **sıfır**, havuzlu kurulumda **üç**. Sıralı
kurulum hiç ek goroutine açmıyor, bu yüzden hiç işçi başlangıç olayı üretmiyor; havuzlu
kurulum üç işçi açıyor ve her biri başladığında bir kez bu etiketi bırakıyor. Bu satır,
izin **neyi gösterdiğini** açıklıyor: kaç ayrı yürütme biriminin gerçekten çalıştığı —
kaynaktaki `for id := 0; id < iscisi; id++` döngüsünün üç kez döndüğü bir insan tarafından
da sayılabilir, ama iz bunu **çalışan** programdan doğrudan okuyor, kaynağı ayrıca
incelemeye gerek kalmadan.

Bu iki satırın bir arada okunması, tek başına hiçbirinin veremeyeceği bir bilgi veriyor.
Yalnız `kayit isleme olayi` okunsaydı iki kurulumun aynı işi yaptığı görülür ama nasıl
yaptığı bilinmezdi. Yalnız `isci baslangic olayi` okunsaydı havuzlu kurulumun üç işçi
açtığı görülür ama bu üç işçinin gerçekten iş yapıp yapmadığı bilinmezdi — üçü de boşta
kalıp hiçbir kayıt işlememiş olabilirdi. İkisi birlikte üçüncü bir soruyu yanıtlıyor: üç
işçi açıldı **ve** altı kaydın tamamı gerçekten işlendi, hiçbiri kaybolmadı, hiçbiri iki
kez işlenmedi. Bu, kanaldan okuma düzeneğinin doğru çalıştığının bir kanıtı — testin
verdiği "geçti" kararından farklı bir yoldan, ama aynı türden bir güvence.

Kursun açılışı on bir bilgiyi kaynak, araç ve koşum diye ayırmıştı; bu ders o üç sınıftan
yalnız koşumu kullanıyor — hem etiketli bölgeler hem işçi başlangıçları ancak program
gerçekten çalıştırılıp izlendiğinde ortaya çıkıyor.

#### Sınırlayıcı Ölçüm: İz Toplamak Davranışı Değiştirir

Bu ders, kursun süre yasağını başka bir açıdan tekrar karşılıyor. `trace.Start` çağrısı
programın her goroutine geçişini, her kilitlenmeyi kaydetmeye başlıyor — bu kaydın kendisi
bedava değil, her olay işlenip yazılırken programın normal akışına ek bir iş biniyor. Bu
yüzden bir programı izlerken görülen davranış, izlenmeyen hâlinden **her zaman biraz
farklı**: iz toplamak programın davranışını değiştiriyor.

Bu iddia burada ölçülmüyor — ölçülmesi süreye bakmayı gerektirirdi ve süre bu kursun
hiçbir dersinde yazılmıyor. Bunun yerine bir cümleyle yazılıp gerekçelendiriliyor, tıpkı
karşılaştırmalı ölçüm dersinin süre yasağını gerekçelendirdiği gibi: iz toplamanın kendisi
bir iş ve bu iş sıfır değil. Bu yüzden yukarıdaki tablo, izlenmeyen bir programın kaç
olay ürettiğini değil, **izlenen** bir programın kaç olay ürettiğini gösteriyor — sayılar
bu çerçevede okunmalı, izleme kapalıyken de aynı davranışın süreceği varsayılmadan.

Bu sınır, önceki üç dersin sınırlarıyla aynı aileden. Profil yalnız koşan yolu görüyordu;
kaçış çözümlemesi yalnız bu derleyicinin kararını gösteriyordu; ayırma sayısı bir sayının
düşmesinin otomatik bir iyileştirme olmadığını öğretmişti. Bu ders de aynı örüntüyü
sürdürüyor: bir ölçüm aracı, ölçtüğü şeyi **ölçme eyleminin kendisiyle** birlikte
sunuyor ve okuyan taraf bu birlikteliği unutursa yanlış bir kesinlik hissine kapılıyor.
İz toplamanın davranışı değiştirmesi, iz aracının bir kusuru değil — herhangi bir gözlem
aracının, gözlemlediği sistemin bir parçası hâline gelmesinin kaçınılmaz bir sonucu.

#### Bu Ölçümün Kapsamı

Gerçek bir üretim izinin okuduğu şey bu dersteki iki etiketten çok daha geniş: bir
zamanlayıcının bir goroutine'i ne zaman durdurup başka birini çalıştırdığı, bir
goroutine'in ne kadar süre bir kilidi beklediği, bir çöp toplama turunun ne zaman
başladığı — bunların hepsi aynı izin içinde duruyor ve bir iz görüntüleyicisiyle zaman
çizelgesi olarak incelenebiliyor. Bu tür bir inceleme genelde belirli bir soruna
yanıt arıyor: neden bu istek beklenenden uzun sürdü, hangi goroutine hangi kaynağı
tıkadı. Bu sorular süre içeriyor, bu yüzden bu kursta hiç sorulmuyor.

Bu derste kurulan yöntem bunun yerine dar bir soruya cevap veriyor: belirli bir olay **kaç
kez** oldu. Bu, gerçek bir izin sunduğu bilginin küçük bir dilimi — zaman çizelgesindeki
sıralamayı, çakışmayı, bekleme süresini hiç görmüyor. Ama bu dar soru bile kaynağa
bakılarak yanıtlanamıyor: `havuzla` işlevini okuyan biri üç goroutine açıldığını görebilir,
ama bu üçünün gerçekten çalışıp çalışmadığını, gerçekten iş alıp almadığını yalnız
çalıştırıp izleyerek öğrenebilir.

#### Özet

- Yürütme izi bir koşum bilgisidir ve bu derste olay sayısıyla okundu; goroutine numarası,
  zaman damgası ve yığıt izi hiçbir yerde basılmadı.
- Aynı altı kaydı işleyen sıralı ve havuzlu iki kurulum, aynı sayıda (on iki) kayıt işleme
  olayı üretti — iş dağıtımı toplam işin miktarını değiştirmedi.
- İki kurulum farklı sayıda (sıfır, üç) işçi başlangıç olayı üretti — bu fark, kaç ayrı
  yürütme biriminin gerçekten çalıştığını izin doğrudan göstermesinden geliyor.
- İz aracının ürettiği metin hiçbir zaman basılmadı; yalnız seçilen iki etiketin kaç kez
  geçtiği sayıldı.
- İz toplamak programın davranışını değiştiriyor; bu ders bunu ölçmedi, bir cümleyle
  yazıp sayıları bu sınırla birlikte sundu.
- Yürütme izinin ham içeriği koşumdan koşuma değişiyor; bu ders bu yüzden izin tamamını
  değil, kendi etiketlediği ve kararlı çıkan bölümünü ölçtü.

#### Sonraki Adım

Bu kursun dört dersi de bir programı **koşturarak** bir şey öğrendi — profil, kaçış kararı,
ayırma sayısı, olay sayısı; hepsi ancak çalışan bir programdan okunabildi ve her biri
kendi ölçüm eyleminin programı biraz değiştirdiğini kabul etmek zorunda kaldı. Sıradaki
konu bu zeminin tamamından çıkıyor: hangi dosyanın derlemeye gireceğini, hangi platforma
çıkılacağını ve ikiliye ne gömüleceğini araç, programı hiç çalıştırmadan söylüyor —
koşumun kendisi artık gerekmiyor.

## Derleme ve Dağıtım

Derleme etiketlerinin hangi dosyaların derlemeye gireceğini belirlemesi, tek komutla başka platformlara çıktı üretilmesi, bağlayıcı seçenekleriyle sürüm bilgisinin ikiliye gömülmesi, modül yayımlamanın taşıdığı uyumluluk sözü, kural denetimi ile bilinen açık taramasının ne bulup ne bulamadığı, ve yerel kütüphane çağırmanın maliyeti.

### Derleme Etiketleri

Source: https://academia.sh/tr/kurslar/go-uretim/derleme-etiketleri

Aynı dizindeki altı kaynak dosyadan hangisinin derlemeye gireceği, çalıştırılan bir programa değil araca sorularak öğrenilir; dört etiket senaryosunda dosya sayısı bu şekilde okunur. Sınırlayıcı ölçüm, etiketiyle dışarıda kalan bir dosyanın diskten silinmediğini ama hiçbir aracın onu incelemediğini — dosyadaki bir kusurun yalnızca etiket etkinleşince görünür hale geldiğini — koşturarak gösteriyor.

Başarım ve Bellek konusunun son dersi bir izin, topladığı anda programın davranışını değiştirdiğini
ölçtü; sayılar bu yüzden hep bu etkinin altında okundu. Bu ders aynı üç sınıftan (kaynak, araç,
koşum) yalnızca birine bakıyor ve koşumu tamamen devre dışı bırakıyor: bir program hiç
çalıştırılmadan önce, hatta hiç derlenmeden önce bile, kaynak ağacındaki hangi dosyanın o
derlemeye gireceği zaten belli. Bu kararı veren şey ne bir kişinin okuması ne de çalışan bir
programın kendisi — yalnızca araç, kaynağın üstündeki bir yönergeyi okuyup dosya listesini
süzüyor. Sıradaki dört ders de aynı örüntüyü izliyor: hangi platforma çıkılacağı, ikiliye ne
gömüleceği, hangi sürümün uyumlu sayılacağı ve hangi kalıbın kusurlu bulunacağı — hepsi
koşum beklemeden, yalnızca araçtan okunuyor.

#### Derleme Etiketi Nedir

**Derleme etiketi (build tag)**, bir kaynak dosyanın en üstüne, paket bildiriminden önce
yazılan özel bir yorum satırıdır: `//go:build`. Yönergenin çalışabilmesi için üç biçim kuralı
var. Yorum dosyanın en üst satırlarından birinde durmalı; paket bildiriminden önce en az bir
boş satır bırakılmalı; ve yönerge ile dosyanın geri kalanı arasında hiçbir kod satırı
girmemeli. Bu üç kural ihlal edilirse derleyici yönergeyi sıradan bir yorum sanır ve dosya her
zaman derlemeye girer — yönergenin kendisi bir hataya değil, sessizce yok sayılmaya yol açar.

Yönergenin gövdesi küçük bir mantık ifadesidir: bir ad tek başına yazılabilir (`genisleme`),
iki ad `&&` ile birleştirilip ikisinin de aynı anda etkin olması istenebilir, `||` ile
ikisinden en az birinin etkin olması istenebilir, ya da başına konan bir değil işaretiyle bir
adın etkin **olmaması** istenebilir. Bu üç işleç herhangi bir derinlikte parantezle
birleştirilebiliyor, ama bu dersin ölçtüğü altı dosya üçünü de yalnızca bir kez kullanıyor —
mantığın kendisini karmaşıklaştırmadan üç köşesini de görmek için bu yeterli.

Bu mantığın pratikteki karşılığı genellikle iki türden bir ayrım. Birincisi, bir paketin
normalde derlenmeyen, yalnızca bir sınama ya da bir tanılama oturumunda etkinleştirilen bir
ek dosya taşıması — kaynak ağacında durur, ama gündelik derlemeyi büyütmez ya da yavaşlatmaz.
İkincisi, aynı paketin birbirini dışlayan iki gerçekleştirimi taşıması — biri varsayılan,
öteki yalnızca belirli bir etiket verildiğinde devreye giren bir değişken. İkisinde de ortak
olan şey, seçimin **derleme anında**, kaynağın kendisine hiç dokunmadan yapılabilmesi;
aynı depo, aynı dal, aynı commit, farklı `-tags` bayrağıyla farklı bir programa dönüşebiliyor.

Etiketin adları program dışından geliyor: kaynağın kendisinde hiçbir yerde tanımlanmıyorlar,
yalnızca derleme komutuna `-tags` bayrağıyla veriliyor. Bu, önceki konudaki derleme
sabitlerinden temel bir farkı işaret ediyor — kaynak içinde `const` ile yazılan bir değer
her derlemede aynı kalırken, bir derleme etiketi kaynağın **dışından**, her çağrıda ayrı ayrı
belirleniyor. Aynı kaynak ağacı, hiçbir satırı değişmeden, verilen etiket kümesine göre
tamamen farklı bir dosya kümesinden derlenebiliyor.

#### Dört Senaryo, Bir Mantık

Aşağıdaki ölçüm altı dosyalık küçük bir paket kuruyor — biri etiketsiz, beşi farklı bir
etiket ifadesiyle işaretli — ve dört ayrı etiket kümesiyle derlemeye giren dosya sayısını
okuyor. Paket gerçek bir dizine değil, `os.MkdirTemp` ile açılan geçici bir modüle yazılıyor;
bu dersin geri kalanında da yol hiçbir yerde basılmıyor.

```go
// yardimcilar.go — bu dersin paylastigi yardimcilar: gecici paket kurma ve arac cagirma
package main

import (
	"fmt"
	"os"
	"os/exec"
	"path/filepath"
	"strconv"
	"strings"
)

// goster bir bilgiyi, kimin soyledigini ve kanitini tek satirda yazdirir.
func goster(bilgi, soyleyen, kanit string) {
	fmt.Printf("%-38s %-7s %s\n", bilgi, soyleyen, kanit)
}

// paket, verilen dosyalari yeni bir gecici modulun icine yazar; yol hicbir yerde basilmaz.
func paket(dosyalar map[string]string) string {
	dizin, err := os.MkdirTemp("", "etiket")
	if err != nil {
		return ""
	}
	os.WriteFile(filepath.Join(dizin, "go.mod"), []byte("module etiketdeneme\n\ngo 1.24\n"), 0o644)
	for ad, icerik := range dosyalar {
		os.WriteFile(filepath.Join(dizin, ad), []byte(icerik), 0o644)
	}
	return dizin
}

// aracCalistir dilin kendi komutunu kosturur, cikisin basarili olup olmadigini doner; cikti basilmaz.
func aracCalistir(dizin string, arg ...string) (string, bool) {
	cmd := exec.Command("go", arg...)
	cmd.Dir = dizin
	cmd.Env = append(os.Environ(), "GOTOOLCHAIN=local")
	cikti, err := cmd.CombinedOutput()
	return string(cikti), err == nil
}

// dosyaSayisi, dizindeki .go uzantili dosyalarin toplam sayisini hicbir arac cagirmadan sayar.
func dosyaSayisi(dizin string) int {
	girdiler, _ := os.ReadDir(dizin)
	n := 0
	for _, g := range girdiler {
		if strings.HasSuffix(g.Name(), ".go") {
			n++
		}
	}
	return n
}

// goFilesSayisi, verilen etiket kumesiyle derlemeye giren dosya sayisini araçtan okur.
func goFilesSayisi(dizin, etiketler string) int {
	arg := []string{"list", "-f", "{{len .GoFiles}}", "."}
	if etiketler != "" {
		arg = []string{"list", "-tags", etiketler, "-f", "{{len .GoFiles}}", "."}
	}
	cikti, _ := aracCalistir(dizin, arg...)
	n, _ := strconv.Atoi(strings.TrimSpace(cikti))
	return n
}

// alanSayisi, "go list" ciktisindaki herhangi bir listeleme alaninin (GoFiles, TestGoFiles)
// uzunlugunu okur; hangi alanin sorulacagi sablon parametresiyle verilir.
func alanSayisi(dizin, sablon string) int {
	cikti, _ := aracCalistir(dizin, "list", "-f", sablon, ".")
	n, _ := strconv.Atoi(strings.TrimSpace(cikti))
	return n
}
```

```go
// olcum.go — hangi dosyalar derlemeye giriyor, dort etiket senaryosunda arac uzerinden okunuyor
package main

import "fmt"

func main() {
	dosyalar := map[string]string{
		"cekirdek.go": "package main\n\nfunc temelIslev() int { return 1 }\n",
		"genisleme.go": "//go:build genisleme\n\npackage main\n\nfunc genislemeIslevi() int { return 2 }\n",
		"deney.go": "//go:build deney\n\npackage main\n\nimport \"fmt\"\n\nfunc deneyIslevi() int {\n\tfmt.Printf(\"%d\\n\", \"hata\")\n\treturn 3\n}\n",
		"ikisi_birden.go": "//go:build genisleme && deney\n\npackage main\n\nfunc ikisiBirdenIslevi() int { return 4 }\n",
		"herhangi.go": "//go:build genisleme || deney\n\npackage main\n\nfunc herhangiIslevi() int { return 5 }\n",
		"hic_degil.go": "//go:build !deney\n\npackage main\n\nfunc hicDegilIslevi() int { return 6 }\n",
	}
	dizin := paket(dosyalar)
	defer func() { _ = dizin }()

	fmt.Println("-- hangi dosyalar derlemeye giriyor: dort etiket senaryosu --")
	goster("etiketsiz dosya sayisi", "arac", fmt.Sprint(goFilesSayisi(dizin, "")))
	goster("genisleme etiketiyle dosya sayisi", "arac", fmt.Sprint(goFilesSayisi(dizin, "genisleme")))
	goster("deney etiketiyle dosya sayisi", "arac", fmt.Sprint(goFilesSayisi(dizin, "deney")))
	goster("iki etiketle birden dosya sayisi", "arac", fmt.Sprint(goFilesSayisi(dizin, "genisleme,deney")))

	fmt.Println()
	fmt.Println("-- sinirlayici olcum: disarida kalan dosya silinmiyor, sinanmiyor --")
	goster("diskteki .go dosyasi sayisi", "arac", fmt.Sprint(dosyaSayisi(dizin)))
	goster("etiketsiz derlemeye giren sayi", "arac", fmt.Sprint(goFilesSayisi(dizin, "")))

	_, etiketsizTemiz := aracCalistir(dizin, "vet", "./...")
	_, deneyliTemiz := aracCalistir(dizin, "vet", "-tags", "deney", "./...")
	goster("etiketsiz cozumleme sonucu", "arac", fmt.Sprint("temiz=", etiketsizTemiz))
	goster("deney etiketiyle cozumleme sonucu", "arac", fmt.Sprint("temiz=", deneyliTemiz))
}
```

```
-- hangi dosyalar derlemeye giriyor: dort etiket senaryosu --
etiketsiz dosya sayisi                 arac    2
genisleme etiketiyle dosya sayisi      arac    4
deney etiketiyle dosya sayisi          arac    3
iki etiketle birden dosya sayisi       arac    5

-- sinirlayici olcum: disarida kalan dosya silinmiyor, sinanmiyor --
diskteki .go dosyasi sayisi            arac    6
etiketsiz derlemeye giren sayi         arac    2
etiketsiz cozumleme sonucu             arac    temiz=true
deney etiketiyle cozumleme sonucu      arac    temiz=false
```

**DR1.** Bu on satırın hiçbiri bir program çalıştırmıyor; `deneyIslevi` ve `ikisiBirdenIslevi`
gibi işlevlerin gövdesi hiç yürütülmedi. Ölçülen tek şey, dosya listesinin kendisi — bu yüzden
tablonun tamamı **araç** sınıfına yazılıyor, kursun `koşum` sütunu bu derste hiç dolmuyor.

Altı dosyanın etiketleri üç işlecin üçünü de örnekliyor: `cekirdek.go` hiç etiketsiz,
`genisleme.go` ve `deney.go` birer tek ad, `ikisi_birden.go` bir `&&`, `herhangi.go` bir
`||`, `hic_degil.go` da başına değil işareti konmuş bir `deney`. Etiketsiz çağrıda yalnızca
etiketsiz dosyayla değil-işaretli koşulu sağlayan iki dosya kalıyor — `deney` etkin olmadığı
için `hic_degil.go` da içeride. `genisleme` etiketiyle çağrıldığında hem kendi etiketli dosyası
hem `||` koşulunu sağlayan `herhangi.go` tabloya ekleniyor, ama `&&` koşulunu sağlamak için
ikinci etiket eksik olduğundan `ikisi_birden.go` hâlâ dışarıda. **DR2.** Yalnız her iki etiket
birden verildiğinde `ikisi_birden.go` tabloya giriyor ve aynı anda `hic_degil.go` çıkıyor —
çünkü artık `deney` etkin ve değil-işaretli koşul bozuluyor. Dört sayı (2, 4, 3, 5) bu yüzden
dört ayrı mantık değerlendirmesinin sonucu, dört ayrı çalıştırmanın değil.

#### Adlandırma da Bir Örtük Etiket: `_test.go`

Derleme etiketi tek mekanizma değil. Bir dosya adının sonuna eklenen `_test.go` soneki, hiçbir
`//go:build` yönergesi yazılmadan aynı soruya yanıt veriyor: bu dosya normal derlemeye girer mi?
Yanıt hayır — ama bu, dosyanın hiç derlenmediği anlamına gelmiyor; yalnızca ayrı bir listede,
`TestGoFiles` altında derleniyor. Aşağıdaki ölçüm iki dosyalık bir paket kuruyor — biri
etiketsiz, biri `_test.go` soneki taşıyan bir sınama dosyası — ve iki ayrı alanı okuyor.

```go
// adlandirma.go — _test.go soneki, hicbir yonerge yazilmadan dosyayi normal derlemeden cikariyor
package main

import "fmt"

func main() {
	dosyalar := map[string]string{
		"cekirdek.go": "package main\n\nfunc temelIslev() int { return 1 }\n",
		"cekirdek_test.go": "package main\n\nimport \"testing\"\n\nfunc TestTemelIslev(t *testing.T) {\n\tif temelIslev() != 1 {\n\t\tt.Fatal(\"beklenmeyen\")\n\t}\n}\n",
	}
	dizin := paket(dosyalar)
	defer func() { _ = dizin }()

	fmt.Println("-- adlandirma da bir ortuk etiket: _test.go --")
	goster("diskteki .go dosyasi sayisi", "arac", fmt.Sprint(dosyaSayisi(dizin)))
	goster("normal derlemeye giren dosya sayisi", "arac", fmt.Sprint(alanSayisi(dizin, "{{len .GoFiles}}")))
	goster("sinama icin derlenen dosya sayisi", "arac", fmt.Sprint(alanSayisi(dizin, "{{len .TestGoFiles}}")))
}
```

```
-- adlandirma da bir ortuk etiket: _test.go --
diskteki .go dosyasi sayisi            arac    2
normal derlemeye giren dosya sayisi    arac    1
sinama icin derlenen dosya sayisi      arac    1
```

**DR3.** İki dosya diskte duruyor ama hiçbiri diğerinin listesine karışmıyor: normal derleme
yalnızca `cekirdek.go`yu görüyor, sınama derlemesi yalnızca `cekirdek_test.go`yu. Bu ayrım bir
yönergeye değil, saf bir ada dayanıyor — dosya adının sonunda `_test.go` görmek, aracın
kendisine gömülü, kaynakta hiç yazılmayan bir kuraldır. `//go:build` yönergesi bu kuralın
üstüne, isteğe bağlı bir ikinci katman ekliyor; adlandırma sözleşmesi hep orada duruyor ve
her paket için otomatik işliyor. İki mekanizma da aynı soruyu yanıtlıyor — hangi dosya hangi
listeye giriyor — ama biri kaynakta açıkça yazılı bir yönergeye, öteki yalnızca bir ada
dayanıyor.

#### Sınırlayıcı Ölçüm: Dosya Silinmiyor, Sınanmıyor

Yukarıdaki tablonun ikinci yarısı bambaşka bir soru soruyor: etiketiyle dışarıda kalan bir
dosyaya ne oluyor? Yanıt iki parçalı ve ikisi de ölçülüyor. Birincisi: dizindeki `.go` dosyası
sayısı her zaman altı — hiçbir etiket kombinasyonu bir dosyayı diskten silmiyor, yalnızca o
çalıştırmanın derleme listesinden çıkarıyor. `dosyaSayisi` işlevi bunu doğrudan `os.ReadDir`
ile sayıyor, hiçbir `go` komutu çağırmadan; **DR4.** diskteki altı dosyaya karşılık etiketsiz
derlemeye giren yalnızca iki — aradaki dört dosya ne kaybolmuş ne bozulmuş, yalnızca o
çalıştırmanın görüş alanının dışında duruyor.

İkincisi ve daha keskin olanı: `deney.go` içine bilinçli olarak bir kusur yazıldı — `%d`
biçimlendiricisine bir dizgi veriliyor, bu tür bir uyuşmazlığı derleyici değil, Go Komut Ailesi
dersinde tanıtılan inceleyici yakalar. Etiketsiz çalıştırıldığında bu araç `temiz=true` diyor,
çünkü kusurlu dosya derleme listesinde hiç yok — araç onu okumuyor bile. **DR5.** Aynı araç
`deney` etiketiyle çağrıldığında `temiz=false`a dönüyor; kusur değişmedi, yalnızca dosyanın
görünürlüğü değişti. Bu, "etiketle dışarıda kalan dosya sınanmaz" iddiasının tam kanıtı: kusur,
etiketi etkinleştiren biri onu çalıştırana kadar hiçbir aracın erişemeyeceği bir köşede bekliyor.

Bu sınır pratikte iki yönlü çalışıyor. Bir yandan, deneysel ya da platforma özgü kod bir
etiketin arkasına yazıldığında, o kodun kalitesi ana derlemenin ve ana çözümlemenin hiçbir
zaman denetlemediği bir alanda kalıyor — etiket yalnızca hangi ortamda derleneceğini
belirliyor, o kodun doğruluğunu garanti etmiyor. Öte yandan, bu tam olarak etiketlerin var
oluş nedeni: bir kaynak ağacı, aynı anda çalışması beklenmeyen birden çok varyantı taşıyabiliyor
— ana akışın dışında kalan bir varyant, ana akışı hiç etkilemeden diskte durabiliyor. Etiketin
kararı derleme öncesinde, tek bir metin karşılaştırmasıyla veriliyor; bu yüzden hem çok ucuz
hem de çok kesin, ama sonuçları yalnızca etiketi veren tarafından görülebiliyor.

Bu iki yön arasındaki denge bir sorumluluk paylaşımına dönüşüyor. Bir dosyayı bir etiketin
arkasına yazan kişi, o dosyanın normal akışın gözünden kaybolduğunu biliyor; dolayısıyla onu
düzenli aralıklarla etiketiyle birlikte derlemek ve çözümlemek, o dosyanın kalitesini
sürdürmenin **tek** yolu haline geliyor. Etiket kendi başına hiçbir hatırlatma yapmıyor —
`deney.go` içindeki kusur, biri açıkça `-tags deney` vermediği sürece haftalarca, hatta
kaynak ağacı büyüdükçe fark edilmeden kalabilir. Bu ders bu riski somut bir sayı üzerinden
gösteriyor: altı dosyalık küçük bir örnekte fark tek bir satırdı, ama gerçek bir depoda
etiketli dosya sayısı arttıkça, etiketsiz derlemenin ve etiketsiz çözümlemenin **görmediği**
kaynak miktarı da büyüyor — üstelik bu miktarın kendisi, yine yalnızca aracın kendisine
sorularak, dosya listesi karşılaştırılarak ölçülebiliyor.

#### Özet

- Derleme etiketi, bir dosyanın derlemeye girip girmeyeceğini kaynağın dışından, `-tags`
  bayrağıyla belirleyen bir yönergedir; kararı hiçbir program çalıştırmadan **araç** verir.
- Etiket ifadeleri `&&`, `||` ve değil işareti ile kurulur; aynı altı dosyalık kaynak, verilen
  etiket kümesine göre iki ile beş arasında değişen bir dosya listesi üretebiliyor.
- Etiketiyle dışarıda kalan bir dosya diskten silinmiyor — yalnızca o çalıştırmanın derleme
  listesinin dışında kalıyor; dizindeki dosya sayısı her zaman sabit kalıyor.
- Dışarıda kalan bir dosyadaki kusur, o dosyayı hiçbir araç okumadığı için görünmüyor;
  aynı araç, etiket etkinleştirildiğinde aynı kusuru hemen buluyor.
- Bu ders `koşum` sınıfını hiç kullanmadı — bütün sonuçlar, hiçbir işlev çağrılmadan, yalnızca
  dosya listesi ve çözümleme sonucu okunarak elde edildi.

#### Sonraki Adım

Bu derste ölçülen soru "hangi dosya" ile sınırlıydı — dosyalar belli olduktan sonra hepsi aynı
tek bir hedefe, çalıştırılan makinenin kendisine derleniyordu. Sıradaki ders aynı sorunun bir
adım ötesini soruyor: aynı kaynak, hiç değişmeden, makineden tamamen farklı bir hedefe
derlenebilir mi? Yanıt yine koşum beklemeden, tek bir komutun verdiği geçti/kaldı bilgisinden
okunuyor — ama bu kez derlenmiş olmanın, hedefte çalışacağı anlamına gelmediği bir sınırla
birlikte.

### Çapraz Derleme

Source: https://academia.sh/tr/kurslar/go-uretim/capraz-derleme

Aynı taşınabilir kaynağın üç ayrı hedef için, o hedeflerin hiçbiri kurulu olmadan tek bir komutla derlenip derlenmediği ölçülür; derlenebilirlik burada da koşum gerektirmeyen bir araç bilgisidir. İkinci ölçüm aynı üç hedefte platforma bağlı bir çağrının derlenebilirliğinin değiştiğini gösterir. Sınırlayıcı ölçüm, derlenmiş olmanın hedefte çalışacağı anlamına gelmediğini — aynı adı taşıyan bir varsayımın bir hedef ailesinde geçerli, ötekinde geçersiz kalabileceğini — modelleyerek ortaya koyar.

Önceki ders, bir dizindeki hangi dosyanın derlemeye gireceğinin, hiçbir program çalıştırmadan,
yalnızca araçtan okunabildiğini gösterdi. Bu ders aynı soruyu bir adım öteye taşıyor: dosyalar
belli olduktan sonra, o dosyalar hangi hedefe derlenecek? Yanıt yine koşum beklemeden geliyor,
ama bu kez sınırı da beraberinde taşıyor — derlenmiş olmak, o hedefte çalışacağı anlamına
gelmiyor.

#### Tek Komut, Üç Hedef

**Çapraz derleme (cross compilation)**, kaynağın derlendiği makineden başka bir hedef için
ikili üretme işlemidir. Dilin kendi derleyicisi bu işi tek bir komutla yapıyor; hedefi seçen
şey iki ortam değişkeni — biri hedef işletim sistemi ailesini, öteki işlemci mimarisini
belirliyor. Bu iki değişken derleme komutunun **dışından** geliyor, tıpkı önceki dersteki
derleme etiketleri gibi; farkları, bir etiketin adı kaynağa özgüyken bu iki değişkenin adının
ve alabileceği değerlerin dilin kendisi tarafından sabitlenmiş olması. Üçüncü bir hedef
makineye ihtiyaç yok: derleyici, çalıştığı makineden bağımsız
olarak, istenen hedef için bir ikili üretiyor ve bunu üretebilip üretemediği yine **araç**
sınıfına giren bir bilgi — hiçbir şey çalıştırılmadan öğreniliyor.

Bu, önceki dersle aynı temel düzenekten besleniyor. Standart kitaplığın kendisi, birçok
işlevi her hedef ailesi için ayrı bir dosyada, o ailenin adını taşıyan bir derleme
etiketinin (ya da eşdeğer bir dosya adı sözleşmesinin) arkasında tutuyor; derleyici, hangi
hedef için çalıştığını iki ortam değişkeninden okuyup doğru dosya kümesini seçiyor. Aradaki
fark ölçek: önceki derste altı dosyalık bir örnekte elle kurulan seçim, standart kitaplığın
kendisinde binlerce dosya üzerinde, her hedef için ayrı ayrı çalışıyor. Bu yüzden bir hedefin
"kurulu" olması gerekmiyor — gereken tek şey, derleyicinin o hedef için hangi kaynak
dosyalarının geçerli olduğunu bilmesi ve bu bilgi de kaynağın kendisinde, dosya adlarında ve
etiketlerinde zaten duruyor.

Aşağıdaki ölçüm taşınabilir, hiçbir platforma özgü çağrı içermeyen bir kaynağı üç ayrı hedef
için derliyor ve her denemenin geçip geçmediğini okuyor. Üretilen ikili hiçbir yere
yazılmıyor — çıktı sistemin boş aygıtına yönlendiriliyor, çünkü bu derste ölçülen tek şey
derlemenin başarısı, üretilen dosyanın kendisi değil.

```go
// yardimcilar.go — bu dersin paylastigi yardimcilar: gecici paket kurma ve arac cagirma
package main

import (
	"fmt"
	"os"
	"os/exec"
	"path/filepath"
)

// goster bir bilgiyi, kimin soyledigini ve kanitini tek satirda yazdirir.
func goster(bilgi, soyleyen, kanit string) {
	fmt.Printf("%-42s %-7s %s\n", bilgi, soyleyen, kanit)
}

// paket, verilen dosyalari yeni bir gecici modulun icine yazar; yol hicbir yerde basilmaz.
func paket(dosyalar map[string]string) string {
	dizin, err := os.MkdirTemp("", "capraz")
	if err != nil {
		return ""
	}
	os.WriteFile(filepath.Join(dizin, "go.mod"), []byte("module capraz\n\ngo 1.24\n"), 0o644)
	for ad, icerik := range dosyalar {
		os.WriteFile(filepath.Join(dizin, ad), []byte(icerik), 0o644)
	}
	return dizin
}

// derlenebilirMi, kaynagi verilen hedef icin derlemeyi dener; cikti hicbir zaman basilmaz,
// yalniz basarili olup olmadigi donuyor. Uretilen ikili sistemin bos aygitina yaziliyor.
func derlenebilirMi(dizin string, ekOrtam []string) bool {
	cmd := exec.Command("go", "build", "-o", os.DevNull, ".")
	cmd.Dir = dizin
	cmd.Env = append(append(os.Environ(), "GOTOOLCHAIN=local", "CGO_ENABLED=0"), ekOrtam...)
	return cmd.Run() == nil
}
```

```go
// ucHedef.go — tasinabilir bir kaynak uc ayri hedef icin, hicbir hedef makine gerekmeden derleniyor
package main

import "fmt"

func main() {
	dizin := paket(map[string]string{
		"tasinabilir.go": "package main\n\nfunc topla(a, b int) int { return a + b }\n\nfunc main() {\n\t_ = topla(1, 2)\n}\n",
	})
	defer func() { _ = dizin }()

	hedefler := []struct{ ad, goos, goarch string }{
		{"birinci hedef", "linux", "amd64"},
		{"ikinci hedef", "windows", "amd64"},
		{"ucuncu hedef", "darwin", "arm64"},
	}

	fmt.Println("-- tek komut, uc hedef: tasinabilir kaynak --")
	for _, h := range hedefler {
		ortam := []string{"GOOS=" + h.goos, "GOARCH=" + h.goarch}
		goster(h.ad+" derlendi mi", "arac", fmt.Sprint(derlenebilirMi(dizin, ortam)))
	}
}
```

```
-- tek komut, uc hedef: tasinabilir kaynak --
birinci hedef derlendi mi                  arac    true
ikinci hedef derlendi mi                   arac    true
ucuncu hedef derlendi mi                   arac    true
```

**DR6.** Üç ayrı hedefin üçü de derlendi ve bu makinede üç hedefin de gerçek bir kopyası hiç
yoktu — çalıştırılan tek şey derleyicinin kendisi, iki ortam değişkeniyle üç kez çağrıldı.
Sonuç yine `koşum` sütununu hiç doldurmuyor: taşınabilir bir kaynağın hangi hedefler için
derlendiği, o hedeflerin hiçbirine dokunmadan, tek bir komutun geçti/kaldı yanıtından
okunuyor.

#### Aynı Çağrı, Değişen Sonuç

Yukarıdaki kaynak platforma özgü hiçbir şey içermiyordu, bu yüzden üç hedefin üçü de aynı
yanıtı verdi. Bir çağrı platforma özgü bir sistem işlevine bağlandığında bu tekdüzelik
bozuluyor — bazı işletim sistemi aileleri o işlevi tanımlarken, bazıları hiç tanımlamıyor.
Aşağıdaki ölçüm aynı üç hedefte, standart kitaplığın belirli bir işletim sistemi ailesine özgü
tek bir sistem çağrısını (adlandırılmış boru oluşturma çağrısı) kullanan bir kaynağı deniyor.

```go
// sistemCagrisi.go — ayni cagri, uc hedef: platforma bagli bir sistem cagrisi
package main

import "fmt"

func main() {
	dizin := paket(map[string]string{
		"cagrili.go": "package main\n\nimport \"syscall\"\n\nfunc main() {\n\t_ = syscall.Mkfifo\n}\n",
	})
	defer func() { _ = dizin }()

	hedefler := []struct{ ad, goos, goarch string }{
		{"birinci hedef", "linux", "amd64"},
		{"ikinci hedef", "windows", "amd64"},
		{"ucuncu hedef", "darwin", "arm64"},
	}

	fmt.Println("-- ayni cagri, uc hedef: platforma bagli bir sistem cagrisi --")
	basarili := 0
	for _, h := range hedefler {
		ortam := []string{"GOOS=" + h.goos, "GOARCH=" + h.goarch}
		d := derlenebilirMi(dizin, ortam)
		if d {
			basarili++
		}
		goster(h.ad+" derlendi mi", "arac", fmt.Sprint(d))
	}
	goster("uc hedeften kaci derlendi", "arac", fmt.Sprint(basarili))
}
```

```
-- ayni cagri, uc hedef: platforma bagli bir sistem cagrisi --
birinci hedef derlendi mi                  arac    true
ikinci hedef derlendi mi                   arac    false
ucuncu hedef derlendi mi                   arac    true
uc hedeften kaci derlendi                  arac    2
```

**DR7.** Aynı kaynak, aynı üç hedef, ama bu kez sonuç tekdüze değil: iki hedef derliyor, biri
derlemiyor. Standart kitaplığın sistem çağrısı paketi her işletim sistemi ailesi için ayrı bir
gerçekleştirim taşıyor ve bir ailede hiç bulunmayan bir işlevi çağırmak, o aile hedeflenince
derlemeyi durduruyor — üstelik bu, önceki derste ölçülen "hangi dosya derlemeye giriyor"
sorusuyla aynı katmanda değil, bir düzey daha derinde: dosyanın kendisi her üç hedefte de
derleme listesine giriyor, ama dosyanın içindeki bir ad üç hedefin ikisinde tanımlı, birinde
tanımsız kalıyor. Hangi ailenin hangi çağrıyı tanımladığı da yine **araç** bilgisi: bir kişi
bunu ezbere bilebilir, ama derlemenin kendisi bunu her seferinde aynı şekilde, koşum
gerektirmeden doğruluyor.

Bu farkın pratikteki sonucu, birden çok hedefi aynı anda desteklemesi gereken bir kaynağın
iki yoldan biriyle yazılması gerektiği. Ya platforma özgü çağrı hiç kullanılmıyor — önceki
bölümdeki taşınabilir kaynak gibi, üç hedefin üçünde de aynı davranışı veriyor — ya da
platforma özgü kısım kendi dosyasına, kendi etiketine ayrılıyor ve her hedef için ayrı bir
gerçekleştirim yazılıyor. İkinci yol, önceki dersteki `genisleme`/`deney` örneğiyle aynı
biçimi taşıyor: dosya adı ya da etiket, hangi hedefte hangi gerçekleştirimin devreye
gireceğini belirliyor. Fark, seçimin bu kez elle verilen bir bayrakla değil, derleme
komutunun kendi hedef bilgisiyle otomatik yapılması. Tek bir dosyanın üç hedefin ikisinde
derlenip birinde derlenmemesi bu yüzden bir kusur değil — kaynağın, o çağrıyı desteklemeyen
hedefler için hiç bir alternatif dosya sağlamadığının bir işareti.

#### Sınırlayıcı Ölçüm: Derlenmek Çalışmak Değildir

Yukarıdaki iki ölçüm de aynı sınırın içinde kaldı: bir kaynağın belirli bir hedef için
derlenip derlenmediği. Bu, o kaynağın o hedefte **çalışıp** çalışmayacağından ayrı bir soru
ve bu ders o ikinci soruyu gerçekten yanıtlayamıyor — üç hedeften ikisinin gerçek bir kopyası
bu makinede yok, üretilen ikili çalıştırılamıyor. Bu sınırı gizlemek yerine açıkça modelliyoruz:
aşağıdaki ölçüm gerçek bir hedefte hiç çalıştırılmıyor, iki hedef ailesinin ortamı kaynak
içinde elle kuruluyor.

```go
// varsayimModeli.go — sinirlayici olcum: derlenmek calismak degildir, model uzerinden gosteriliyor
package main

import "fmt"

// evYoluBul, verilen ortam haritasinda TEK bir degisken adina bakarak ev dizinini arar.
func evYoluBul(ortam map[string]string, degiskenAdi string) (string, bool) {
	deger, varMi := ortam[degiskenAdi]
	return deger, varMi
}

func main() {
	// iki hedef ailesinin ortami burada gercekten calistirilmadan, elle modelleniyor:
	// her aile kullanicinin ev dizinini kendi adiyla tasiyan bir degisken sunuyor.
	birinciAileOrtami := map[string]string{"HOME": "kullanici-dizini"}
	ikinciAileOrtami := map[string]string{"USERPROFILE": "kullanici-dizini"}

	// tasinabilir goruntusune ragmen, kod yalnizca TEK bir degisken adini ariyor.
	arananDegisken := "HOME"

	_, birinciBuldu := evYoluBul(birinciAileOrtami, arananDegisken)
	_, ikinciBuldu := evYoluBul(ikinciAileOrtami, arananDegisken)

	fmt.Println("-- sinirlayici olcum: derlenmek calismak degildir (model) --")
	goster("birinci hedef ailesinde ev yolu bulundu mu", "arac", fmt.Sprint(birinciBuldu))
	goster("ikinci hedef ailesinde ev yolu bulundu mu", "arac", fmt.Sprint(ikinciBuldu))
}
```

```
-- sinirlayici olcum: derlenmek calismak degildir (model) --
birinci hedef ailesinde ev yolu bulundu mu arac    true
ikinci hedef ailesinde ev yolu bulundu mu  arac    false
```

**DR8.** `evYoluBul` işlevi platforma özgü hiçbir çağrı içermiyor — tek bir dizgi anahtarıyla
bir haritaya bakıyor — bu yüzden yukarıdaki gibi üç gerçek hedef için derlenseydi üçünde de
sorunsuz derlenirdi. Kusur derlemede değil, **varsayımda**: kod yalnızca tek bir değişken
adını arıyor ve bu ad birinci hedef ailesinde geçerli, ikincisinde geçersiz. Gerçek bir
programda bu satır, bir kullanıcının ayar dizinini bulmaya çalışan bir çağrı olabilir; ikinci
hedef ailesinde çalıştırıldığında sessizce boş dönebilir, hiçbir derleme hatası, hiçbir
çalışma zamanı çökmesi olmadan. Bu yüzden "derlendi=true" satırı, önceki iki ölçümde olduğu
gibi burada da güvenilir bir bilgi — ama neyin doğru olduğunu söylüyor, neyin **iyi çalıştığını**
değil. Bu ayrım modellenmeden ölçülemiyor, çünkü koşumun kendisi bu derste hiçbir zaman gerçek
bir hedefte gerçekleşmiyor.

Modelin kendisi kasıtlı olarak küçük tutuldu: iki harita, bir dizgi anahtarı, iki arama.
Gerçek bir programda bu tür bir varsayım genellikle çok daha derine gömülü olur — bir yapılandırma
dosyasının aranacağı dizin, bir günlük dosyasının yazılacağı yer, bir geçici dosyanın adı — ve
hepsi aynı örüntüyü paylaşır: kod bir tek adı, bir tek yolu ya da bir tek biçimi doğru kabul
eder, bu kabul üç hedefin de derlemesini geçer ve yalnızca yanlış olduğu hedefte, o hedef
gerçekten çalıştırıldığında ortaya çıkar. Derlenebilirlik ölçümünün gücü tam burada bitiyor:
söylediği şey doğru ve güvenilir, ama sorduğu soru dar — yalnızca "bu kaynak bu hedef için bir
ikiliye dönüşür mü" sorusuna yanıt veriyor, "bu ikili doğru şeyi yapar mı" sorusuna değil.

Bu sınırın pratikte kapatılma yolu, yine aynı üç sınıfın içinde kalıyor: bir hedefte
gerçekten çalıştığını göstermenin tek güvenilir yolu, o hedefte gerçekten **koşturmak** —
gerçek bir makinede ya da o hedefi taklit eden bir ortamda. Derlenebilirlik sınaması bu
adımın yerini almıyor, önüne geçiyor: bir kaynak üç hedefin birinde bile derlenmiyorsa, o
hedefte hiç koşum denemeye gerek kalmıyor. Üç hedefin üçünde de derlenen bir kaynak için ise
soru hâlâ açık — derlenebilirlik testi bu açık soruyu **erken**, ucuz bir adımda eliyor, ama
kapatmıyor. Bu derste ikinci adım (gerçek koşum) hiç yapılmadı, çünkü üç hedeften ikisinin bu
makinede gerçek bir kopyası yok; bu, ölçümün kapsamının bir sınırı, çapraz derlemenin
kendisinin bir sınırı değil.

Aynı sınır, çapraz derlemenin kendi sınırlarından biri için de geçerli: kaynak `cgo` üzerinden
bir yerel kütüphaneye bağlanıyorsa, derleyici genellikle hedef için gerekli dış derleyiciyi
bulamıyor ve derleme bu kez gerçekten reddediliyor — bu, kursun son dersinin konusu. Bu derste
ölçülen üç hedefin üçü de saf kaynaktı; bir dış derleyici hiç gerekmedi ve bu yüzden hiçbirinde
reddedilme yaşanmadı.

#### Özet

- Çapraz derleme, hedef bir sistem çevresi hiç kurulu olmadan, tek bir komutla o hedef için
  ikili üretme işlemidir; derlenebilirlik burada da koşum gerektirmeyen bir **araç** bilgisi.
- Taşınabilir bir kaynak üç hedefin üçünde de aynı yanıtı verdi; platforma bağlı tek bir
  sistem çağrısı eklendiğinde aynı üç hedeften yalnızca ikisi derlendi.
- Bir çağrının hangi hedef ailesinde tanımlı olduğu da araçtan okunan bir bilgi — derleme,
  bu bilgiyi her denemede aynı şekilde, hiçbir kişisel bilgiye ihtiyaç duymadan doğruluyor.
- Sınırlayıcı ölçüm: derlenmiş olmak hedefte doğru çalışacağı anlamına gelmiyor; aynı ada
  bağlı bir varsayım bir hedef ailesinde geçerli, ötekinde geçersiz kalabiliyor.
- Bu sınır gerçek bir hedefte koşturularak değil, iki hedefin ortamı kaynak içinde açıkça
  modellenerek gösterildi; kursun hiçbir yerinde gerçek bir hedef makineye bağlanılmadı.

#### Sonraki Adım

Bu derste hedef, dosyanın kendisinin hiç değişmediği bir seçimdi — kaynak sabitti, yalnızca
hangi ortama derlendiği değişti. Sıradaki ders kaynağın içine hiç yazılmayan, ama yine de
ikiliye giren bir değeri ele alıyor: bir sürüm bilgisi, kaynağın hiçbir satırında durmadan,
yalnızca derleme anında bir başka araç tarafından ikiliye yerleştiriliyor. Bu kez sorulan soru
"hangi hedef" değil, "kaynakta hiç yazılmayan bir değer nereden geliyor" — ve bu derste
seçilen hedefin ikiliye hiçbir iz bırakmadığı gibi, o değer de kaynağın kendisinde hiçbir iz
bırakmıyor; ikisi de yalnızca derleme sürecinin kendi araçlarıyla, kaynağın dışından ikiliye
ekleniyor.

### Bağlayıcı Seçenekleri

Source: https://academia.sh/tr/kurslar/go-uretim/baglayici-secenekleri

Kaynakta hiçbir yerde yazılı olmayan bir dizgi değerinin, üç ayrı derleme çağrısında üç ayrı biçimde ikiliye gömüldüğü ölçülür; kaynak metin üçünde de birebir aynı kalır. Sınırlayıcı ölçüm, gömülen değerin bağlayıcı tarafından hiç doğrulanmadığını — biçimsiz bir dizginin de sessizce kabul edildiğini — ve ayrı bir bağlayıcı seçeneğinin sembol tablosunu silerken programın çıktısını değiştirmediğini koşturarak gösteriyor.

Önceki derste seçilen hedef ikiliye hiçbir iz bırakmadan, yalnızca derleme sürecinin kendi
araçlarıyla belirleniyordu. Bu ders aynı örüntüyü, bu kez bir dizin ya da bir mimari yerine
tek bir dizgi değeri için sürdürüyor: kaynakta hiçbir yerde yazılı olmayan bir değer, yine
derleme anında, yine kaynağın dışından ikiliye giriyor. Bu kez soruyu soran taraf derleyici
değil **bağlayıcı** — kaynağın derlendiği son adımda çalışan, ayrı derlenmiş parçaları tek bir
ikilide birleştiren araç.

#### Kaynakta Olmayan Bir Değer

Bir paket düzeyi değişken, kaynakta bir varsayılan değerle bildirilebilir ve bu değer
**bağlayıcı seçeneği (linker flag)** ile derleme anında değiştirilebilir. Aşağıdaki ölçüm
tek bir kaynağı — bir tek `surum` değişkeni ve onu yazdıran bir `main` işlevi — üç ayrı
derleme çağrısıyla çalıştırıyor. Kaynak metin üçünde de birebir aynı; değişen tek şey,
komuta verilen bir bayrak.

Bayrağın kendisi bir eşitlik ifadesi taşıyor: değişkenin **tam paket yolu**, bir nokta, adı,
sonra istenen değer. Bağlayıcı, ayrı ayrı derlenmiş paket parçalarını tek bir ikilide
birleştirirken, her paketin dışa açtığı adları zaten bir tabloda tutuyor — bu tablo normalde
paketler arasındaki çağrıları birbirine bağlamak için var. `-X` seçeneği bu aynı tabloyu bir
başka amaçla kullanıyor: adı tabloda bulup, kaynaktaki ilk değerin yerine verilen dizgiyi
yerleştiriyor. Bu yüzden hedef adın **tam** ve **doğru** yazılması gerekiyor — paket yolu
yanlışsa ya da adın kendisi yanlış yazılmışsa, bağlayıcı genellikle hiçbir eşleşme bulamıyor
ve bayrağı sessizce yok sayıyor; kaynaktaki varsayılan değer olduğu gibi kalıyor.

```go
// yardimcilar.go — bu dersin paylastigi yardimcilar: gecici paket kurma ve arac cagirma
package main

import (
	"fmt"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
)

// goster bir bilgiyi, kimin soyledigini ve kanitini tek satirda yazdirir.
func goster(bilgi, soyleyen, kanit string) {
	fmt.Printf("%-38s %-7s %s\n", bilgi, soyleyen, kanit)
}

// paket, verilen dosyalari yeni bir gecici modulun icine yazar; yol hicbir yerde basilmaz.
func paket(dosyalar map[string]string) string {
	dizin, err := os.MkdirTemp("", "baglayici")
	if err != nil {
		return ""
	}
	os.WriteFile(filepath.Join(dizin, "go.mod"), []byte("module baglayicideneme\n\ngo 1.24\n"), 0o644)
	for ad, icerik := range dosyalar {
		os.WriteFile(filepath.Join(dizin, ad), []byte(icerik), 0o644)
	}
	return dizin
}

// aracCalistir dilin kendi komutunu kosturur, ciktiyi ve basari durumunu doner.
func aracCalistir(dizin string, arg ...string) (string, bool) {
	cmd := exec.Command("go", arg...)
	cmd.Dir = dizin
	cmd.Env = append(os.Environ(), "GOTOOLCHAIN=local")
	cikti, err := cmd.CombinedOutput()
	return string(cikti), err == nil
}

// calistir, kaynagi verilen baglayici degeriyle calistirir ve yazdirdigi tek satiri doner.
// uygula false ise hicbir -X bayragi verilmez; true ise deger bos dizgi bile olsa uygulanir.
func calistir(dizin string, uygula bool, deger string) string {
	arg := []string{"run", "."}
	if uygula {
		arg = []string{"run", "-ldflags=-X main.surum=" + deger, "."}
	}
	cikti, _ := aracCalistir(dizin, arg...)
	return strings.TrimRight(cikti, "\n")
}

// sembolVarMi, verilen ek baglayici bayragiyla derlenen ikilide bir sembol adinin
// bulunup bulunmadigini soyler; ikilinin kendisi ve yolu hicbir yerde basilmaz.
func sembolVarMi(dizin, ekBayrak, sembolAdi string) bool {
	ikiliYolu := filepath.Join(dizin, "ikili")
	arg := []string{"build", "-o", ikiliYolu}
	if ekBayrak != "" {
		arg = append(arg, "-ldflags="+ekBayrak)
	}
	arg = append(arg, ".")
	if _, tamam := aracCalistir(dizin, arg...); !tamam {
		return false
	}
	cikti, _ := aracCalistir(dizin, "tool", "nm", ikiliYolu)
	return strings.Contains(cikti, sembolAdi)
}
```

```go
// olcum1.go — kaynakta yazili olmayan bir deger, ayni kaynaktan uc kez farkli okunuyor
package main

import "fmt"

const kaynakMetni = "package main\n\nimport \"fmt\"\n\nvar surum = \"gelistirme\"\n\nfunc main() {\n\tfmt.Println(surum)\n}\n"

func main() {
	dizin := paket(map[string]string{"prog.go": kaynakMetni})
	defer func() { _ = dizin }()

	fmt.Println("-- ayni kaynak, uc farkli baglayici girdisi --")
	goster("hicbir deger verilmeden", "arac", calistir(dizin, false, ""))
	goster("birinci deger verildiginde", "arac", calistir(dizin, true, "surum-a"))
	goster("ikinci deger verildiginde", "arac", calistir(dizin, true, "surum-b"))
}
```

```
-- ayni kaynak, uc farkli baglayici girdisi --
hicbir deger verilmeden                arac    gelistirme
birinci deger verildiginde             arac    surum-a
ikinci deger verildiginde              arac    surum-b
```

**DR9.** `kaynakMetni` sabiti üç çalıştırmanın üçünde de karakter karakter aynı; değişen tek
şey `calistir` işlevine verilen bayrak. Kaynakta yazılı olan tek değer `"gelistirme"` — bu,
hiçbir bayrak verilmediğinde görünen değer, yani **kaynak** sınıfına giren tek satır.
Öteki iki satır kaynakta hiç yok; bağlayıcı, derleme sırasında paket düzeyi bir dizgi
değişkeninin ilk değerini, kaynağı hiç değiştirmeden, dışarıdan verilen bir dizgiyle
değiştiriyor. Bu üç satırın hepsi **araç** sınıfına yazılıyor, çünkü değer her çağrıda aynı
bayrakla aynı sonucu veriyor — koşumdan koşuma değişen bir şey yok, değişen yalnızca hangi
bayrağın verildiği.

Bu düzeneğin çalışabilmesi için iki koşul birden gerekiyor. Değişken paket düzeyinde
olmalı — bir işlevin içindeki yerel bir değişken bağlayıcının erişemeyeceği bir yerde duruyor.
Ve değişkenin tipi dizgi olmalı; bağlayıcı yalnızca dizgi değerlerini bu şekilde
değiştirebiliyor, bir tam sayıyı ya da bir yapının alanını aynı yoldan değiştiremiyor. Bu iki
kısıt, düzeneği kasıtlı olarak dar tutuyor: karmaşık bir veri yapısını değil, tek bir metin
parçasını derleme anında yerleştirmek için var.

Bu darlık bir eksiklik değil, bir tasarım kararı. Bağlayıcı, kaynağı hiç ayrıştırmadan,
yalnızca derlenmiş nesne dosyalarının içindeki ikili temsille çalışıyor — bir dizgi
değişkeninin ikili temsili, uzunluğu ve baytları önceden bilinen bir bellek bloğu, bu yüzden
bağlayıcının yapması gereken tek şey o bloğun içeriğini değiştirmek. Bir tam sayı ya da bir
yapı için aynı işlem çok daha kırılgan olurdu: değerin tipini ve büyüklüğünü bilmeden rastgele
bir baytı yerine koymak, ikilinin geri kalanını bozma riski taşırdı. Dizgi tipi bu riski
taşımıyor, çünkü Go'nun dizgi temsili zaten bir uzunluk alanıyla birlikte tutuluyor ve
bağlayıcı yeni değerin uzunluğunu da bu alana yazabiliyor.

#### Sınırlayıcı Ölçüm: Gömülen Değer Doğrulanmıyor

Yukarıdaki üç değer de biçimli, okunaklı dizgilerdi. Bağlayıcının kendisi bu biçime hiç
bakmıyor — verilen her şeyi olduğu gibi yerleştiriyor. Aşağıdaki ölçüm bunu iki uçla
sınıyor: bir yandan hiçbir tanınabilir kalıba uymayan bir dizgi, öte yandan tümüyle boş bir
dizgi.

```go
// olcum2.go — sinirlayici olcum: gomulen deger dogrulanmiyor, sembol tablosu ayri bir sey
package main

import "fmt"

const kaynakMetni = "package main\n\nimport \"fmt\"\n\nvar surum = \"gelistirme\"\n\nfunc main() {\n\tfmt.Println(surum)\n}\n"

func main() {
	dizin := paket(map[string]string{"prog.go": kaynakMetni})
	defer func() { _ = dizin }()

	fmt.Println("-- sinirlayici olcum: gomulen deger dogrulanmiyor --")
	goster("bicimsiz bir deger verildiginde", "arac", calistir(dizin, true, "###GECERSIZ-SURUM-999"))
	goster("bos dizgi verildiginde", "arac", "["+calistir(dizin, true, "")+"]")

	fmt.Println()
	fmt.Println("-- ikinci baglayici secenegi: sembol tablosunu cikarmak --")
	goster("normal derlemede sembol var mi", "arac", fmt.Sprint(sembolVarMi(dizin, "", "main.surum")))
	goster("-s ile derlemede sembol var mi", "arac", fmt.Sprint(sembolVarMi(dizin, "-s -w", "main.surum")))
	goster("-s ile davranis degisiyor mu", "arac", "cikti="+calistir(dizin, false, ""))
}
```

```
-- sinirlayici olcum: gomulen deger dogrulanmiyor --
bicimsiz bir deger verildiginde        arac    ###GECERSIZ-SURUM-999
bos dizgi verildiginde                 arac    []

-- ikinci baglayici secenegi: sembol tablosunu cikarmak --
normal derlemede sembol var mi         arac    true
-s ile derlemede sembol var mi         arac    false
-s ile davranis degisiyor mu           arac    cikti=gelistirme
```

**DR10.** Birinci satır, bir sürüm numarası gibi görünmeyen bir dizgiyi hiçbir uyarı, hiçbir
red olmadan kabul etti; ikinci satır tümüyle boş bir dizgiyi de aynı şekilde kabul etti —
köşeli parantezlerin arası boş çünkü değişkenin kendisi boş bir dizgiye eşitlendi. Bağlayıcı
burada bir tür denetimi ya da bir biçim denetimi yapmıyor; yaptığı iş, verilen bayttan
diziyi kaynak koddaki ilk değerin yerine koymaktan ibaret. Bu, önceki dersteki "derlenmek
çalışmak değildir" sınırının bir başka görünümü: bağlayıcı geçtiyse, gömülen değerin
**anlamlı** olduğu hiç garanti edilmiyor — yalnızca doğru yere, doğru tipte yerleştirildiği
garanti ediliyor.

Bu boşluğu kapatmanın tek yolu, gömülen değeri kaynağın dışında, ayrı bir adımda denetlemek —
bağlayıcının kendisi bu işi hiçbir zaman üstlenmiyor. Bir dağıtım sürecinde bu genellikle
gömülecek dizginin, çağıran tarafından önceden bilinen bir kalıba uyup uymadığının, bağlayıcı
çağrılmadan **önce** sınanması biçiminde çözülüyor — tıpkı bu dersin ölçtüğü gibi, sınama
bağlayıcının kendisinden değil, onu çağıran sürecin kendi disiplininden geliyor. Bu ders o
disiplini uygulamadı, kasıtlı olarak: amaç bağlayıcının kendi davranışını, çevresindeki hiçbir
denetim katmanı olmadan çıplak haliyle görmekti.

**DR11.** İkinci ölçüm ayrı bir bağlayıcı seçeneğine bakıyor: sembol tablosunu ve hata ayıklama
bilgisini kaldıran bayrak. Normal derlemede `main.surum` adı ikilinin sembol tablosunda
bulunuyor — bu ad, bir hata ayıklayıcının ya da bir çözümleme aracının değişkeni tanımasını
sağlıyor. Aynı bayrak eklendiğinde sembol tablosundan bu ad siliniyor, ama programın kendi
çıktısı — son satırdaki `calistir` çağrısı — hiç değişmiyor. Bu, iki farklı bağlayıcı
seçeneğinin iki farklı katmanda çalıştığını gösteriyor: biri **programın verisini**
değiştiriyor (`-X`), öteki yalnızca **programın hakkındaki bilgiyi** siliyor (`-s`) — ikisi
birlikte kullanılabiliyor ve biri ötekinin sonucunu hiç etkilemiyor.

Bu ayrım rastgele değil: `-s` bayrağının kaldırdığı sembol tablosu ve hata ayıklama bilgisi,
programın kendi mantığının **hiçbir parçası değil** — bir hata ayıklayıcının değişken adlarını,
işlev adlarını ve satır numaralarını okuyabilmesi için ikiliye eklenen, ama çalışma zamanında
programın kendisi tarafından hiç kullanılmayan ek veriler. `-X` ise tam tersi bir yere,
programın kendi çalışma zamanı verisinin içine yazıyor. İki seçenek aynı komutta birlikte
verildiğinde bile aralarında bir sıralama sorunu çıkmıyor, çünkü sembol tablosu **derleme
sürecinin sonunda**, bütün değerler zaten yerlerine oturmuşken siliniyor — silinen şey adların
kendisi, adların işaret ettiği veriler değil.

#### Kaynakta Bir İz Bırakmadan

İki ölçümün ortak noktası, bağlayıcının çalıştığı katmanın kaynağın kendisinden ayrı olması.
Kaynak dosyası hem `surum-a` hem `surum-b` hem `###GECERSIZ-SURUM-999` çalıştırmalarında
birebir aynı kaldı; hiçbiri diskte, kaynağın hiçbir versiyonunda görünmüyor. Bu, önceki
dersin sonunda işaret edilen paralelliği tamamlıyor: hedef seçimi ikiliye nasıl hiçbir iz
bırakmadan giriyorsa, gömülen bir dizgi de kaynağa hiçbir iz bırakmadan giriyor. İkisi de
derleme sürecinin kendi araçlarıyla, derleme **girdisinin** bir parçası olarak
uygulanıyor — kaynağın **çıktısının** değil.

Bunun pratik sonucu, aynı kaynak ağacından üretilen her ikilinin, kendine özgü bir "kimlik"
taşıyabilmesi: bir sürüm dizgisi, bir yapı zaman damgası, bir dağıtım kimliği gibi değerler
kaynağı hiç değiştirmeden, yalnızca derleme çağrısı değiştirilerek her ikiliye ayrı ayrı
verilebiliyor. Bedeli de aynı yerde duruyor: bu değerlerin doğruluğu kaynak denetiminin dışında
kalıyor, bir bakan-denetleyen kişi ya da süreç olmadan yanlış bir değer sessizce ikiliye
girebiliyor. Bir sonraki derste kaynakta **gerçekten yazılan** bir sürüm bilgisiyle
karşılaştırıldığında bu fark daha keskin görünüyor: orada sürüm numarası kaynağın kendisinde
durur ve bir araç yalnızca onu **denetler**, burada sürüm benzeri değer kaynakta hiç durmaz ve
hiçbir araç onu denetlemez.

Bu iki uç arasındaki seçim genellikle şu soruya bağlı: değer, kaynağın **kimliğinin** bir
parçası mı, yoksa kaynağın **hangi anda, hangi ortamda** derlendiğinin bir parçası mı? Bir
kütüphanenin taşıdığı bir sürüm numarası birincisine örnek — o değer kaynakla birlikte
sürüyor, kaynak değişmeden değişmiyor, bu yüzden kaynakta yazılı olması daha tutarlı bir
kayıt bırakıyor. Bir yapı kimliği ya da bir derleme ortamının bir bilgisi ikincisine örnek —
bu değer kaynaktan değil, o **anki** derleme çağrısından geliyor ve her derlemede farklı
olması beklenebiliyor; kaynağa yazmak bu durumda kaynağı, aslında ona ait olmayan bir bilgiyle
kirletmek anlamına geliyor. Bağlayıcı seçeneği tam bu ikinci durumu, kaynağı hiç dokunmadan
karşılıyor.

#### Özet

- Paket düzeyinde bildirilen bir dizgi değişkeni, kaynak hiç değişmeden, bağlayıcı seçeneğiyle
  derleme anında farklı bir değerle doldurulabiliyor; bu değer kaynakta hiçbir zaman yazılı
  değil.
- Aynı kaynak üç ayrı çağrıyla üç ayrı çıktı verdi; fark yalnızca verilen bayraktaydı, kaynak
  dosyanın hiçbir sürümünde değil.
- Sınırlayıcı ölçüm: bağlayıcı gömülen değeri hiç doğrulamıyor — biçimsiz bir dizgi de, boş
  bir dizgi de aynı şekilde, hiçbir uyarı üretmeden kabul ediliyor.
- Sembol tablosunu kaldıran ayrı bir bağlayıcı seçeneği, programın kendi çıktısını
  değiştirmiyor; iki seçenek birbirinden bağımsız iki katmanda çalışıyor.
- Hedef seçimi (önceki ders) ve gömülen değer (bu ders) aynı örüntüyü paylaşıyor: ikisi de
  derleme girdisinin bir parçası, kaynağın kendisinin bir parçası değil.

#### Sonraki Adım

Bu derste gömülen değer kaynakta hiç yazılı değildi ve hiçbir araç onu denetlemedi. Sıradaki
ders tam tersi bir durumu ele alıyor: bir sürüm numarası kaynağın kendisinde, açıkça
bildiriliyor — ama bu kez bir araç, o sürümün taşıdığı bir sözü, yani eski koda karşı hâlâ
uyumlu olup olmadığını denetliyor. Soru artık "değer nereden geliyor" değil, "kaynakta yazılan
bir sayı neyi garanti ediyor".

### Modül Yayımlama

Source: https://academia.sh/tr/kurslar/go-uretim/modul-yayimlama

Bir sürüm numarası kaynakta açıkça bildirilir, ama o sürümün taşıdığı uyumluluk sözünü kaynağın kendisi değil bir araç denetler: dışa aktarılan işlevlerin imzası karşılaştırılarak eklenen, değişen ve silinen adlar sayılır. Statik karar aynı kaynağı gerçekten derleyerek doğrulanıyor. Sınırlayıcı ölçüm, sözün imzalar üzerine kurulu olduğunu — aynı imzayı taşıyan iki gerçekleştirimin tamamen farklı bir sonuç verebildiğini ve aracın bunu hiç göremediğini — koşturarak gösteriyor.

Önceki derste gömülen değer kaynakta hiç yazılı değildi ve hiçbir araç onu denetlemedi. Bu
ders tam tersi bir kaynaktan başlıyor: bir sürüm numarası, kaynağın kendisinde, açıkça
bildiriliyor. Ama kaynakta yazılı olan bu sayı tek başına hiçbir şey garanti etmiyor —
garanti eden şey, o sayının taşıdığı bir söz ve bu sözü tutup tutmadığını denetleyen ayrı
bir araç.

#### Sürüm Kaynakta, Uyumluluk Araçtan

Bir modülün ardışık sürümleri arasındaki **uyumluluk sözü (compatibility promise)**, yeni bir
sürümün eski sürümü kullanan kodu bozmayacağını söylüyor. Bu söz kaynakta bir sayıyla
bildiriliyor, ama sayının kendisi bir denetim yapmıyor; kimin doğru sayıyı yazdığını kontrol
eden bir mekanizma yok. Denetlenebilen şey, sözün **davranışsal** karşılığı değil —
gerçekleştirimin içinde ne olduğu— **imzasal** karşılığı: dışa aktarılan bir işlevin adı,
parametreleri ve dönüş tipi bir sürümden ötekine değişmiş mi?

Aşağıdaki ölçüm standart kitaplığın kendi ayrıştırma paketlerini kullanarak küçük bir
karşılaştırıcı kuruyor — gerçek bir yayımlama aracı değil, bu dersin sorusunu yanıtlamak için
modellenmiş bir araç. İki kaynak metni ayrıştırıyor, her ikisinde de dışa aktarılan işlevlerin
imzasını çıkarıyor ve iki kümeyi karşılaştırıyor.

Karşılaştırıcının kendisi üç adımdan oluşuyor. Önce her kaynak, dilin kendi ayrıştırıcısıyla
bir söz dizim ağacına dönüştürülüyor — bu adım hiçbir tip çözümlemesi yapmıyor, yalnızca
kaynağın söz dizimini okuyor. Sonra ağaçtaki her işlev bildirimi süzülüyor: bir yöntemin
alıcısı olmayan (yani sıradan bir işlev olan) ve adı büyük harfle başlayan bildirimler
tutuluyor, geri kalan her şey — yöntemler, dışa aktarılmayan yardımcı işlevler — atılıyor.
Son adımda, kalan her işlevin tipi (parametre listesi ve dönüş listesi) yeniden kaynak
biçimine yazdırılıyor ve bu yazdırılmış metin, o işlevin **imzası** olarak bir sözlükte
tutuluyor. Sözlüğün anahtarı işlev adı, değeri imza metni; iki sürümün sözlükleri bu yüzden
doğrudan karşılaştırılabiliyor.

```go
// yardimcilar.go — bu dersin paylastigi yardimcilar: imza cikarma, gecici paket kurma, arac cagirma
package main

import (
	"bytes"
	"fmt"
	"go/ast"
	"go/parser"
	"go/printer"
	"go/token"
	"os"
	"os/exec"
	"path/filepath"
	"sort"
	"strings"
)

// goster bir bilgiyi, kimin soyledigini ve kanitini tek satirda yazdirir.
func goster(bilgi, soyleyen, kanit string) {
	fmt.Printf("%-42s %-7s %s\n", bilgi, soyleyen, kanit)
}

// imzalar, verilen kaynaktaki disa aktarilan serbest islevlerin adini, yazdirilmis tip
// imzasina esler. Standart kitapligin kendi ayristirma paketleriyle kurulan kucuk bir model;
// gercek bir uyumluluk araci degil.
func imzalar(kaynak string) map[string]string {
	fset := token.NewFileSet()
	dosya, err := parser.ParseFile(fset, "kaynak.go", kaynak, 0)
	if err != nil {
		return nil
	}
	sonuc := map[string]string{}
	for _, dekl := range dosya.Decls {
		fd, tamam := dekl.(*ast.FuncDecl)
		if !tamam || fd.Recv != nil {
			continue
		}
		ad := fd.Name.Name
		if !ast.IsExported(ad) {
			continue
		}
		var buf bytes.Buffer
		printer.Fprint(&buf, fset, fd.Type)
		sonuc[ad] = strings.TrimPrefix(buf.String(), "func")
	}
	return sonuc
}

// karsilastir, iki imza kumesi arasindaki eklenen, degisen ve silinen adlari doner.
func karsilastir(eski, yeni map[string]string) (eklenen, degisen, silinen []string) {
	for ad, yeniImza := range yeni {
		eskiImza, varDi := eski[ad]
		if !varDi {
			eklenen = append(eklenen, ad)
		} else if eskiImza != yeniImza {
			degisen = append(degisen, ad)
		}
	}
	for ad := range eski {
		if _, varMi := yeni[ad]; !varMi {
			silinen = append(silinen, ad)
		}
	}
	sort.Strings(eklenen)
	sort.Strings(degisen)
	sort.Strings(silinen)
	return
}

// uyumluMu, sadece imzalara bakarak eski surumu kullanan kodun yeni surumle de
// derlenecegini soyler: hicbir ad silinmemis ve hicbir imza degismemis olmali.
func uyumluMu(eski, yeni map[string]string) bool {
	_, degisen, silinen := karsilastir(eski, yeni)
	return len(degisen) == 0 && len(silinen) == 0
}

// paket, verilen dosyalari yeni bir gecici modulun icine yazar; yol hicbir yerde basilmaz.
func paket(dosyalar map[string]string) string {
	dizin, err := os.MkdirTemp("", "yayimlama")
	if err != nil {
		return ""
	}
	for ad, icerik := range dosyalar {
		os.MkdirAll(filepath.Dir(filepath.Join(dizin, ad)), 0o755)
		os.WriteFile(filepath.Join(dizin, ad), []byte(icerik), 0o644)
	}
	return dizin
}

// derlenebilirMi, verilen dizindeki paketi derlemeyi dener; cikti hicbir zaman basilmaz.
func derlenebilirMi(dizin string) bool {
	cmd := exec.Command("go", "build", "-o", os.DevNull, "./...")
	cmd.Dir = dizin
	cmd.Env = append(os.Environ(), "GOTOOLCHAIN=local")
	return cmd.Run() == nil
}
```

```go
// olcum1.go — surum kaynakta bildiriliyor, uyumluluk sozunu arac (imza karsilastirmasi) denetliyor
package main

import "fmt"

const surumV1 = "v1.0.0"
const surumV2Uyumlu = "v1.1.0"
const surumV2Uyumsuz = "v2.0.0"

const kaynakV1 = "package ornek\n\nfunc Ikiye(x int) int { return x * 2 }\n"
const kaynakV2Uyumlu = "package ornek\n\nfunc Ikiye(x int) int { return x * 2 }\n\nfunc Uce(x int) int { return x * 3 }\n"
const kaynakV2Uyumsuz = "package ornek\n\nfunc Ikiye(x int, taban int) int { return x * taban }\n"

func main() {
	i1 := imzalar(kaynakV1)
	i2u := imzalar(kaynakV2Uyumlu)
	i2s := imzalar(kaynakV2Uyumsuz)

	fmt.Println("-- surum kaynakta, uyumluluk aractan --")
	goster("surum "+surumV1+" -> "+surumV2Uyumlu, "arac", fmt.Sprint("uyumlu=", uyumluMu(i1, i2u)))
	goster("surum "+surumV1+" -> "+surumV2Uyumsuz, "arac", fmt.Sprint("uyumlu=", uyumluMu(i1, i2s)))

	eklenenU, degisenU, _ := karsilastir(i1, i2u)
	goster("uyumlu degisiklikte eklenen ad sayisi", "arac", fmt.Sprint(len(eklenenU)))
	goster("uyumlu degisiklikte degisen imza sayisi", "arac", fmt.Sprint(len(degisenU)))

	_, degisenS, _ := karsilastir(i1, i2s)
	goster("uyumsuz degisiklikte degisen imza sayisi", "arac", fmt.Sprint(len(degisenS)))
}
```

```
-- surum kaynakta, uyumluluk aractan --
surum v1.0.0 -> v1.1.0                     arac    uyumlu=true
surum v1.0.0 -> v2.0.0                     arac    uyumlu=false
uyumlu degisiklikte eklenen ad sayisi      arac    1
uyumlu degisiklikte degisen imza sayisi    arac    0
uyumsuz degisiklikte degisen imza sayisi   arac    1
```

**DR12.** Üç sürüm numarasının üçü de kaynakta yazılı — bu ders boyunca tek `kaynak` sınıfına
giren bilgi bu. Ama sürümlerin **hangi ilişkide** olduğu — `v1.1.0`'ın `v1.0.0`'ı bozmadığı,
`v2.0.0`'ın bozduğu — sayıların kendisinden değil, karşılaştırıcının imza kümeleri üzerinde
çalıştırılmasından geliyor. `v1.1.0` yalnızca yeni bir ad ekliyor, `Ikiye`nin imzası aynı
kalıyor — bu yüzden `uyumlu=true`. `v2.0.0` ise `Ikiye`ye ikinci bir parametre ekliyor; ad
aynı ama imza değişti — bu yüzden `uyumlu=false`. Sürüm numaralarının kendisi (`v1.0.0`,
`v1.1.0`, `v2.0.0`) bir kural değil bir **etiket**; kuralı uygulayan taraf sayıları hiç
okumadan, yalnızca imza kümelerini karşılaştırıyor.

**M09/K01 Go Temelleri**'nin `go-packages` konusu, bir modülün hangi sürümünün seçildiğini
en düşük sürüm seçimi kuralıyla ölçmüştü; bu ders o seçim mekanizmasını tekrar kurmuyor,
yalnızca seçilen sürümün **taşıdığı sözü** ölçüyor.

#### Gerçek Bir Derlemeyle Doğrulama

Yukarıdaki karar tümüyle statik: hiçbir kaynak gerçekten derlenmedi, yalnızca ayrıştırıldı ve
karşılaştırıldı. Bu kararın gerçek bir derlemeyle örtüşüp örtüşmediği ayrı bir soru ve
aşağıdaki ölçüm bunu doğrudan sınıyor — `v1.0.0`'ı eski biçimiyle çağıran bir kaynağı, hem
`v1.1.0` hem `v2.0.0` paketiyle birlikte gerçekten derliyor.

```go
// olcum2.go — statik karsilastirmayi gercek bir derlemeyle dogrulama
package main

import "fmt"

const kaynakV2Uyumlu = "package ornek\n\nfunc Ikiye(x int) int { return x * 2 }\n\nfunc Uce(x int) int { return x * 3 }\n"
const kaynakV2Uyumsuz = "package ornek\n\nfunc Ikiye(x int, taban int) int { return x * taban }\n"

// kur, eski cagiranin kaynagini verilen paket surumuyle birlikte gecici bir module yaziyor.
func kur(paketKaynagi string) string {
	return paket(map[string]string{
		"go.mod":          "module deneme\n\ngo 1.24\n",
		"ornek/ornek.go":  paketKaynagi,
		"cagiran.go":      "package main\n\nimport \"deneme/ornek\"\n\nfunc main() {\n\t_ = ornek.Ikiye(5)\n}\n",
	})
}

func main() {
	dizinUyumlu := kur(kaynakV2Uyumlu)
	dizinUyumsuz := kur(kaynakV2Uyumsuz)
	defer func() { _, _ = dizinUyumlu, dizinUyumsuz }()

	fmt.Println("-- statik karar, gercek derlemeyle dogrulaniyor --")
	goster("eski cagiran + uyumlu surum derlendi mi", "arac", fmt.Sprint(derlenebilirMi(dizinUyumlu)))
	goster("eski cagiran + uyumsuz surum derlendi mi", "arac", fmt.Sprint(derlenebilirMi(dizinUyumsuz)))
}
```

```
-- statik karar, gercek derlemeyle dogrulaniyor --
eski cagiran + uyumlu surum derlendi mi    arac    true
eski cagiran + uyumsuz surum derlendi mi   arac    false
```

**DR13.** İki satır da bir önceki ölçümün verdiği kararla birebir örtüşüyor: `uyumlu=true`
denen sürüm çiftinde eski çağıran gerçekten derleniyor, `uyumlu=false` denen çiftte gerçekten
derlenmiyor. Bu örtüşme rastlantı değil — imza karşılaştırması zaten derleyicinin kendi
kuralını modelliyor: bir çağrının derlenebilmesi için çağrılan işlevin adı ve parametre
listesi çağrı yerinde beklenenle eşleşmeli. Statik karşılaştırıcı bu kuralı derleyiciyi hiç
çalıştırmadan uyguluyor; gerçek derleme ise aynı kuralı, gerçek bir çağrı üzerinden bir kez
daha, bu kez **koşum olmadan ama gerçek bir araç çağrısıyla** doğruluyor. Bağlayıcı
seçeneklerinde olduğu gibi burada da iki farklı yol aynı soruya varıyor; farkları, birinin
kaynağı hiç derlemeden okuması, ötekinin gerçekten derlemesi.

Bu iki yolun birbirini doğrulaması pratikte önemli bir tasarım seçimini haklı çıkarıyor:
statik karşılaştırma, gerçek bir çağıran kaynağına hiç ihtiyaç duymadan çalışıyor — yalnızca
iki sürümün kendi kaynağını okuyor, bir "eski kod" örneğine gerek yok. Gerçek derleme ise tam
tersi: belirli bir çağıran olmadan hiçbir şey söyleyemiyor, ama söylediği şey doğrudan o
çağıranın kaderiyle ilgili. Bir yayımlama sürecinde ilk yol ucuz ve genel bir ön elemeyi,
ikinci yol belirli bir tüketiciye karşı kesin bir kanıtı sağlıyor; bu ders ikisinin aynı iki
sonuca vardığını göstererek, ucuz yolun bu dar örnekte pahalı yolun yerini tutabildiğini
ölçüyor.

#### Sınırlayıcı Ölçüm: Söz İmzalar Üzerinedir, Davranış Üzerine Değil

Yukarıdaki iki ölçüm de imza değişikliğine bakıyordu. Bir imza hiç değişmeden de bir
gerçekleştirimin **davranışı** değişebilir — aynı ad, aynı parametre, aynı dönüş tipi, ama
gövdenin içinde farklı bir hesap. Aşağıdaki ölçüm bunu doğrudan gösteriyor: `Ikiye` adlı iki
gerçek işlev, birebir aynı imzayı taşıyor ama biri ikiyle, öteki üçle çarpıyor.

```go
// olcum3.go — sinirlayici olcum: uyumluluk sozu imzalar uzerinedir, davranis uzerine degil
package main

import "fmt"

// ikiyeEski, ilk surumun gercek gerceklestirimi.
func ikiyeEski(x int) int { return x * 2 }

// ikiyeYeniDavranis, AYNI imzayi tasiyan ama farkli hesaplayan bir sonraki surum gerceklestirimi.
func ikiyeYeniDavranis(x int) int { return x * 3 }

const kaynakEski = "package ornek\n\nfunc Ikiye(x int) int { return x * 2 }\n"
const kaynakYeniDavranis = "package ornek\n\nfunc Ikiye(x int) int { return x * 3 }\n"

func main() {
	fmt.Println("-- sinirlayici olcum: imza ayni, davranis farkli --")
	goster("imza karsilastirmasi", "arac", fmt.Sprint("uyumlu=", uyumluMu(imzalar(kaynakEski), imzalar(kaynakYeniDavranis))))
	goster("Ikiye(5) eski gerceklestirim", "arac", fmt.Sprint(ikiyeEski(5)))
	goster("Ikiye(5) yeni gerceklestirim", "arac", fmt.Sprint(ikiyeYeniDavranis(5)))
	goster("iki gerceklestirim ayni sonucu mu veriyor", "arac", fmt.Sprint(ikiyeEski(5) == ikiyeYeniDavranis(5)))
}
```

```
-- sinirlayici olcum: imza ayni, davranis farkli --
imza karsilastirmasi                       arac    uyumlu=true
Ikiye(5) eski gerceklestirim               arac    10
Ikiye(5) yeni gerceklestirim               arac    15
iki gerceklestirim ayni sonucu mu veriyor  arac    false
```

**DR14.** Karşılaştırıcı bu değişikliği `uyumlu=true` olarak sınıflıyor, çünkü `Ikiye`nin adı,
parametre tipi ve dönüş tipi hiç değişmedi. Ama aynı girdiyle (`5`) çağrıldığında iki
gerçekleştirim farklı sayı veriyor — `10` ve `15`. Bu, uyumluluk sözünün **kapsamının**
sınırı: söz "eski kodunuz derlenmeye ve çalışmaya devam edecek" diyor, "eski kodunuzun
**sonucu** aynı kalacak" demiyor. Bir çağıranın kodu her iki sürümle de sorunsuz derlenir ve
çalışır — hiçbir hata, hiçbir panik yok — ama ürettiği sonuç sürüme göre değişir. İmza
karşılaştırıcısı bu farkı hiçbir zaman göremiyor, çünkü baktığı şey isimler ve tipler; bir
gövdenin içinde ne hesaplandığı, bu aracın erişebildiği katmanın tümüyle dışında.

Bu sınırın nedeni keyfi değil: bir gövdenin **anlamını** iki sürüm arasında karşılaştırmak,
imzasını karşılaştırmaktan bambaşka bir problem. İmza karşılaştırması bu derste birkaç
satırlık bir ayrıştırma ve bir sözlük karşılaştırmasıyla çözüldü; davranış karşılaştırması
ise genel halinde çözülemez — iki işlevin her girdi için aynı sonucu verip vermediğini
sınamak, sonlu bir sürede yanıtlanamayan bir problem sınıfına giriyor. Bu yüzden uyumluluk
sözü pratikte hep imzalar üzerine kuruluyor: ölçülebilir, sonlu zamanda karar verilebilir bir
şey. Davranış uyumluluğunu güvence altına almanın elde kalan tek yolu, bu dersin ilk
bölümünde ölçülen tablo güdümlü testler ve karşılaştırmalı ölçümler — yeni sürümü eski
sürümün beklediği girdilerle koşturup sonuçları karşılaştırmak. Bu, imza karşılaştırıcısının
yapamadığını, gerçek koşumla tamamlıyor; ama artık bu derste ölçülen `araç` sınıfının dışında,
kursun `koşum` sınıfının konusu.

Bu, bir yayımlama sürecinde imza uyumluluğunun **yeterli** değil, **gerekli** bir koşul
olduğunu gösteriyor. İmza uyumsuzsa, eski kod kesinlikle derlenmiyor — bu ders bunu gerçek bir
derlemeyle doğruladı. İmza uyumluysa, eski kod kesinlikle derleniyor, ama ürettiği sonucun
öncekiyle aynı olacağı hiçbir şekilde garanti edilmiyor. Bir sürüm numarasını artırmak bu
yüzden iki ayrı kararı birden taşıyor: imzaların değişip değişmediği (bu ders tarafından
ölçülebilir) ve davranışın değişip değişmediği (yalnızca ayrı bir test ve gözlem süreciyle
ölçülebilir).

#### Özet

- Bir sürüm numarası kaynakta açıkça bildiriliyor, ama taşıdığı uyumluluk sözünü kaynağın
  kendisi değil, dışa aktarılan işlevlerin imzasını karşılaştıran bir araç denetliyor.
- Yalnızca yeni ad eklenen bir değişiklik uyumlu, var olan bir imzayı değiştiren bir değişiklik
  uyumsuz sayılıyor; karar sürüm numarasının kendisinden değil, imza kümesinin
  karşılaştırılmasından geliyor.
- Statik imza kararı, eski bir çağıranı yeni sürümle gerçekten derleterek doğrulandı; iki
  yöntem de aynı iki sonuca vardı.
- Sınırlayıcı ölçüm: birebir aynı imzayı taşıyan iki gerçekleştirim tamamen farklı bir sonuç
  verebiliyor ve imza karşılaştırıcısı bu farkı hiçbir zaman göremiyor.
- Uyumluluk sözü, bir kaynağın **derlenip çalışacağını** garanti ediyor, ürettiği sonucun
  sürümden sürüme **aynı kalacağını** garanti etmiyor.

#### Sonraki Adım

Bu derste ölçülen araç, kaynağı yalnızca isimler ve tipler düzeyinde okudu; gövdelerin içinde
ne olduğuna hiç bakmadı. Sıradaki ders tam da bu boşluğa iniyor: bir kaynağın içindeki belirli
kalıpları arayan, imzayı değil gövdenin kendisini okuyan bir araç. Soru artık "isimler ve
tipler değişti mi" değil, "kaynağın içinde daha önce hiç aranmamış bir kalıp var mı".

### Statik Çözümleme ve Güvenlik Denetimi

Source: https://academia.sh/tr/kurslar/go-uretim/statik-cozumleme-ve-guvenlik-denetimi

Aynı iki kaynaktan biri temiz, öteki kusurlu; derleyicinin ikisini de aynı şekilde kabul ettiği, ama bir çözümleyicinin aralarındaki farkı çıkış koduyla bildirdiği ölçülür. Üç kalıptan derleyicinin hiçbirini yakalamadığı, çözümleyicinin ikisini yakaladığı sayılıyor. Sınırlayıcı ölçüm, çözümlemenin temiz çıkmasının kusursuz olmak anlamına gelmediğini — yakalanmayan üçüncü kalıbın koşum sırasında gerçekten çöktüğünü — koşturarak gösteriyor. Son ölçüm, bir bağımlılığın bildirilen sürümünü modellenmiş bir eşik listesiyle karşılaştırıyor.

Önceki derste ölçülen araç, kaynağı yalnızca isimler ve tipler düzeyinde okudu; gövdelerin
içinde ne olduğuna hiç bakmadı. Bu ders tam o boşluğa iniyor: gövdenin kendisini okuyan, belirli
kalıpları arayan bir araç. Ama bu aracın da kendi sınırı var — bulduğunu söylemesi, aramadığı
bir şeyin de yok olduğu anlamına gelmiyor.

#### İki Kaynak, Bir Çözümleyici

**Statik çözümleme (static analysis)**, bir kaynağı hiç çalıştırmadan, yalnızca metnini
okuyarak belirli hata kalıplarını arayan bir işlemdir. Dilin kendi dağıtımının çözümleme aracı
bu işi tek bir komutla yapıyor ve yalnızca **çıkış kodu** bildiriyor — bulduğu satırların
metnini bu derste hiç okumuyoruz, yalnızca temiz mi kusurlu mu olduğunu.

Aşağıdaki ölçüm iki kaynak kuruyor: biri bir tam sayıyı doğru biçimlendiriciyle yazdırıyor,
öteki aynı biçimlendiriciye bir dizgi veriyor — tip ile biçim arasında bir uyuşmazlık.

```go
// yardimcilar.go — bu dersin paylastigi yardimcilar: gecici paket kurma ve arac cagirma
package main

import (
	"fmt"
	"os"
	"os/exec"
	"path/filepath"
)

// goster bir bilgiyi, kimin soyledigini ve kanitini tek satirda yazdirir.
func goster(bilgi, soyleyen, kanit string) {
	fmt.Printf("%-40s %-7s %s\n", bilgi, soyleyen, kanit)
}

// paket, verilen dosyalari yeni bir gecici modulun icine yazar; yol hicbir yerde basilmaz.
func paket(dosyalar map[string]string) string {
	dizin, err := os.MkdirTemp("", "cozumleme")
	if err != nil {
		return ""
	}
	os.WriteFile(filepath.Join(dizin, "go.mod"), []byte("module cozumlemedeneme\n\ngo 1.24\n"), 0o644)
	for ad, icerik := range dosyalar {
		os.WriteFile(filepath.Join(dizin, ad), []byte(icerik), 0o644)
	}
	return dizin
}

// aracCalistir dilin kendi komutunu kosturur, ciktiyi ve basari durumunu doner; cikti basilmaz.
func aracCalistir(dizin string, arg ...string) (string, bool) {
	cmd := exec.Command("go", arg...)
	cmd.Dir = dizin
	cmd.Env = append(os.Environ(), "GOTOOLCHAIN=local")
	cikti, err := cmd.CombinedOutput()
	return string(cikti), err == nil
}

// derlenebilirMi, verilen dizindeki paketi derlemeyi dener.
func derlenebilirMi(dizin string) bool {
	_, tamam := aracCalistir(dizin, "build", "-o", os.DevNull, ".")
	return tamam
}

// temizMi, verilen dizindeki paketin statik cozumlemeden temiz gecip gecmedigini soyler.
func temizMi(dizin string) bool {
	_, tamam := aracCalistir(dizin, "vet", "./...")
	return tamam
}
```

```go
// olcum1.go — ayni iki kaynak, biri temiz biri kusurlu, tek bir cozumleyiciden geciyor
package main

import "fmt"

func main() {
	temiz := paket(map[string]string{
		"prog.go": "package main\n\nimport \"fmt\"\n\nfunc main() {\n\tfmt.Printf(\"deger=%d\\n\", 7)\n}\n",
	})
	kusurlu := paket(map[string]string{
		"prog.go": "package main\n\nimport \"fmt\"\n\nfunc main() {\n\tfmt.Printf(\"deger=%d\\n\", \"metin\")\n}\n",
	})

	fmt.Println("-- iki kaynak, bir cozumleyici --")
	goster("temiz kaynak derlendi mi", "arac", fmt.Sprint(derlenebilirMi(temiz)))
	goster("kusurlu kaynak derlendi mi", "arac", fmt.Sprint(derlenebilirMi(kusurlu)))
	goster("temiz kaynak cozumlemesi", "arac", fmt.Sprint("temiz=", temizMi(temiz)))
	goster("kusurlu kaynak cozumlemesi", "arac", fmt.Sprint("temiz=", temizMi(kusurlu)))
}
```

```
-- iki kaynak, bir cozumleyici --
temiz kaynak derlendi mi                 arac    true
kusurlu kaynak derlendi mi               arac    true
temiz kaynak cozumlemesi                 arac    temiz=true
kusurlu kaynak cozumlemesi               arac    temiz=false
```

**DR15.** İki kaynak da derleniyor — tip uyuşmazlığı derleyici düzeyinde bir hata değil,
çünkü biçimlendirme dizgisi ile argümanlar arasındaki ilişki, derleyicinin tip denetiminin
konusu değil. Çözümleyici ise bu iki satırı ayırt ediyor: birinde biçimlendirici ile argüman
tipi eşleşiyor, ötekinde eşleşmiyor. Bu, önceki derslerdeki "araç" bilgisinin bir başka
biçimi — çözümleyici kaynağı çalıştırmadan, yalnızca okuyarak bir kalıp arıyor ve bulduğunu
tek bir çıkış koduyla bildiriyor.

#### Derleyicinin Yakalamadığı Kalıp

Yukarıdaki tek kalıp bir başlangıç. Aşağıdaki ölçüm üç ayrı kalıbı bir arada deniyor: bir
biçim uyuşmazlığı, bir kilit değerinin kopyalanması ve bir hatanın yok sayılması. Üçü de
derleniyor; sorulan soru, kaçının çözümleyici tarafından yakalandığı.

```go
// olcum2.go — derleyicinin hicbirini reddetmedigi uc kalip, cozumleyiciden gecerken ayriliyor
package main

import "fmt"

const kalipBicim = "package main\n\nimport \"fmt\"\n\nfunc main() {\n\tfmt.Printf(\"deger=%d\\n\", \"metin\")\n}\n"

const kalipKilit = "package main\n\nimport \"sync\"\n\ntype Sayac struct {\n\tkilit sync.Mutex\n\tdeger int\n}\n\nfunc kopyala(s Sayac) Sayac {\n\treturn s\n}\n\nfunc main() {\n\tkopyala(Sayac{})\n}\n"

const kalipYokSayilanHata = "package main\n\nimport \"fmt\"\n\ntype Kayit struct{ Ad string }\n\nfunc bul(anahtar string) (*Kayit, error) {\n\tif anahtar == \"\" {\n\t\treturn nil, fmt.Errorf(\"anahtar bos\")\n\t}\n\treturn &Kayit{Ad: anahtar}, nil\n}\n\nfunc main() {\n\tk, _ := bul(\"\")\n\tfmt.Println(k.Ad)\n}\n"

func main() {
	kaliplar := []struct{ ad, kaynak string }{
		{"bicim uyusmazligi", kalipBicim},
		{"kilit kopyalama", kalipKilit},
		{"yok sayilan hata", kalipYokSayilanHata},
	}

	fmt.Println("-- derleyicinin yakalamadigi uc kalip --")
	derlenenSayisi, temizSayisi := 0, 0
	for _, k := range kaliplar {
		dizin := paket(map[string]string{"prog.go": k.kaynak})
		d := derlenebilirMi(dizin)
		t := temizMi(dizin)
		if d {
			derlenenSayisi++
		}
		if t {
			temizSayisi++
		}
		goster(k.ad+" derlendi mi", "arac", fmt.Sprint(d))
		goster(k.ad+" cozumleme temiz mi", "arac", fmt.Sprint(t))
	}
	goster("uc kaliptan derlenen sayisi", "arac", fmt.Sprint(derlenenSayisi))
	goster("uc kaliptan cozumlemeyi gecen sayisi", "arac", fmt.Sprint(temizSayisi))
	goster("cozumleyicinin yakaladigi kalip sayisi", "arac", fmt.Sprint(len(kaliplar)-temizSayisi))
}
```

```
-- derleyicinin yakalamadigi uc kalip --
bicim uyusmazligi derlendi mi            arac    true
bicim uyusmazligi cozumleme temiz mi     arac    false
kilit kopyalama derlendi mi              arac    true
kilit kopyalama cozumleme temiz mi       arac    false
yok sayilan hata derlendi mi             arac    true
yok sayilan hata cozumleme temiz mi      arac    true
uc kaliptan derlenen sayisi              arac    3
uc kaliptan cozumlemeyi gecen sayisi     arac    1
cozumleyicinin yakaladigi kalip sayisi   arac    2
```

**DR16.** Üç kalıbın üçü de derleniyor — derleyicinin yakaladığı kalıp sayısı sıfır. Bu üç
kalıp derleyicinin çalıştığı katmanın tümüyle dışında duruyor, çünkü üçü de **tipçe geçerli**
kod: yanlış biçimlendirilmiş bir yazdırma çağrısı, bir alanı kopyalanan bir yapı, kullanılmayan
bir hata değeri — bunların hiçbiri bir tip uyuşmazlığı ya da bir söz dizimi hatası değil.
Çözümleyici üç kalıptan ikisini yakalıyor: biçim uyuşmazlığını ve kilit kopyalamasını. Üçüncüsü
— hatanın yok sayılması — çözümlemeden **temiz** çıkıyor. Bu üçüncü kalıp bir sonraki bölümün
konusu.

Yakalanan iki kalıbın ortak bir özelliği var: ikisi de dilin kendi tip sisteminin **dışında**
kalan, ama yine de mekanik olarak tanınabilen bir örüntüye uyuyor. Biçim uyuşmazlığında
örüntü, bir biçimlendirme dizgisindeki her `%` işaretinin, verilen argümanların tipiyle
eşleşip eşleşmediği; kilit kopyalamasında örüntü, `sync` paketinin belgelediği bir tipin
(kilit içeren herhangi bir yapı) değerle değil yalnızca işaretçiyle taşınması gerektiği.
Çözümleyici bu iki kuralı, kaynağı ayrıştırıp söz dizim ağacında belirli bir biçimi arayarak
uyguluyor — tıpkı önceki dersteki imza karşılaştırıcısının yaptığı gibi, yalnızca aranan
biçim burada bir imza değil, bir kullanım kalıbı. Üçüncü kalıbın yakalanmaması, çözümleyicinin
bu iki kuraldan **hiçbirine** uymadığı için değil, hatanın yok sayılmasını arayan bir kural
listede hiç olmadığı için.

#### Sınırlayıcı Ölçüm: Temiz Çıkmak Kusursuz Demek Değil

Yukarıdaki üçüncü kalıp, bir işlevin döndürdüğü hatayı yok sayıp sonucu doğrudan kullanan bir
çağrı. Çözümleyici bunu temiz buldu; derleyici de reddetmedi. Aşağıdaki ölçüm bu kalıbı
gerçekten koşturuyor — bu kez bir alt süreç çağırarak değil, aynı mantığı bu ölçümün kendi
içinde çalıştırarak, çünkü panik burada `recover` ile yakalanıp yalnızca bir doğruluk değeri
olarak okunacak.

```go
// olcum3.go — sinirlayici olcum: temiz cikmak kusursuz demek degil
package main

import "fmt"

const kalipYokSayilanHata = "package main\n\nimport \"fmt\"\n\ntype Kayit struct{ Ad string }\n\nfunc bul(anahtar string) (*Kayit, error) {\n\tif anahtar == \"\" {\n\t\treturn nil, fmt.Errorf(\"anahtar bos\")\n\t}\n\treturn &Kayit{Ad: anahtar}, nil\n}\n\nfunc main() {\n\tk, _ := bul(\"\")\n\tfmt.Println(k.Ad)\n}\n"

// Kayit ve bul, yukaridaki kaynagin gercek, calistirilabilir bir eslesi: cozumleyicinin
// okudugu metinle birebir ayni mantigi tasiyor.
type Kayit struct{ Ad string }

func bul(anahtar string) (*Kayit, error) {
	if anahtar == "" {
		return nil, fmt.Errorf("anahtar bos")
	}
	return &Kayit{Ad: anahtar}, nil
}

// dene, hatayi yok sayan cagriyi kosturur ve panigi yakalar; yigit izi hicbir yerde basilmaz.
func dene() (panikledi bool) {
	defer func() {
		if r := recover(); r != nil {
			panikledi = true
		}
	}()
	k, _ := bul("")
	_ = k.Ad
	return false
}

func main() {
	dizin := paket(map[string]string{"prog.go": kalipYokSayilanHata})

	fmt.Println("-- sinirlayici olcum: temiz cikmak kusursuz demek degil --")
	goster("cozumleme sonucu", "arac", fmt.Sprint("temiz=", temizMi(dizin)))
	goster("calistirildiginda panikledi mi", "kosum", fmt.Sprint(dene()))
}
```

```
-- sinirlayici olcum: temiz cikmak kusursuz demek degil --
cozumleme sonucu                         arac    temiz=true
calistirildiginda panikledi mi           kosum   true
```

**DR17.** Çözümleyicinin verdiği yanıt (`temiz=true`) ve koşumun verdiği yanıt (`panikledi=true`)
aynı kaynak hakkında iki ayrı şey söylüyor ve ikisi de doğru. Çözümleyici "aradığım kalıplardan
hiçbirini bulamadım" diyor — bu, bir hatanın yok sayılmasını arayan bir kural içermiyor. Koşum
ise "bu kaynak, bu girdiyle çalıştırıldığında çöküyor" diyor. Bu iki yanıt çelişmiyor; ikisi de
aracın **kapsamıyla** sınırlı. `temiz=true` satırı bu yüzden "bu kaynakta kusur yok" diye
okunamaz — yalnızca "çözümleyicinin bildiği kalıplardan hiçbiri bu kaynakta yok" diye okunabilir.
Bu, kursun daha önce kapsama oranı için kurduğu ayrımın aynısı: bir aracın sessiz kalması, o
aracın **aramadığı** şeyin var olmadığı anlamına gelmiyor.

Bu sınırın pratikteki karşılığı, bir çözümleyicinin kural listesinin **sabit** olmadığı ama
her zaman **sonlu** olduğu. Yeni bir kalıp fark edildikçe listeye yeni bir kural eklenebiliyor
— bu ders özelinde kullanılan iki kural (biçim uyuşmazlığı, kilit kopyalama) da böyle
eklenmiş, gerçek bir hatanın gerçek programlarda tekrar tekrar görülmesinden sonra listeye
girmiş kurallar. Ama liste ne kadar büyürse büyüsün, her zaman henüz eklenmemiş bir kalıp
kalıyor; bu ders bu boşluğu somutlaştırmak için tek bir örnek seçti, ama seçilen örneğin
kendisi önemli değil — önemli olan, çözümlemenin **temiz** yanıtının her zaman "şu ana kadar
bilinen kurallara göre temiz" anlamına geldiği, mutlak bir doğruluk iddiası taşımadığı.

#### Bilinen Açık Taraması: Modellenen Bir Liste

Çözümleyicinin aradığı kalıplar dilin kendi kurallarıyla sınırlı. Bir bağımlılığın taşıdığı
bilinen bir **açık (vulnerability)** ise ayrı bir bilgi türü — kodun kendi biçiminden değil,
o bağımlılığın **hangi sürümde** düzeltildiğinden geliyor. **Bilinen açık taraması (known
vulnerability scan)**, bir bağımlılığın bildirilen sürümünü, düzeltmenin geldiği sürümle
karşılaştırıyor. Aşağıdaki ölçüm gerçek bir açık listesine hiç bağlanmıyor; karşılaştırma
kaynak içinde, küçük bir eşik tablosuyla **modelleniyor**.

```go
// olcum4.go — bilinen acik taramasi, modellenen bir esik listesiyle kurulan bir arac
package main

import "fmt"

// Surum uc parcali bir surum numarasini tasir (M09/K01 go-packages/02'deki kalibin aynisi).
type Surum struct{ Ana, Ikincil, Yama int }

func (s Surum) metin() string { return fmt.Sprintf("%d.%d.%d", s.Ana, s.Ikincil, s.Yama) }

func (s Surum) kucukMu(diger Surum) bool {
	if s.Ana != diger.Ana {
		return s.Ana < diger.Ana
	}
	if s.Ikincil != diger.Ikincil {
		return s.Ikincil < diger.Ikincil
	}
	return s.Yama < diger.Yama
}

// guvenliEsik, her modul icin bilinen en dusuk guvenli surumu tutan MODELLENMIS bir tablo;
// gercek bir aciklar veritabani degil, yalnizca bu olcumun kendi kurdugu kucuk bir liste.
var guvenliEsik = map[string]Surum{
	"kutuphaneX": {1, 4, 0},
	"kutuphaneY": {2, 0, 0},
}

type Bagimlilik struct {
	Modul      string
	Bildirilen Surum
}

// acikVarMi, bildirilen surumun modellenen esikten dusuk olup olmadigina bakar.
func acikVarMi(b Bagimlilik) bool {
	esik, varMi := guvenliEsik[b.Modul]
	if !varMi {
		return false
	}
	return b.Bildirilen.kucukMu(esik)
}

func main() {
	bagimliliklar := []Bagimlilik{
		{Modul: "kutuphaneX", Bildirilen: Surum{1, 2, 0}},
		{Modul: "kutuphaneY", Bildirilen: Surum{2, 3, 0}},
	}

	fmt.Println("-- bilinen acik taramasi: modellenen bir liste --")
	acikSayisi := 0
	for _, b := range bagimliliklar {
		a := acikVarMi(b)
		if a {
			acikSayisi++
		}
		goster(b.Modul+" bildirilen="+b.Bildirilen.metin(), "arac", fmt.Sprint("acik=", a))
	}
	goster("iki bagimliliktan acik bulunan sayisi", "arac", fmt.Sprint(acikSayisi))
}
```

```
-- bilinen acik taramasi: modellenen bir liste --
kutuphaneX bildirilen=1.2.0              arac    acik=true
kutuphaneY bildirilen=2.3.0              arac    acik=false
iki bagimliliktan acik bulunan sayisi    arac    1
```

**DR18.** `kutuphaneX` için bildirilen sürüm (`1.2.0`), modellenen eşiğin (`1.4.0`) altında
kalıyor — açık var. `kutuphaneY` için bildirilen sürüm (`2.3.0`) eşiği (`2.0.0`) aşıyor — açık
yok. Bu karşılaştırma da önceki iki ölçümle aynı sınırı taşıyor: eşik tablosunun kendisi
**bu ölçümün kurduğu bir modeldir**, gerçek bir kayıttan gelmiyor ve bir modül tabloda hiç
yoksa `acikVarMi` sessizce `false` dönüyor — tablo eksikse tarama hiçbir şey bulmuyor, tıpkı
çözümleyicinin aramadığı kalıbı hiç görmediği gibi. **M09/K01 Go Temelleri**'nin en düşük
sürüm seçimi ölçümü hangi sürümün seçildiğini kurmuştu; bu ölçüm o seçilen sürümün bilinen bir
eşiğin altında kalıp kalmadığını, ayrı ve modellenmiş bir karşılaştırmayla soruyor.

#### Özet

- Statik çözümleme kaynağı hiç çalıştırmadan, belirli kalıpları arayarak çalışır; derleyicinin
  kabul ettiği iki kaynak arasındaki farkı yalnızca çıkış koduyla bildirir.
- Derleyicinin hiçbirini reddetmediği üç kalıptan ikisi çözümleyici tarafından yakalandı; biri
  — hatanın yok sayılması — temiz çıktı.
- Sınırlayıcı ölçüm: temiz çıkan kalıp, gerçekten koşturulduğunda çöktü; çözümlemenin
  sessizliği kusursuzluk değil, aranan kalıbın bulunmaması anlamına geliyor.
- Bilinen açık taraması, bir bağımlılığın bildirilen sürümünü modellenmiş bir eşikle
  karşılaştırıyor; tablo eksikse tarama hiçbir şey bulamıyor.
- Bu ders boyunca kullanılan ölçü hep aynıydı: bir aracın sessiz kalması, aradığı şeyin
  bulunmadığını gösteriyor, o şeyin **var olmadığını** göstermiyor.

#### Sonraki Adım

Bu derste ölçülen çözümleyici, dilin kendi sınırları içinde kaldı — aradığı kalıplar hep Go
kaynağının kendi söz dizimine ve tipine aitti. Kursun kapanış dersi bu sınırı aşıyor: bir Go
programı başka bir dilin kütüphanesini çağırdığında, çözümleyicinin, çözümleyicinin göremediği
bir sınır daha açılıyor. Sıradaki soru, dilin kendi sınırının ötesine geçmenin bedelinin hangi
araçla, hangi sayıyla görüneceği.

### C ile Birlikte Çalışma

Source: https://academia.sh/tr/kurslar/go-uretim/c-ile-birlikte-calisma

Aynı iş iki yazımla karşılaştırılıyor — dilin kendi kodu ve cgo ile çağrılan bir C işlevi — ve ikisi de aynı ayırma sayısını (1) veriyor. Yerel çağrı içeren paket CGO_ENABLED=0 ile derlenmiyor, çapraz derlenmiyor ve kaçış çözümlemesinin C işlevinin gövdesine dair hiçbir bildirimi yok (0/21 satır). Sınırlayıcı ölçüm: C tarafında serbest bırakılmayan bir bellek ne çözümleyicide ne ayırma sayısında görünüyor — bedel çağrının kendisinde değil, aracın söyleyebildiklerinde. Kurs ve M09 Go müfredatı bu dersle, 16 satırlık bir tabloyla kapanıyor.

Önceki ders bir çözümleyicinin temiz çıkmasının kusursuzluk anlamına gelmediğini, yalnız
aradığı kalıbın bu kaynakta bulunmadığı anlamına geldiğini ölçtü — çözümleyicinin
sessizliği aracın kural listesinin bir sınırıydı, bir kanıt değildi. Bu ders aynı sınırı
bir adım öteye taşıyor: bir Go programı dilin sınırının dışına, başka bir dilin koduna
çıktığında, o kod artık ne derleyicinin ne çözümleyicinin ne de profil aracının her
zaman erişebildiği bir yerde duruyor. Sorulan soru dar: sınırı geçmenin bedeli hangi
araçla, hangi sayıyla görünüyor?

C burada bir ürün ya da bir kütüphane kaydı değil, bir dil — adı yazılabilir, Go'nun
kendisi gibi. Ama bu ders boyunca kullanılan düzenek, cgo, kursun geri kalanının
taşımadığı bir bağımlılık taşıyor: kaynağın derlenebilmesi için gerçek bir C
derleyicisinin ortamda bulunması ve `CGO_ENABLED` değişkeninin açık olması gerekiyor.
**DR19.** Bu ders bu bağımlılığı açık bir varsayım olarak taşıyor; derleyici yoksa ya
da `CGO_ENABLED` kapalıysa bloklar hiç derlenmiyor. Kursun on beş kardeş dersinin
hiçbiri bu ikinci araç zincirine ihtiyaç duymadı.

#### cgo: Dilin Sınırını Aşan Çağrı

Standart kitaplık bu geçişi bir sözde paket üzerinden açıyor: `import "C"`'den hemen
önce, boşluksuz yazılan bir yorum bloğu gerçek C kaynağı taşıyor, derleyici bu bloğu C
derleyicisine geçirip sonucu Go tarafına bağlıyor. Aşağıdaki `yerel.go`, standart
`<string.h>` ve `<ctype.h>` ile bir dizgiyi büyük harfe çeviren küçük bir C işlevi
(`buyukC`) tanımlıyor ve iki Go sarmalayıcısından çağırıyor.

```go
// yerel.go — C dilinde yazilmis bir islevi cgo ile cagiran yerel kutuphane sarmalayicisi
package main

/*
#include <string.h>
#include <stdlib.h>
#include <ctype.h>

char* buyukC(const char* s) {
	size_t n = strlen(s);
	char* sonuc = (char*)malloc(n + 1);
	for (size_t i = 0; i < n; i++) {
		sonuc[i] = toupper((unsigned char)s[i]);
	}
	sonuc[n] = 0;
	return sonuc;
}
*/
import "C"
import "unsafe"

// buyukTemiz, dizgiyi C tarafindaki buyukC islevine gecirip sonucu geri aliyor;
// C tarafinda ayrilan sonuc arabellegini serbest birakiyor.
func buyukTemiz(s string) string {
	cs := C.CString(s)
	defer C.free(unsafe.Pointer(cs))
	sonuc := C.buyukC(cs)
	defer C.free(unsafe.Pointer(sonuc))
	return C.GoString(sonuc)
}

// buyukSizdiran ayni cagriyi yapiyor ama C tarafindaki sonuc arabellegini hic serbest birakmiyor.
func buyukSizdiran(s string) string {
	cs := C.CString(s)
	defer C.free(unsafe.Pointer(cs))
	sonuc := C.buyukC(cs)
	return C.GoString(sonuc)
}
```

`C.CString` verilen Go dizgisinin bir kopyasını **C'nin kendi ayırıcısıyla** (`malloc`)
kuruyor — Go'nun çöp toplayıcısının hiç izlemediği bir bellek alanı. `C.GoString` ters
yönde çalışıyor: C tarafındaki bir baytı okuyup Go'nun öbeğinde yeni bir dizgi kuruyor.
`buyukTemiz` iki C arabelleğini de (`cs`, `sonuc`) `C.free` ile serbest bırakıyor;
`buyukSizdiran` ikincisini hiç bırakmıyor — bu fark ilerleyen bir bölümde ölçülüyor.

Bu dersin geri kalanı, önceki derslerin paylaştığı iki yordamı aynen kullanıyor: `paket`
geçici bir modül kuruyor, `aracCalistir` dilin kendi komutunu bu modül üzerinde
çalıştırıyor.

```go
// araclar.go — bu dersin paylastigi yardimcilar: gecici paket kurma, arac cagirma, bes kosum
package main

import (
	"fmt"
	"os"
	"os/exec"
	"path/filepath"
	"testing"
)

// goster bir bilgiyi, kimin soyledigini ve kanitini tek satirda yazdirir.
func goster(bilgi, soyleyen, kanit string) {
	fmt.Printf("%-40s %-7s %s\n", bilgi, soyleyen, kanit)
}

// paket, verilen dosyalari yeni bir gecici modulun icine yazar; yol hicbir yerde basilmaz.
func paket(dosyalar map[string]string) string {
	dizin, err := os.MkdirTemp("", "yerelkutuphane")
	if err != nil {
		return ""
	}
	os.WriteFile(filepath.Join(dizin, "go.mod"), []byte("module yereldeneme\n\ngo 1.24\n"), 0o644)
	for ad, icerik := range dosyalar {
		os.WriteFile(filepath.Join(dizin, ad), []byte(icerik), 0o644)
	}
	return dizin
}

// aracCalistir dilin kendi komutunu kosturur, cikisin basarili olup olmadigini doner; cikti basilmaz.
func aracCalistir(dizin string, ekOrtam []string, arg ...string) (string, bool) {
	cmd := exec.Command("go", arg...)
	cmd.Dir = dizin
	cmd.Env = append(append(os.Environ(), "GOTOOLCHAIN=local"), ekOrtam...)
	cikti, err := cmd.CombinedOutput()
	return string(cikti), err == nil
}

// besKosum ayni karsilastirmali olcumu bes kez kosturur; ilk deger ve kac ayri deger
// gorulduguyle doner. Kursun kararlilik kalibi: bir sayi ancak boyle kanitlanip yazilir.
func besKosum(f func(*testing.B)) (int64, int) {
	degerler := map[int64]bool{}
	var ilk int64
	for i := 0; i < 5; i++ {
		r := testing.Benchmark(f)
		degerler[r.AllocsPerOp()] = true
		if i == 0 {
			ilk = r.AllocsPerOp()
		}
	}
	return ilk, len(degerler)
}
```

#### Aynı İş, İki Yazım: Ayırma Sayısı

Kursun karşılaştırmalı ölçüm dersinde kurulan kalıp burada aynen sürüyor: aynı işin iki
yazımı, beşer koşumda, yalnız ayırma sayısıyla karşılaştırılıyor. Bir yazım dilin kendi
kodu (`strings.ToUpper`), öteki `buyukTemiz` üzerinden bir yerel kütüphane çağrısı.

```go
// olcum1.go — ayni is iki yazimla: dilin kendi kodu ve yerel kutuphane cagrisi, ayirma sayisiyla karsilastiriliyor
package main

import (
	"fmt"
	"strings"
	"testing"
)

var ornekDizgi = "vida-somun-pul"
var yut string

//go:noinline
func benchGo(b *testing.B) {
	for i := 0; i < b.N; i++ {
		yut = strings.ToUpper(ornekDizgi)
	}
}

//go:noinline
func benchYerel(b *testing.B) {
	for i := 0; i < b.N; i++ {
		yut = buyukTemiz(ornekDizgi)
	}
}

func main() {
	fmt.Println("-- ayni is, iki yazim: ayirma sayisi --")
	goAyirma, goAyri := besKosum(benchGo)
	yerelAyirma, yerelAyri := besKosum(benchYerel)
	goster("dilin kendi kodu (strings.ToUpper)", "kosum", fmt.Sprint(goAyirma, " bes kosumda ayri deger=", goAyri))
	goster("yerel kutuphane cagrisi (cgo)", "kosum", fmt.Sprint(yerelAyirma, " bes kosumda ayri deger=", yerelAyri))
}
```

```
-- ayni is, iki yazim: ayirma sayisi --
dilin kendi kodu (strings.ToUpper)       kosum   1 bes kosumda ayri deger=1
yerel kutuphane cagrisi (cgo)            kosum   1 bes kosumda ayri deger=1
```

**DR20.** İki yazım da aynı sayıya, bire, ulaşıyor — ikisi de beş koşumda değişmiyor. Bu
eşitlik yüzeyde "yerel çağrının bedeli yok" gibi okunabilir, ama `buyukTemiz`'in gövdesi
`strings.ToUpper`'ın yapmadığı iki işi de yapıyor: dizgiyi C'nin ayırıcısında bir kez
kopyalıyor (`CString`), C tarafında ikinci bir arabellek ayırıp dolduruyor (`buyukC`),
ikisini de `free` ile geri veriyor. Ayırma sayısındaki tek `1`, yalnız `GoString`'in Go
öbeğine yazdığı son kopyayı sayıyor — C'nin `malloc` çağrıları bu sayıda hiç yok, çünkü
ölçüm aracı yalnız **kendi** öbeğini izliyor. Bu gözlem ilerleyen bir bölümde tekrar ele
alınıyor.

#### Yerel Çağrının Kaybettirdiği Araç Bilgisi

Kursun `araç` sınıfına yazdığı bilgiler — derlenebilirlik, çapraz derleme, derleyicinin
kararı — bir yerel kütüphane çağrısı eklendiğinde ne kadarını taşıyor? Aşağıdaki ölçüm
`yerelKaynak`ı geçici bir modüle yazıp üç soru soruyor: `CGO_ENABLED=0` ile derlenir mi,
kopyası olmayan bir platforma (`linux/arm64`) çapraz derlenir mi ve kaçış
bildirimlerinin kaçı Go tarafına, kaçı `buyukC`'ye dair.

```go
// olcum2.go — yerel cagri iceren paketin kaybettirdigi arac bilgisi: derlenebilirlik, capraz derleme, kacis siniri
package main

import (
	"fmt"
	"os"
	"strings"
)

const yerelKaynak = "package main\n\n/*\n#include <string.h>\n#include <ctype.h>\n#include <stdlib.h>\n\nchar* buyukC(const char* s) {\n\tsize_t n = strlen(s);\n\tchar* sonuc = (char*)malloc(n + 1);\n\tfor (size_t i = 0; i < n; i++) sonuc[i] = toupper((unsigned char)s[i]);\n\tsonuc[n] = 0;\n\treturn sonuc;\n}\n*/\nimport \"C\"\nimport \"unsafe\"\n\nfunc buyukYerel(s string) string {\n\tcs := C.CString(s)\n\tdefer C.free(unsafe.Pointer(cs))\n\tsonuc := C.buyukC(cs)\n\tdefer C.free(unsafe.Pointer(sonuc))\n\treturn C.GoString(sonuc)\n}\n\nfunc main() {\n\t_ = buyukYerel(\"x\")\n}\n"

func main() {
	dizin := paket(map[string]string{"yerel.go": yerelKaynak})

	fmt.Println("-- yerel cagrinin kaybettirdigi arac bilgisi --")
	_, kapaliDerlendi := aracCalistir(dizin, []string{"CGO_ENABLED=0"}, "build", "-o", os.DevNull, ".")
	goster("CGO_ENABLED=0 ile derlendi mi", "arac", fmt.Sprint(kapaliDerlendi))

	_, caprazDerlendi := aracCalistir(dizin, []string{"CGO_ENABLED=1", "GOOS=linux", "GOARCH=arm64"}, "build", "-o", os.DevNull, ".")
	goster("linux/arm64 hedefinde derlendi mi", "arac", fmt.Sprint(caprazDerlendi))

	cikti, _ := aracCalistir(dizin, []string{"CGO_ENABLED=1"}, "build", "-gcflags=-m", "-o", os.DevNull, ".")
	satirlar := strings.Split(strings.TrimSpace(cikti), "\n")
	goTarafi := 0
	for _, s := range satirlar {
		if strings.Contains(s, "yerel.go") {
			goTarafi++
		}
	}
	cTarafi := strings.Count(cikti, "buyukC")
	goster("toplam kacis/derleme bildirimi satiri", "arac", fmt.Sprint(len(satirlar)))
	goster("yerel.go (go tarafi) satirina dair bildirim", "arac", fmt.Sprint(goTarafi))
	goster("buyukC govdesine (C tarafi) dair bildirim", "arac", fmt.Sprint(cTarafi))
}
```

```
-- yerel cagrinin kaybettirdigi arac bilgisi --
CGO_ENABLED=0 ile derlendi mi            arac    false
linux/arm64 hedefinde derlendi mi        arac    false
toplam kacis/derleme bildirimi satiri    arac    21
yerel.go (go tarafi) satirina dair bildirim arac    11
buyukC govdesine (C tarafi) dair bildirim arac    0
```

**DR21.** `CGO_ENABLED=0` ile paket derlenmiyor, çünkü `import "C"` taşıyan bir dosya
cgo kapalıyken derlemeye hiç girmiyor — Derleme Etiketleri dersinin "dosya derlemeden
dışarıda kalıyor" davranışının bir başka biçimi, ama seçimi bir yorum satırı değil tek
bir ortam değişkeni yapıyor. **DR22.** Çapraz Derleme dersi, platforma özgü çağrı
taşımayan bir kaynağın üç hedefte de derlendiğini ölçmüştü; burada kopyası olmayan
`linux/arm64`'e derleme başarısız oluyor, çünkü artık gereken Go'nun çapraz derleyicisi
değil, o hedefin **kendi** C derleyicisi ve bu ortamda yok.

**DR23.** Yirmi bir kaçış bildiriminin on biri `yerel.go`'daki bir satıra bağlanıyor.
Bildirimlerin **hiçbiri** — sıfırı — `buyukC`'nin adını taşımıyor. Bunun nedeni
derleyicinin bu işlevi görmezden gelmesi değil, hiç **görememesi**: kaçış çözümlemesi
Go'nun söz dizim ağacı üzerinde çalışan bir geçiş, `buyukC`'nin gövdesi ise C. Derleyici
sınıra kadar (`cs` ve `sonuc` işaretçilerine) karar veriyor, ötesinde bir sorusu yok.

#### Sınırlayıcı Ölçüm: Bedel Çağrının Kendisinde Değil

Yukarıdaki ölçümler aynı yöne işaret ediyor: kaçış çözümlemesi sınırın ötesini görmüyor,
derlenebilirlik bir C derleyicisine bağlı, ayırma sayısı yalnız Go'nun öbeğini sayıyor.
Bu son ölçüm üçünü daha keskin bir örnekle birleştiriyor: `buyukTemiz` ile
`buyukSizdiran` arasındaki tek fark, C tarafındaki sonuç arabelleğinin serbest
bırakılıp bırakılmaması — gerçek bir sızıntı, C'nin kendi öbeğinde.

```go
// sinir.go — sinirlayici olcum: bedel cagrinin kendisinde degil, aracin soyleyebildiklerinde
package main

import (
	"fmt"
	"testing"
)

const sizdiranKaynak = "package main\n\n/*\n#include <string.h>\n#include <ctype.h>\n#include <stdlib.h>\n\nchar* buyukC(const char* s) {\n\tsize_t n = strlen(s);\n\tchar* sonuc = (char*)malloc(n + 1);\n\tfor (size_t i = 0; i < n; i++) sonuc[i] = toupper((unsigned char)s[i]);\n\tsonuc[n] = 0;\n\treturn sonuc;\n}\n*/\nimport \"C\"\nimport \"unsafe\"\n\nfunc buyukSizdiran(s string) string {\n\tcs := C.CString(s)\n\tdefer C.free(unsafe.Pointer(cs))\n\tsonuc := C.buyukC(cs)\n\treturn C.GoString(sonuc)\n}\n\nfunc main() {\n\t_ = buyukSizdiran(\"x\")\n}\n"

var yut string
var ornekDizgi = "vida-somun-pul"

//go:noinline
func benchTemiz(b *testing.B) {
	for i := 0; i < b.N; i++ {
		yut = buyukTemiz(ornekDizgi)
	}
}

//go:noinline
func benchSizdiran(b *testing.B) {
	for i := 0; i < b.N; i++ {
		yut = buyukSizdiran(ornekDizgi)
	}
}

func main() {
	fmt.Println("-- sinirlayici olcum: bellek sizintisi kosumda gorunuyor mu --")
	temizAyirma, temizAyri := besKosum(benchTemiz)
	sizdiranAyirma, sizdiranAyri := besKosum(benchSizdiran)
	goster("serbest birakan yazimin ayirma sayisi", "kosum", fmt.Sprint(temizAyirma, " bes kosumda ayri deger=", temizAyri))
	goster("sizdiran yazimin ayirma sayisi", "kosum", fmt.Sprint(sizdiranAyirma, " bes kosumda ayri deger=", sizdiranAyri))

	dizin := paket(map[string]string{"yerel.go": sizdiranKaynak})
	_, temizMi := aracCalistir(dizin, []string{"CGO_ENABLED=1"}, "vet", "./...")
	goster("cozumleyici sizdiran yazimi da temiz buluyor mu", "arac", fmt.Sprint(temizMi))
}
```

```
-- sinirlayici olcum: bellek sizintisi kosumda gorunuyor mu --
serbest birakan yazimin ayirma sayisi    kosum   1 bes kosumda ayri deger=1
sizdiran yazimin ayirma sayisi           kosum   1 bes kosumda ayri deger=1
cozumleyici sizdiran yazimi da temiz buluyor mu arac    true
```

**DR24.** İki sayı birebir aynı — serbest bırakan da sızdıran da bir ayırma, beş
koşumda tek değer. Ayırma sayısı yalnız Go öbeğini izlediği için, C tarafında büyüyen
sızıntı hiç görünmüyor. Çözümleyici de aynı sessizliği veriyor: `vet` sızdıran yazımı da
temiz buluyor, çünkü aradığı kalıplardan hiçbiri bir C çağrısının sonucunu serbest
bırakmamayı kapsamıyor — zaten kapsayamaz, çünkü sızıntı `buyukC`'nin gövdesinde, Go söz
diziminin dışında oluyor. Kursun ölçü ekseni burada kendi sınırına varıyor: üç yanıt da
(kaynak, araç, koşum) bir sınırın içindeydi ve dışındaki C derleyicisinin kararlarına
hiç ulaşmadı. Bedel çağrının kendisinde değil, aracın **söyleyebildiklerinde**: ne
derleyici, ne çözümleyici, ne de profil aracı sınırın ötesindeki kodu görebiliyor.

#### Kurs Kapanışı

Bu kurs on altı ders boyunca tek bir soruyu sürdürdü: bir programa dair bir bilgiyi kim
söylüyor — kaynak mı, araç mı, koşum mu? Ortak tanımın üç iddiası buna üç katman ekledi.
Birincisi, bir programın hakkında bilinebileceklerin çoğu kaynakta yazılı değil: on bir
bilginin yalnız ikisi metinde duruyor, geri kalan dokuzu bir araçtan ya da koşumdan
geliyor. İkincisi, aracın söylediği ile koşumun söylediği aynı güvenilirlikte değil —
araç bilgisi koşum gerektirmiyor, her çağrıda aynı yanıtı veriyor; koşum bilgisinin aynı
kalıp kalmadığı ayrıca sınanmak zorunda. Üçüncüsü, kursa özgü olanı: karşılaştırmalı
ölçümün iki sayısından yalnız biri, ayırma sayısı, tekrarlanabilir; süre hiçbir derste
yazılmadı, basılmadı, ölçülmedi.

Bu son ders aynı üç sınıfı dilin sınırının ötesine taşıdı: kaçış çözümlemesi C
işlevinin gövdesine dair bir şey söylemiyor, çözümleyici C'deki bir sızıntıyı görmüyor,
ayırma sayısı C'nin ayırıcısını saymıyor. Kursun "araç söylüyor, doğru söylüyor demek
değildir" okuması burada en keskin biçimine ulaşıyor: sınırın ötesinde araç ve koşum
ikisi de susuyor ve bu susma kusursuzluk kanıtı değil, aracın hiç bakmadığı bir yerin
kanıtı.

Kurs aşırı genelleme yapmadan kapanıyor: ölçülen tek bir yerel kütüphane çağrısıydı,
cgo'nun bütün düzeneği değil; aynı sınır başka bir geçişte farklı bir araçla
görünebilir. M09 Go müfredatı da bu dersle kapanıyor ve beş kursun beş sorusu aynı
kökten çıkıyor: Go Temelleri yazılmayanın karşılığını sordu — tanımlı mı, reddedildi mi,
açıkta mı; Arayüzler, Hatalar ve Genellikler bir davranışın tipe bağının nerede
kurulduğunu sordu; Eşzamanlılık bir goroutine'in nasıl duracağını sordu; Standart
Kütüphane ve Ağ Uygulamaları bir değerin sınırı geçerken tip bilgisine ne olduğunu
sordu; bu kurs da bilginin hangi kaynaktan geldiğini sordu. Beşi de aynı gözlemde
birleşiyor: bir programı yalnız kaynağını okuyarak bilmek yetmiyor.

| Ders | Ölçülen bilgi | Söyleyen | Sınırlayıcı ölçüm |
|---|---|---|---|
| Test Paketi Temelleri | Testin geçip geçmediği | koşum | Geçen test sınadığının tuttuğunu söyler, doğruyu değil |
| Tablo Güdümlü Testler | Vaka eklemenin kapsamayı değiştirip değiştirmediği | koşum | Altıncı vaka kapsamayı %100'de sabit bıraktı |
| HTTP Testleri | İsteğin hangi durum koduna dönüştüğü | koşum | İstek adresi sabit bir blokta — sınanan ağ değil işleyici |
| Sahteleme ve Arayüz Ayrımı | Sahtelemeyi mümkün kılan arayüz ayrımı | kaynak | Aşırı azaltmada gerçek reddetti, sahte kabul etti |
| Karşılaştırmalı Ölçüm | On bir bilginin dağılımı, ayırma sayısı | kaynak · araç · koşum (2/5/4) | İş başına ile toplam ayırma ayrı, karışırsa yanıltır |
| Kapsam Raporlama | Üç test kümesinin verdiği kapsama oranı | koşum | Denetlemeyen bir vaka hatalı kaynakla da %100 verdi |
| Profilleme | İki çağrı biçiminin ayırma sayısı | koşum | Koşmayan dalın ayırdığı değer profilde görünmüyor |
| Kaçış Çözümlemesi | Değerin yığıtta mı öbekte mi ayrıldığı kararı | araç | Karar dilin sözü değil, derleyicinin gerçekleştirim ayrıntısı |
| Çöp Toplama Davranışı | Üç tekniğin ayırma sayısına etkisi | koşum | Aynı sayıya okunaklı ve okunaksız iki yazımla ulaşıldı |
| İzleme | İki kurulumun olay sayısı | koşum | İz toplamak davranışı değiştirir, sayılar bu sınırla okunur |
| Derleme Etiketleri | Hangi dosyaların derlemeye girdiği | araç | Dışarıdaki dosya silinmez, etiketlenene dek görülmez |
| Çapraz Derleme | Kaynağın üç hedef için derlenip derlenmediği | araç | Derlenmek çalışacağı anlamına gelmez — varsayım hedefe bağlı |
| Bağlayıcı Seçenekleri | Kaynaksız bir değerin ikiliye gömülmesi | araç | Bağlayıcı gömülen değeri doğrulamaz, boşu da kabul eder |
| Modül Yayımlama | Sürümün imza düzeyinde uyumlu olup olmadığı | kaynak · araç | Aynı imza farklı sonuç verebilir, söz imzada davranışta değil |
| Statik Çözümleme ve Güvenlik Denetimi | Farkın çözümleyicide yakalanıp yakalanmadığı | araç | Temiz çıkan kalıp koşunca çöktü — temizlik kusursuzluk değil |
| C ile Birlikte Çalışma | Yerel çağrının ayırma sayısı, derlenebilirliği, kaçış görünürlüğü | araç · koşum | Sızan C belleği ne çözümleyicide ne ayırmada görünür |

#### Özet

- İki yazım — dilin kendi kodu, cgo ile çağrılan bir C işlevi — aynı ayırma sayısına
  (bir) ulaştı, ama bu eşitlik C tarafındaki iki `malloc` çağrısını hiç saymadı.
- Yerel çağrı içeren bir paket `CGO_ENABLED=0` ile derlenmedi, C derleyicisi bulunmayan
  bir hedefe çapraz derlenmedi; ders bu bağımlılığı açık bir varsayım olarak taşıdı.
- Kaçış çözümlemesinin yirmi bir bildiriminin on biri Go tarafına bağlandı, sıfırı C
  işlevinin gövdesine — derleyici sınırın ötesini göremiyor, göz ardı etmiyor.
- Sınırlayıcı ölçüm: serbest bırakılmayan C belleği ne çözümleyicide ne ayırma
  sayısında görünüyor; bedel çağrının kendisinde değil, aracın söyleyebildiklerinde.
- M09 Go müfredatı bu dersle kapanıyor; beş kursun beş sorusu — yazılmayanın karşılığı,
  bağın yeri, goroutine'in durması, sınırda tip bilgisi, bu bilgiyi kim söylüyor — aynı
  gözlemde birleşiyor: bir programı bilmek, kaynağını okumaktan fazlasını gerektiriyor.

