---
title: 'Standart Kütüphane ve Ağ Uygulamaları'
source: 'https://academia.sh/tr/kurslar/go-uygulamalar'
language: tr
updated: '2026-08-23T19:49:13+00:00'
license: 'CC BY-SA 4.0'
lessons: 12
outcomes:
    - 'Standart kütüphaneyle HTTP servisi ve komut satırı aracı yazar'
    - 'Dizileştirme, günlükleme ve zaman işlemlerini doğru kullanır'
    - 'Veritabanı erişimini bağlantı yönetimiyle kurar'
    - 'Bir değerin sınırı geçerken tip bilgisine ne olduğunu ölçerek ayırt eder'
---

# Standart Kütüphane ve Ağ Uygulamaları

Bu kursun on iki dersi tek bir şeyi anlatır: programın sınırlarını. Ağ, dosya, şablon çıktısı, veritabanı satırı, günlük kaydı ve komut satırı argümanı aynı olayın ayrı yüzleridir — bir değer tip sisteminden çıkar ve karşı tarafta yeniden belirir. Kurs her geçişi tek bir soruyla ölçer: tip bilgisi korunuyor mu, yeniden mi kuruluyor, yoksa kayboluyor mu?

## Ağ ve Veri Biçimleri

İstek işleyicilerinin ve ara katman kalıbının standart kitaplıkta kurulması, istemci tarafında zaman aşımı ve gövde kapatma sorumluluğu, yapı etiketleriyle alan adlarının karşıya taşınması, şablonların yalnız metin üretmesi, iki tarafın paylaştığı bir tarifle sözleşme temelli iletişim, ve uzun süreli bağlantılarda çerçevelemenin taşıdığı tek ortak bilgi.

### HTTP Sunucusu

Source: https://academia.sh/tr/kurslar/go-uygulamalar/http-sunucusu

Kursun açılış dersi. Bir isteğin taşıdığı yol, üstbilgi ve gövdenin üçünün de tip bilgisi taşımadığı `net/http/httptest` ile gerçek bağlantı noktası açmadan ölçülüyor; ara katman kalıbının bir işleyiciyi saran başka bir işleyici olduğu üç halkalı bir zincirle koşturuluyor. Sınırlayıcı ölçüm: içerik tipi üstbilgisi bir ipucudur, tip değil — yanlış bir ipucu sunucu tarafında hiçbir hata üretmiyor, hata yalnızca gövdeyi o ipucuna göre çözmeye çalışan tarafta ortaya çıkıyor.

Önceki kursun kapanışı bir goroutine'in nasıl duracağını ölçtü ve durmanın çoğunlukla
**başka birinin yaptığı bir işe** bağlı olduğunu gösterdi: bir kanal kapanmadan, bir bağlam
iptal edilmeden, bekleyen goroutine kendi kararını veremiyordu. O ölçüm hep programın
**içindeydi** — iki goroutine aynı ikili çalıştırılabilir dosyanın parçasıydı, aynı tip
sistemine bağlıydı. Bu ders soruyu programın **dışına** taşıyor: bir değer ağa, dosyaya,
şablona ya da veritabanına gittiğinde ne oluyor? Karşı taraf aynı derleyiciden geçmedi, aynı
tip tanımlarını görmedi — bazen aynı dilde bile yazılmadı. Kursun tamamı bu tek soruyu on iki
farklı sınırda soruyor: **bir değer sınırı geçerken tip bilgisine ne oluyor?** Bu ders sınırın
en sık geçileni ile başlıyor: bir HTTP isteği.

Bir sunucunun ne iş yaptığı, kimin isteği ne zaman işlediği ve ara katmanların bir zincirde
nasıl sıralandığı önceki kurslarda kavram olarak kuruldu. Burada tekrarlanmıyor; bu dersin
sorduğu soru farklı — sunucunun **görevi** değil, sunucuya **gelen ve sunucudan çıkan verinin**
tip sistemiyle ilişkisi. Bu ayrım kursun geri kalanında da sürüyor: her ders bir katmanın
görevini değil, o katmanın sınırındaki verinin akıbetini ölçüyor.

#### İşleyicinin Aldığı Şey: Yol, Üstbilgi, Gövde

Bir istemci bir HTTP isteği gönderdiğinde, isteğin içinde ne olduğuna dair tek kaynak
kablodan geçen bayt dizisidir. Go'nun standart kitaplığı bu bayt dizisini ayrıştırıp bir
`*http.Request` değerine dönüştürüyor, ama dönüştürülen şey hâlâ üç ham parça: **yol**
(`r.URL.Path`, bir dizgi), **üstbilgiler** (`r.Header`, dizgiden dizgi dilimine bir sözlük)
ve **gövde** (`r.Body`, bayt akıtan bir okuyucu). İsteği gönderen tarafın kafasında yol bir
tamsayı kimlik, üstbilgi bir yapılandırılmış değer, gövde bir kayıt olabilir — ama sınırı
geçtikten sonra elde kalan yalnızca dizgi ve bayttır. Go'nun tip sistemi bu üçünün hiçbirine
"bu aslında bir tamsayıydı" ya da "bu aslında bir yapıydı" demiyor; söylemeye yetkisi de yok,
çünkü kablonun karşı ucunda hangi programın çalıştığını, o programın hangi tipleri
tanımladığını bilmiyor.

Aşağıdaki kaynak bu ölçümün ortak parçalarını kuruyor: sonuçları sınıflandırıp yazdıran bir
yardımcı, isteğin yolunu ve bir üstbilgisini okuyup gövdenin uzunluğunu yanıt olarak yazan bir
çekirdek işleyici ve bu çekirdeği saran iki ara katman.

```go
// istekcekirdek.go — istek isleyicisinin ve ara katmanin ortak parcalari
package main

import (
	"fmt"
	"io"
	"net/http"
)

var parcaSayisi, kayboluyorSayisi int
var sonOkunanGovde []byte

// olcumSatiri, bu dersin sinif adlarindan birini (korunuyor / yeniden / kayboluyor)
// bir kanitla birlikte yazdirir ve toplami tutar.
func olcumSatiri(parca, sinif, kanit string) {
	parcaSayisi++
	if sinif == "kayboluyor" {
		kayboluyorSayisi++
	}
	fmt.Printf("%-30s %-10s %s\n", parca, sinif, kanit)
}

// cekirdekIsleyici, gelen istegin yolunu, bir ustbilgisini ve govdesini okuyup
// duz metin bir yanit yazar. Yanita sabit bir icerik tipi ipucu ekler; bu ipucunun
// dogru olup olmadigini denetlemek bu islevin isi degildir.
func cekirdekIsleyici(w http.ResponseWriter, r *http.Request) {
	govde, _ := io.ReadAll(r.Body)
	sonOkunanGovde = govde
	kimlik := r.Header.Get("X-Istek-Kimligi")
	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(http.StatusOK)
	fmt.Fprintf(w, "yol=%s kimlik=%s govde-uzunlugu=%d", r.URL.Path, kimlik, len(govde))
}

// araGunlukleme, sardigi isleyiciyi cagirmadan once bir sayaci artirir; govdeye
// ya da baglantiya dokunmuyor.
func araGunlukleme(sonraki http.Handler, sayac *int) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		*sayac++
		sonraki.ServeHTTP(w, r)
	})
}

// araBasliklandirma, yanita sabit bir ustbilgi ekleyip zinciri surdurur.
func araBasliklandirma(sonraki http.Handler, sayac *int) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		*sayac++
		w.Header().Set("X-Zincir-Adimi", "gecti")
		sonraki.ServeHTTP(w, r)
	})
}
```

Ölçüm bu çekirdeği bir istekle koşturuyor. İstek `net/http/httptest` ile kuruluyor —
`httptest.NewRequest` gerçek bir bağlantı noktası açmadan bellekte bir `*http.Request`
üretiyor, `httptest.NewRecorder` ise gerçek bir bağlantı yerine yanıtı bir arabelleğe
yazan sahte bir `http.ResponseWriter` veriyor. İstemci ile sunucu arasında hiçbir gerçek
soket, adres ya da süre yok.

```go
// istekolcumu.go — bir istegin tasidigi parcalarin tip bilgisi ve ara katman zinciri
package main

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

func main() {
	fmt.Println("-- isleyicinin aldigi sey: bayt ve metin --")

	govde := bytes.NewBufferString("adet=3")
	istek := httptest.NewRequest(http.MethodPost, "/urunler/42", govde)
	istek.Header.Set("X-Istek-Kimligi", "1001")

	var gunlukSayaci, basliklandirmaSayaci int
	zincir := araGunlukleme(
		araBasliklandirma(http.HandlerFunc(cekirdekIsleyici), &basliklandirmaSayaci),
		&gunlukSayaci,
	)

	kaydedici := httptest.NewRecorder()
	zincir.ServeHTTP(kaydedici, istek)

	olcumSatiri("istek yolu (r.URL.Path)", "kayboluyor",
		fmt.Sprintf("deger=%q tip=%T", istek.URL.Path, istek.URL.Path))
	olcumSatiri("istek ustbilgisi (Header.Get)", "kayboluyor",
		fmt.Sprintf("deger=%q tip=%T", istek.Header.Get("X-Istek-Kimligi"), istek.Header.Get("X-Istek-Kimligi")))
	olcumSatiri("istek govdesi (okunan bayt)", "kayboluyor",
		fmt.Sprintf("tip=%T uzunluk=%d", sonOkunanGovde, len(sonOkunanGovde)))

	fmt.Println("-- ara katman zinciri --")
	fmt.Println("zincir: araGunlukleme( araBasliklandirma( cekirdekIsleyici ) ) - 3 halka")
	fmt.Printf("gunlukleme cagrisi=%d basliklandirma cagrisi=%d (ikisi de bu dersin yazdigi kod)\n",
		gunlukSayaci, basliklandirmaSayaci)
	fmt.Printf("durum kodu=%d zincir ustbilgisi=%s\n", kaydedici.Code, kaydedici.Header().Get("X-Zincir-Adimi"))
	fmt.Println("yanit govdesi:", kaydedici.Body.String())

	fmt.Printf("bu istegin %d parcasindan tip bilgisi tasiyani=%d (kayboluyor=%d)\n",
		parcaSayisi, parcaSayisi-kayboluyorSayisi, kayboluyorSayisi)
}
```

```
-- isleyicinin aldigi sey: bayt ve metin --
istek yolu (r.URL.Path)        kayboluyor deger="/urunler/42" tip=string
istek ustbilgisi (Header.Get)  kayboluyor deger="1001" tip=string
istek govdesi (okunan bayt)    kayboluyor tip=[]uint8 uzunluk=6
-- ara katman zinciri --
zincir: araGunlukleme( araBasliklandirma( cekirdekIsleyici ) ) - 3 halka
gunlukleme cagrisi=1 basliklandirma cagrisi=1 (ikisi de bu dersin yazdigi kod)
durum kodu=200 zincir ustbilgisi=gecti
yanit govdesi: yol=/urunler/42 kimlik=1001 govde-uzunlugu=6
bu istegin 3 parcasindan tip bilgisi tasiyani=0 (kayboluyor=3)
```

**AG1.** Yolun `"/urunler/42"` olması, `42` rakamının bir tamsayı **anlamına gelmesi**
gerektiğini düşündürüyor — kaynak kodda bu değer bir ürün kimliğiydi. Ama ölçüm `%T` ile
sorduğunda yanıt tektir: `string`. Go'nun yönlendiricisi yolu ayrıştırırken hiçbir rakam
dizisini otomatik olarak sayıya çevirmiyor; `/urunler/42` ile `/urunler/kirk-iki` aynı tipte
iki dizgidir, aralarındaki fark yalnızca içerik. Aynı gözlem üstbilgi için de geçerli: istek
kimliği üstbilgisindeki `"1001"` değeri de bir dizgi, isteyen tarafın kafasında bir sayısal
kimlik olsa bile. Kablonun üzerinden geçen hiçbir şey kendi tipini taşımıyor; taşıyan tek şey
karakterler.

**AG2.** Gövde bu üçünün en açık örneği: `io.ReadAll(r.Body)` her zaman `[]byte` döndürüyor,
gövdenin içeriği ister bir sayı, ister bir yapı, ister rastgele metin olsun. Ölçümde
`uzunluk=6` çıkıyor çünkü `"adet=3"` dizgisi altı bayt — ama bu uzunluk gövdenin **neyi**
temsil ettiğini söylemiyor, yalnızca **kaç bayt** olduğunu söylüyor. Bu üç satırın üçü de
`kayboluyor` sınıfında; bu dersin ölçtüğü isteğin üç parçasından tip bilgisi taşıyanı sıfır.
Kursun on üç sınır geçişinin tam dağılımı (1 korunuyor, 5 yeniden kuruluyor, 7 kayboluyor)
JSON Dizileştirme dersinde kuruluyor; bu ders yalnız kendi diliminin üç satırını basıyor.

#### Ara Katman: Bir İşleyiciyi Saran Başka Bir İşleyici

`http.Handler`, Go'nun standart kitaplığında tek bir yöntemle tanımlanan bir arayüz:
`ServeHTTP(http.ResponseWriter, *http.Request)`. Bu küçüklük bilinçli bir tasarım kararı —
bir işleyici, bu tek yöntemi gerçekleştiren **her şey** olabiliyor, dolayısıyla bir işleyiciyi
başka bir işleyicinin içine gömmek özel bir çerçeve gerektirmiyor, sıradan bir fonksiyon
çağrısı yeterli. `araGunlukleme` ve `araBasliklandirma` işlevlerinin ikisi de aynı kalıbı
izliyor: bir `http.Handler` alıyor, kendi mantığını çalıştıran yeni bir `http.HandlerFunc`
döndürüyor ve döndürdüğü işleyicinin gövdesinde aldığı işleyicinin `ServeHTTP` yöntemini
çağırıyor. Sonuç bir **zincir**: dıştaki halka önce çalışıyor, işini yapıp içteki halkayı
çağırıyor, o da kendi işini yapıp bir sonrakini çağırıyor — en içte çekirdek işleyici durana
kadar.

Bu zincir kaynak kodda `main` işlevindeki tek bir ifadeyle, `araGunlukleme` çağrısının
içine `araBasliklandirma` çağrısının, onun içine de çekirdek işleyicinin konulmasıyla elle
kuruluyor: üç halka, üçü de bu dersin yazdığı kod, hiçbiri bir kayıt listesinden ya da bir
etiketten otomatik olarak
keşfedilmiyor. Çıktıdaki `gunlukleme cagrisi=1` ve `basliklandirma cagrisi=1` satırları bunu
doğrudan sayıyor: zincirdeki iki ara katmanın ikisi de tam olarak bir kez çağrıldı, çünkü
istek de tam olarak bir kez geldi. Sunucunun görevi, istek işleme zincirinin genel yapısı ve
ara katmanın neden var olduğu önceki kurslarda kavram olarak kuruldu; burada ölçülen şey o
kavramın Go'nun standart kitaplığındaki somut karşılığı — bir işleyicinin başka bir işleyiciyi
sarması dışında hiçbir gizli düzenek yok.

**AG3.** Zincirin sırası sonucu etkiliyor. `araBasliklandirma`, `X-Zincir-Adimi` üstbilgisini
kendi `ServeHTTP` gövdesinde, iç işleyiciyi çağırmadan **önce** ayarlıyor; ama bu üstbilgi yanıta
yalnızca `w.WriteHeader` çağrıldığı anda kilitleniyor ve `WriteHeader`i çağıran
`cekirdekIsleyici`. Yani `araBasliklandirma`nın eklediği üstbilgi, çekirdek henüz yanıtı yazmadan
önce ayarlandığı için başarıyla yanıta giriyor — çıktıdaki `zincir ustbilgisi=gecti` satırı bunu
doğruluyor. Sıra tersine çevrilseydi, yani `araBasliklandirma` çekirdekten **sonra** çalışsaydı,
`WriteHeader` zaten çağrılmış olacağı için üstbilgi sessizce yok sayılırdı — Go bu durumda hata
vermiyor, yalnızca üstbilgiyi görmezden geliyor — durum kodu 1xx sınıfında olmadıkça ve üstbilgi
bir art bilgi (trailer) olarak işaretlenmedikçe.

**AG4.** İki ara katmanın da imzası aynı: bir `http.Handler` ve bir sayaç işaretçisi alıp
yeni bir `http.Handler` döndürüyorlar. Bu tekdüzelik, üçüncü bir ara katmanın aynı zincire
eklenmesini tek satırlık bir sarmalamaya indiriyor — zincire yeni bir halka eklemek, listeye
yeni bir eleman eklemekten farklı bir işlem değil, sadece bir fonksiyon çağrısını bir
öncekinin içine koymak. Ama bu kolaylığın bir bedeli var: zincir ne kadar uzarsa, bir isteğin
`cekirdekIsleyici`ye ulaşana kadar kaç halkadan geçtiğini görmek o kadar çok, iç içe geçmiş
fonksiyon çağrısını okumayı gerektiriyor. Zincirin kendisi hiçbir yerde bir liste olarak
durmuyor — yalnızca `main` işlevindeki tek bir ifade.

Bu, zincirin **tip bilgisiyle** ilişkisini de belirliyor: `araGunlukleme` ve
`araBasliklandirma` işlevlerinin ikisi de kendi işlerini görmek için isteğin ya da yanıtın
içeriğine hiç bakmıyor, yalnızca `*http.Request` ve `http.ResponseWriter` değerlerini
olduğu gibi bir sonraki halkaya aktarıyor. Bir ara katman isteğin gövdesindeki bir alanı
okumak isteseydi, bu ders boyunca görülen aynı sınırla karşılaşırdı — gövde yine yalnızca
bayt, ara katman da yalnızca kendi yazdığı ayrıştırma koduyla o baytlara bir anlam
yükleyebilirdi.

#### Sınırlayıcı Ölçüm: İçerik Tipi Bir İpucudur, Tip Değil

Çekirdek işleyicinin yanıtına eklediği `Content-Type: application/json` üstbilgisi, ilk
bakışta yanıt gövdesinin tipini garanti ediyormuş gibi görünüyor. Değil. Bu üstbilgi bir
dizgiden başka bir şey değil ve onu kim okursa okusun, gövdenin gerçekten o biçimde olup
olmadığını denetleyen hiçbir mekanizma araya girmiyor. Aşağıdaki ölçüm bunu bilerek kırıyor:
işleyici `application/json` ipucu veriyor ama gövdeye düz metin yazıyor. Bu, önceki ölçümdeki
üç `kayboluyor` satırından farklı bir gözlem: oradaki satırlar isteğin taşıdığı bilginin
**hiç var olmadığını** ölçtü, buradaki ölçüm ise var olan bir bilginin (üstbilginin kendisi)
**yanlış olabileceğini** ve bunun hiçbir yerde yakalanmadığını gösteriyor.

```go
// yanlisipucu.go — icerik tipi bir ipucudur, hatasi kosarken degil cozerken cikar
package main

import (
	"encoding/json"
	"fmt"
	"net/http"
	"net/http/httptest"
)

// yalanciIsleyici, govdeye duz metin yazip ustbilgide "application/json" ipucu verir.
// Ipucu ile govde arasindaki uyumu denetleyen hicbir kod yoktur.
func yalanciIsleyici(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(http.StatusOK)
	fmt.Fprint(w, "bu govde json degil, duz metin")
}

func main() {
	fmt.Println("-- icerik tipi bir ipucudur, tip degil --")

	istek := httptest.NewRequest(http.MethodGet, "/rapor", nil)
	kaydedici := httptest.NewRecorder()
	http.HandlerFunc(yalanciIsleyici).ServeHTTP(kaydedici, istek)

	fmt.Printf("durum kodu=%d (ipucu yanlisken bile basarili)\n", kaydedici.Code)
	fmt.Printf("ustbilgideki ipucu=%s\n", kaydedici.Header().Get("Content-Type"))

	var hedef map[string]any
	cozHatasi := json.Unmarshal(kaydedici.Body.Bytes(), &hedef)
	fmt.Printf("govdeyi json olarak cozme hatasi var mi=%t\n", cozHatasi != nil)
	fmt.Println("hata sunucuda degil, govdeyi ipucuna gore cozmeye calisan tarafta cikiyor")
}
```

```
-- icerik tipi bir ipucudur, tip degil --
durum kodu=200 (ipucu yanlisken bile basarili)
ustbilgideki ipucu=application/json
govdeyi json olarak cozme hatasi var mi=true
hata sunucuda degil, govdeyi ipucuna gore cozmeye calisan tarafta cikiyor
```

**AG5.** Durum kodu `200` — yani işleyici, gövdenin ipucuyla uyuşmadığını hiç fark etmeden
başarıyla tamamlandı. `http.ResponseWriter` arayüzünde `Content-Type` üstbilgisini gövdeyle
karşılaştıran, ya da gövdenin gerçekten geçerli JSON olup olmadığını denetleyen hiçbir
adım yok — üstbilgi, alıcıya "bu gövdeyi böyle yorumlamayı dene" diyen bir öneri, alıcının
uyması gereken bir kural değil. Hata ancak bir taraf bu öneriye güvenip `json.Unmarshal`
çağırdığında ortaya çıkıyor; ölçümde bu çağrı `main` işlevinin kendisinde yapılıyor ve
`cozHatasi != nil` sonucu `true` çıkıyor. Sunucu tarafında hiçbir uyarı, hiçbir günlük satırı,
hiçbir hata dönüşü yok — sınır geçişinin kendisi tamamen sessiz.

**AG6.** Bu gözlem, dersin baştaki iddiasını netleştiriyor: sınırın ötesine geçen bir yanıtın
yanında taşıdığı **tek** ipucu böyle bir üstbilgidir ve bu ipucu bir tip değil, bir dizgidir
— aynı `r.URL.Path` ya da `r.Header.Get` gibi, `%T` ile sorulduğunda yine `string` çıkıyordu.
İçerik tipi üstbilgisinin doğru olması, sunucuyu yazan tarafın disiplinine kalmış bir şey;
Go'nun derleyicisi ya da çalışma zamanı bu disiplini zorlamıyor. Bu sınırlayıcı ölçüm,
gövdenin kendisinin taşıdığı bilgiyle üstbilgide yazan bilginin **birbirinden bağımsız** iki
şey olduğunu, ikisinin birbirini doğrulamadığını gösteriyor.

Standart kitaplığın kendisi bu ikisini birbirine bağlayan hiçbir denetim eklemiyor; ekleyecek
olan, işleyiciyi yazan taraf. `cekirdekIsleyici` isteseydi, `w.Write` çağrısından önce
gövdenin gerçekten geçerli JSON olduğunu kendi kodunda doğrulayabilirdi — ama bu doğrulama
dilin bir parçası değil, yazarın bir kararı. Sınırın bu tarafında kalan tek garanti, üstbilgi
ile gövde arasındaki tutarlılığı **kimsenin otomatik olarak sağlamadığıdır**; bu tutarlılığı
kuran her katman, kendi kodunda bunu açıkça yapmak zorunda.

#### Özet

- Bir HTTP isteğinin yolu, üstbilgileri ve gövdesi Go'nun standart kitaplığında sırasıyla
  dizgi, dizgi ve bayt dilimi olarak temsil ediliyor; üçü de `%T` ile sorgulandığında hiçbir
  özel tip taşımadığı görülüyor.
- Ölçüm `net/http/httptest` ile gerçek bir bağlantı noktası açmadan kuruluyor:
  `httptest.NewRequest` bellekte bir istek üretiyor, `httptest.NewRecorder` yanıtı bir
  arabelleğe yazan sahte bir yanıt yazıcı sağlıyor.
- Ara katman kalıbı, `http.Handler` arayüzünün tek yöntemli olması sayesinde bir işleyiciyi
  başka bir işleyicinin içine sarmaktan ibaret; zincirdeki her halka çağıranın yazdığı sıradan
  bir fonksiyon çağrısı, otomatik keşif ya da kayıt yok.
- Ara katmanların çalışma sırası sonucu etkiliyor: bir üstbilgi ancak `WriteHeader`
  çağrılmadan önce eklenirse yanıta giriyor, sıra tersine dönerse üstbilgi hatasızca yok
  sayılıyor.
- İçerik tipi üstbilgisi bir ipucu, bir garanti değil: yanlış bir ipucu sunucu tarafında
  hiçbir hata üretmiyor, hata yalnızca gövdeyi o ipucuna güvenerek çözmeye çalışan tarafta
  ortaya çıkıyor.
- Kursun on üç sınır geçişinin tam dağılımı bu derste kurulmuyor; bu ders yalnız isteğin
  sunucu tarafındaki üç parçasını ölçüyor, tam tabloyu JSON Dizileştirme dersi kuruyor.

#### Sonraki Adım

Bu ders, sınırı **alıcı** tarafından ölçtü: bir isteğin sunucuya ne getirdiğini ve sunucunun
gövdeye ne kadar körü körüne güvendiğini. Sıradaki ders aynı sınırı **gönderen** taraftan
soruyor: bir istemci bir isteği gönderirken neyi garanti etmek zorunda ve bir yanıtı aldıktan
sonra hangi sorumlulukları devralıyor. Bir yanıt gövdesi de bu dersteki istek gövdesi gibi
bir bayt akışı, ama bu kez akışı kimin ne zaman kapatacağı ve zaman aşımının bir süre yerine
neyle kurulduğu ölçülüyor.

### HTTP İstemcisi

Source: https://academia.sh/tr/kurslar/go-uygulamalar/http-istemcisi

Önceki ders sınırı alıcı taraftan ölçtü; bu ders aynı sınırı gönderen taraftan ölçüyor. Zaman aşımı bir süreyle değil, iptal edilmiş bir bağlamla kuruluyor. Bağlantının yeniden kullanılıp kullanılmadığı yanıt gövdesinin kapatılıp kapatılmadığına bağlı: gövde okunup kapatılınca sonraki istek aynı bağlantıyı kullanıyor, kapatılmayınca kullanmıyor. Sınırlayıcı ölçüm: yanıt gövdesi bir okuyucudur, bir değer değil — bir kez okunduktan sonra ikinci okuma boş dönüyor.

Önceki ders sınırı **alıcı** taraftan ölçtü: bir isteğin sunucuya ne getirdiğini ve sunucunun
gövdeye ne kadar körü körüne güvendiğini. Bu ders aynı sınırı **gönderen** taraftan soruyor.
Bir istemci bir isteği gönderirken üç sorumluluğu üstleniyor: isteğin makul bir sürede
sonuçlanmasını sağlamak, aynı sunucuyla konuşan bağlantıları yeniden kullanmak ve aldığı
yanıtın gövdesini kapatmak. Bu üçü birbirinden bağımsız görünse de, aşağıdaki ölçüm ikisinin
(bağlantı yeniden kullanımı ile gövde kapatma) aslında **aynı kararın** iki yüzü olduğunu
gösteriyor.

Bu ders de bir önceki gibi `net/http/httptest` kullanıyor; ama bu kez `httptest.NewRecorder`
değil, `httptest.NewServer` — istemcinin gerçekten bir bağlantı kurup kapatabileceği,
bellekte çalışan bir sunucu. Sunucunun adresi (`sunucu.URL`) yalnızca istek kurmak için
kullanılıyor, hiçbir ölçüm satırında basılmıyor; ölçülen şey adresin kendisi değil, istemcinin
bu adrese karşı nasıl davrandığı. Bu farkın kendisi ölçümün bir parçası: önceki ders sunucuyu
hiç ağa bağlamadan koşturabiliyordu, çünkü ölçtüğü şey yalnızca isteğin ve yanıtın içeriğiydi;
bu ders istemcinin bağlantı yönetimini ölçtüğü için gerçek (ama hiçbir yerde basılmayan) bir
yerel bağlantıya ihtiyaç duyuyor.

Üç sorumluluk sırayla ele alınıyor. İlki, bir isteğin sonsuza kadar beklemeyeceğinin
garantisi. İkincisi, art arda gelen isteklerin her birinde yeni bir bağlantı kurmanın
maliyetinden kaçınmak. Üçüncüsü, alınan yanıtın gövdesini bırakmadan kapatmak — ve aşağıdaki
ölçüm bu üçüncü sorumluluğun aslında ikinciyi mümkün kılan şart olduğunu gösteriyor.

#### Zaman Aşımı: Süre Değil, İptal Edilmiş Bir Bağlam

Önceki kursun bağlam paketi dersinde kurulan kalıp burada aynen sürüyor: bir zaman aşımı
`time.Sleep` ya da gerçek bir süre ölçümüyle değil, önceden **iptal edilmiş bir bağlamla**
kuruluyor. Bu, dersin çıktısının koşumdan koşuma değişmemesini sağlıyor — gerçek bir zaman
aşımı denenseydi, isteğin ne kadar sürede başarısız olacağı makineye ve yüke bağlı kalırdı.

```go
// sunucucekirdek.go — istemci olcumlerinin ortak sunucu govdesi
package main

import (
	"fmt"
	"net/http"
)

// yaniticiIsleyici, her istege sabit bir metin govde donduren bir isleyicidir.
// Govde kucuk tutuluyor ki okunup kapatilmasi olcumu etkilemesin.
func yaniticiIsleyici(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "text/plain")
	fmt.Fprint(w, "adet=7 birim=kg")
}

// olcumSatiri, sinirdaki tip bilgisinin uc sonucundan birini (korunuyor / yeniden /
// kayboluyor) bir kanitla birlikte yazdirir.
func olcumSatiri(parca, sinif, kanit string) {
	fmt.Printf("%-30s %-10s %s\n", parca, sinif, kanit)
}
```

```go
// zamanasimi.go — zaman asimi sureyle degil, iptal edilmis bir baglamla kuruluyor
package main

import (
	"context"
	"errors"
	"fmt"
	"net/http"
	"net/http/httptest"
)

func main() {
	fmt.Println("-- zaman asimi: sureyle degil, iptal edilmis bir baglamla --")

	sunucu := httptest.NewServer(http.HandlerFunc(yaniticiIsleyici))
	defer sunucu.Close()

	baglam, iptalEt := context.WithCancel(context.Background())
	iptalEt() // istek daha baslamadan baglam zaten iptal edildi

	istek, _ := http.NewRequestWithContext(baglam, http.MethodGet, sunucu.URL, nil)
	_, hata := http.DefaultClient.Do(istek)

	fmt.Printf("istek hatasi var mi=%t\n", hata != nil)
	fmt.Printf("hata baglam iptaliyle mi eslesiyor=%t\n", errors.Is(hata, context.Canceled))
}
```

```
-- zaman asimi: sureyle degil, iptal edilmis bir baglamla --
istek hatasi var mi=true
hata baglam iptaliyle mi eslesiyor=true
```

**AG7.** `iptalEt()` çağrısı, isteğin gönderilmesinden **önce** çalışıyor; bu sıralama
bilinçli, çünkü ölçümün amacı zamanlamayla ilgili hiçbir tartışmaya yer bırakmamak. Bağlam
zaten iptal edilmiş durumdayken `http.DefaultClient.Do` çağrılıyor ve istemci isteği hiç
kabloya koymadan hemen hata döndürüyor. `errors.Is(hata, context.Canceled)` denetimi
`true` çıkıyor — dönen hata, ağ katmanının kendi ürettiği bir hata değil, bağlamın
taşıdığı sabit hatanın sarmalanmış hâli. Bir gerçek zaman aşımı (`context.WithTimeout`)
kurulsaydı aynı mekanizma işlerdi, yalnızca hata `context.Canceled` yerine
`context.DeadlineExceeded` olurdu; ikisi de aynı `ctx.Err()` yolundan geliyor.

**AG8.** Bu ölçüm, önceki kursun kapanışında bırakılan soruyu bu sınırda yeniden soruyor:
bir goroutine'i durduran şey neydi — kendi kod yolundaki bir kontrol noktası. `http.Client`
da aynı ilkeyi izliyor: bağlamın iptalini kendi iç döngüsünde (bağlantı kurulurken,
yanıt beklerken) denetliyor ve bu kontrol noktasına ulaştığında durup hatayı döndürüyor.
Fark, bu kez kontrol noktasının kaynak kodda görünmemesi — istemcinin kendisi standart
kitaplığın bir parçası, ama davranış aynı: bağlam bir zorlama aracı değil, bir haber
kaynağı ve `http.Client` bu haberi dinleyen taraflardan biri. Bu tutarlılık, standart
kitaplığın kendi iç işlevlerini de aynı ilkeye tabi tutmasından geliyor; bağlam paketi bir
kez öğrenildiğinde, sınırın hangi tarafında olursa olsun aynı biçimde davranıyor.

Bu ölçümde gerçek bir süre hiç kullanılmadı; `context.WithCancel` ile `iptalEt()`in hemen
çağrılması, isteğin başarısız olacağını **kesin** kılıyor, "belki zamanında yetişir belki
yetişmez" belirsizliğini tamamen ortadan kaldırıyor. Gerçek bir `context.WithTimeout`
kullanılsaydı, ölçümün sonucu doğrulayıcının çalıştığı makinenin hızına bağlı kalırdı — aynı
kaynak kimi koşumda zaman aşımına uğrar, kimi koşumda sunucu yeterince hızlı yanıt verdiği
için uğramazdı. Önceden iptal edilmiş bir bağlam bu belirsizliği koşum zamanından tasarım
zamanına taşıyor.

#### Bağlantı Yeniden Kullanımı: Gövdeyi Kapatmanın Bedeli

Bir TCP bağlantısı kurmak ucuz bir işlem değil; Go'nun `http.Transport`'u bu yüzden
kullanılmış bağlantıları bir **bağlantı havuzunda** tutup sonraki isteklerde yeniden
kullanmaya çalışıyor. Bağlantı havuzu kavramının kendisi Veri Erişim Katmanı kursunda
kuruldu; burada ölçülen şey Go'nun standart kitaplığındaki `http.Transport`'un bu havuzu
hangi koşulda işlettiği. Bu yeniden kullanım koşulsuz değil: bir yanıtın gövdesi tamamen
okunup kapatılmadan, altındaki bağlantı havuza geri dönmüyor. Bu, önceki bölümdeki zaman
aşımından bağımsız bir sorumluluk — istek başarıyla tamamlansa, durum kodu `200` dönse bile,
gövdeyi görmezden gelmek bağlantıyı **kaybettirebiliyor**.

Aşağıdaki ölçüm bunu `net/http/httptrace` ile gözlemliyor: `httptrace.ClientTrace`in
`GotConn` alanı, her istek için elde edilen bağlantının **yeniden kullanılmış** olup
olmadığını (`Reused` alanı, bir `bool`) bildiriyor. Bu paket de standart kitaplığın bir
parçası ve hiçbir gerçek adres ya da bağlantı noktası açığa çıkarmıyor, yalnızca bir mantıksal
bayrak veriyor.

Bir önceki bölüm zaman aşımını ölçerken `http.DefaultClient`i kullanmıştı, çünkü orada
ölçülen şey bağlam iptaliydi ve hangi istemcinin kullanıldığı sonucu değiştirmiyordu. Bu
bölüm kendi istemcilerini açıkça kuruyor, çünkü burada ölçülen şey tam olarak istemcinin
taşıdığı bağlantı havuzu — `http.DefaultClient`i kullanmak, bu ölçümü kursun başka bir
dersinde çalışan kodun bıraktığı bağlantılarla karışabilir hâle getirirdi. Bu, bir HTTP
istemcisinin görünmeyen bir durum (açık bağlantılar) taşıdığının ilk işareti: iki farklı
`*http.Client` değeri, aynı sunucuya karşı bile olsa, birbirinden tamamen farklı bir geçmiş
biriktirebiliyor.

```go
// baglantitekrar.go — govdenin kapatilmasi baglantinin havuza donup donmedigini belirliyor
package main

import (
	"context"
	"fmt"
	"io"
	"net/http"
	"net/http/httptest"
	"net/http/httptrace"
)

// izleyerekIste, bir istegi verilen istemciyle gonderir; baglantinin yeniden mi
// kullanildigini httptrace ile ogrenip govdeyi istege gore kapatir.
func izleyerekIste(istemci *http.Client, url string, govdeyiKapat bool) (yenidenKullanildi bool) {
	baglam := httptrace.WithClientTrace(context.Background(), &httptrace.ClientTrace{
		GotConn: func(bilgi httptrace.GotConnInfo) { yenidenKullanildi = bilgi.Reused },
	})
	istek, _ := http.NewRequestWithContext(baglam, http.MethodGet, url, nil)
	yanit, hata := istemci.Do(istek)
	if hata != nil {
		return false
	}
	if govdeyiKapat {
		io.ReadAll(yanit.Body)
		yanit.Body.Close()
	}
	return yenidenKullanildi
}

func main() {
	fmt.Println("-- baglanti yeniden kullanimi: govde kapatildiginda ve kapatilmadiginda --")

	sunucu := httptest.NewServer(http.HandlerFunc(yaniticiIsleyici))
	defer sunucu.Close()

	// Her istemci kendi Transport'unu tasiyor; boylece baglanti havuzlari birbirinden
	// bagimsiz oluyor ve bir istemcideki kapatilmamis govde otekini etkilemiyor.
	istemciA := &http.Client{Transport: &http.Transport{}}
	ilkA := izleyerekIste(istemciA, sunucu.URL, true)
	ikinciA := izleyerekIste(istemciA, sunucu.URL, true)
	fmt.Printf("govde her seferinde kapatildi: ilk=%t ikinci=%t\n", ilkA, ikinciA)

	istemciB := &http.Client{Transport: &http.Transport{}}
	izleyerekIste(istemciB, sunucu.URL, false)
	ikinciB := izleyerekIste(istemciB, sunucu.URL, true)
	fmt.Printf("ilk istegin govdesi hic kapatilmadi: ikinci=%t\n", ikinciB)
}
```

```
-- baglanti yeniden kullanimi: govde kapatildiginda ve kapatilmadiginda --
govde her seferinde kapatildi: ilk=false ikinci=true
ilk istegin govdesi hic kapatilmadi: ikinci=false
```

**AG9.** `istemciA` ile yapılan iki istekte gövde her seferinde okunup kapatılıyor. İlk
istekte henüz açık bir bağlantı olmadığı için `Reused=false` çıkması beklenen bir sonuç; asıl
ölçülen şey **ikinci** istek. Gövde ilk istekte tam okunup kapatıldığı için bağlantı havuza
geri dönmüş oluyor ve ikinci istek `Reused=true` ile aynı bağlantıyı devralıyor. Bu, istemci
tarafının üçüncü sorumluluğunun (gövde kapatma) neden yalnızca "temizlik" değil, doğrudan
başarım kararı olduğunu gösteriyor.

`istemciA` ve `istemciB`nin her biri kendi `*http.Transport` değerini taşıyor; bu bilinçli
bir seçim. `Transport` alanı boş bırakılsaydı, ikisi de standart kitaplığın **paylaşılan**
varsayılan aktarımını kullanırdı ve iki istemcinin bağlantı havuzları aslında aynı havuz
olurdu — `istemciB`nin kapatılmamış gövdesi, adı farklı olsa bile `istemciA`nın sonraki
isteğini de etkileyebilirdi. Ayrı birer `Transport` vermek, iki senaryoyu birbirinden
**yalıtıyor**: ölçülen etki gerçekten `istemciB`nin kendi kapatılmamış gövdesinden geliyor,
başka bir isteğin yan etkisinden değil.

**AG10.** `istemciB` ile yapılan ilk istekte gövde hiç okunmuyor, hiç kapatılmıyor. İkinci
istek aynı istemciyle, gövdesi düzgün kapatılarak gönderiliyor, ama sonuç yine
`Reused=false`. Sebep, ilk isteğin bağlantısının hâlâ **meşgul** sayılması: `Transport`,
gövdesi okunup kapatılmamış bir yanıtın altındaki bağlantıyı asla havuza koymuyor, çünkü o
bağlantının ne zaman serbest kalacağını bilmiyor. İkinci istek bu yüzden yeni bir bağlantı
açmak zorunda kalıyor — birinci isteğin kapatılmamış gövdesi, kendisinden sonraki isteği de
etkiliyor.

Bu etkinin ölçekle büyüdüğü açık: art arda yüzlerce istek gönderen, ama gövdelerinin bir
kısmını kapatmayı unutan bir program, her kapatılmamış gövde için bir bağlantıyı sonsuza
kadar meşgul tutuyor. Programın kendisi çökmüyor, hata da vermiyor — yalnızca istemcinin
açık bağlantı sayısı zamanla birikiyor ve bu birikim gövdenin kendisi kadar sessiz kalıyor.

**AG11.** Bu iki ölçüm birlikte okunduğunda ortaya çıkan kural net: bağlantı yeniden
kullanımı **kimin işi** sorusuna tek bir yanıt veriyor — çağıranın işi. Standart kitaplık
gövdeyi otomatik olarak okuyup kapatmıyor; `Body.Close()` çağrılmazsa, hatta çağrılsa bile
gövde önce tam okunmazsa, bağlantı sızıyor ya da en azından yeniden kullanılamıyor. Bu,
önceki dersteki ara katman zincirinde görülen ilkeyle aynı yerde duruyor: standart kitaplık
sınırın bu tarafında bir mekanizma sunuyor, ama mekanizmayı doğru kullanmak yazan tarafın
sorumluluğunda kalıyor.

Bu kural, tip sisteminin sınırdaki rolüne de bir örnek ekliyor: `yanit.Body`nin tipi
(`io.ReadCloser`) kendisi hiçbir yerde "beni tam okumadan kapatma" demiyor. Derleyici,
gövdesi okunmadan bırakılan bir yanıtı reddetmiyor — kaynak sorunsuz derleniyor, sorunsuz
çalışıyor, yalnızca bağlantı havuzu sessizce küçülüyor. Bu tür bir sözleşme (bir kaynağın
kapatılmadan önce tüketilmesi gerektiği) tipin kendisinde değil, belgesinde ve yazan tarafın
alışkanlığında yaşıyor.

#### Sınırlayıcı Ölçüm: Gövde Bir Okuyucudur, Bir Değer Değil

Önceki iki ölçüm gövdenin kapatılmasının **neden** önemli olduğunu gösterdi. Bu ölçüm
gövdenin **ne olduğunu** netleştiriyor: `yanit.Body`, `io.ReadCloser` arayüzünü
gerçekleştiren bir akış, isteğin gövdesi gibi bir kez tüketiliyor ve tüketildikten sonra
geriye hiçbir şey kalmıyor. Bu, dilin önceki kurslarında görülen kopyalanabilir değerlerden
(bir dilim, bir yapı) temelden farklı bir davranış: bir dilimi iki değişkene atayıp ikisini de
ayrı ayrı okumak mümkündü, çünkü dilim bir değerdi. Bir okuyucu değer değil, bir **konum**
taşıyor — okundukça o konum ilerliyor ve geriye dönmüyor.

```go
// govdeokuma.go — yanit govdesi bir okuyucudur, bir deger degil
package main

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

func main() {
	fmt.Println("-- yanit govdesi bir okuyucudur, iki kez okunamaz --")

	sunucu := httptest.NewServer(http.HandlerFunc(yaniticiIsleyici))
	defer sunucu.Close()

	yanit, _ := http.Get(sunucu.URL)
	defer yanit.Body.Close()

	ilkOkuma, _ := io.ReadAll(yanit.Body)
	ikinciOkuma, ikinciHata := io.ReadAll(yanit.Body)

	olcumSatiri("yanit govdesi (istemcide okunan)", "kayboluyor",
		fmt.Sprintf("tip=%T ilk-uzunluk=%d", ilkOkuma, len(ilkOkuma)))
	fmt.Printf("ikinci okuma uzunlugu=%d hata=%v\n", len(ikinciOkuma), ikinciHata)
}
```

```
-- yanit govdesi bir okuyucudur, iki kez okunamaz --
yanit govdesi (istemcide okunan) kayboluyor tip=[]uint8 ilk-uzunluk=15
ikinci okuma uzunlugu=0 hata=<nil>
```

**AG12.** İlk `io.ReadAll` çağrısı gövdeyi baştan sona okuyup on beş baytlık bir dilim
döndürüyor — sınırın bu tarafına ulaşan şey yine `[]byte`, önceki derste görülen aynı sınıf:
**kayboluyor**. İkinci `io.ReadAll` çağrısı ise `0` uzunlukta bir sonuç ve `nil` bir hata
döndürüyor; okunacak hiçbir şey kalmadığı için okuma işlemi anında bitiyor, ama bu bir hata
sayılmıyor. Gövde bir dilim ya da bir dizgi olsaydı, aynı değeri istendiği kadar okumak mümkün
olurdu; bir okuyucu olduğu için tüketildiği an geride yalnızca boşluk kalıyor.

**AG13.** Bu davranışın pratik sonucu: bir yanıt gövdesi ancak **bir kez** ayrıştırılabilir.
Bir işlev gövdeyi hem günlüğe yazmak hem de bir hedefe çözmek istiyorsa, gövdeyi önce bir
arabelleğe (`bytes.Buffer` gibi) kopyalayıp iki farklı okumayı o arabellek üzerinden yapmak
zorunda — kaynağın kendisine ikinci kez dönmek mümkün değil. Bu sınırlama, bir önceki
bölümdeki bağlantı yeniden kullanımıyla da bağlantılı: gövdeyi bir arabelleğe kopyalamak,
onu aynı zamanda tam olarak tüketmek anlamına geliyor, dolayısıyla bu kopyalama işlemi hem
ikinci bir okumayı mümkün kılıyor hem de altındaki bağlantının havuza dönmesini sağlıyor —
aynı `io.ReadAll` çağrısı iki ayrı sorunu birden çözüyor.

Kursun on üç sınır geçişinin tam dağılımı (1 korunuyor, 5 yeniden kuruluyor, 7 kayboluyor)
sıradaki derste, JSON Dizileştirme'de kuruluyor; bu ders yalnız istemci tarafının üç
sorumluluğunu ve gövdenin okuyucu doğasını ölçtü.

#### Özet

- Zaman aşımı bir süre yerine önceden iptal edilmiş bir bağlamla kuruluyor; `http.Client`
  bağlamın iptalini kendi iç kontrol noktalarında denetleyip isteği bu noktada durduruyor.
- `errors.Is` ile denetlenen istek hatası, bağlamın taşıdığı sabit hatanın
  (`context.Canceled` ya da `context.DeadlineExceeded`) sarmalanmış hâli.
- Bir bağlantının yeniden kullanılıp kullanılmayacağı `httptrace.GotConnInfo.Reused`
  alanıyla ölçülüyor; gövde tam okunup kapatılmazsa bağlantı havuza dönmüyor.
- Gövdesi kapatılmamış bir isteğin etkisi kendisinden sonraki isteğe de yansıyor: bir
  önceki bağlantı meşgul sayıldığı için yeni bir bağlantı açılıyor.
- Yanıt gövdesi bir okuyucudur, bir değer değil: bir kez tam okunduktan sonra ikinci okuma
  boş dönüyor, hata vermeden.
- Bu ders sınırı gönderen taraftan ölçtü; kursun on üç sınır geçişinin tam dağılımı henüz
  burada kurulmadı, sıradaki ders kuruyor.

#### Sonraki Adım

İki ders de sınırın **taşıyıcısını** ölçtü — istek ve yanıt bayt ve metin taşıyor; gövde bir kez
okunabiliyor. Bu iki dersin ikisinde de gövdenin **içeriği** hiç önemli değildi; `"adet=3"` ya da
`"adet=7 birim=kg"` yerine rastgele herhangi bir metin de aynı sonuçları verirdi, çünkü ölçülen
şey gövdenin davranışıydı, taşıdığı veri değil. Ama hiçbiri şu soruyu henüz sormadı: gönderen
taraf bir **yapıyı** gönderirken karşı tarafın onu geri kurabilmesi için ne bırakıyor? Sıradaki
ders bu soruyu JSON ile ölçüyor ve kursun bağlayıcı sayısını üretiyor: bir yapının alan adları
yapı etiketleriyle karşıya taşınıyor, ama bu taşımanın sınırları var — alıcıda karşılığı olmayan
bir alan sessizce düşüyor ve "geri kuruldu" görünmesi her zaman "doğru kuruldu" anlamına gelmiyor.

### JSON Dizileştirme

Source: https://academia.sh/tr/kurslar/go-uygulamalar/json-dizilestirme

Kursun bağlayıcı sayısı burada üretiliyor: on üç sınır geçişinin 1'inde tip bilgisi korunuyor, 5'inde yeniden kuruluyor, 7'sinde kayboluyor. Bir yapının alan adlarını karşıya taşıyan tarif yapı etiketidir; aynı bayt dizisi tarifsiz bir kaba çözüldüğünde tamsayı `float64` olur, alıcıda karşılığı olmayan bir alan sessizce düşer ve hiçbir hata çıkmaz. Sınırlayıcı ölçüm: geri kuruldu görünmesi doğru kuruldu anlamına gelmiyor.

Önceki ders, sınırı geçen bir gövdenin yalnızca bayt taşıdığını, hiçbir tip bilgisi
taşımadığını ölçtü. Ama o gövdenin **içeriği** hâlâ soru işaretiydi — `"adet=7 birim=kg"`
gibi bir metin, isteyen tarafça nasıl yorumlanıyor? Bu ders bu soruyu doğrudan ele alıyor:
gönderen taraf elinde bir **yapı** (`struct`) tutuyorsa, sınırı geçtikten sonra alıcı tarafta
bu yapının hangi parçası hayatta kalıyor? Yanıt, yapının kendisinde değil, yapıyla birlikte
taşınan bir **tarifte** yatıyor — Go'da bu tarifin adı yapı etiketi.

Bu ders aynı zamanda kursun bağlayıcı ölçümünü üretiyor. Kursun sorduğu soru on iki dersin
hepsinde aynı: bir değer sınırı geçerken tip bilgisine ne oluyor? Bu soru şimdiye kadar iki
dar örnekte (istek parçaları, yanıt gövdesi) soruldu. Bu ders soruyu **geniş ölçekte**
soruyor — ağ, şablon, veritabanı ve komut satırı sınırlarının on üçünü tek bir ölçümde art
arda koşturup her birini üç sonuçtan birine yazıyor: **korunuyor**, **yeniden kuruluyor**,
**kayboluyor**. Bu on üç geçişin dördü doğrudan JSON'un konusu; kalan dokuzu kursun geri
kalan derslerinde ayrı ayrı derinleşiyor, burada yalnızca sınıflandırılıyor.

Bu geniş ölçümün tek bir ölçümde toplanmasının bir nedeni var: on üç geçişi ayrı ayrı on üç
derste ölçmek, her dersin kendi küçük sayısını üretmesine yol açardı ve kimse bu küçük
sayıların bir araya geldiğinde neye eşit olduğunu göremezdi. Tek bir koşumda toplanmaları,
kursun ana iddiasının (sınırın çoğunlukla tip bilgisini kaybettiği) tek bir sayıyla
kanıtlanmasını sağlıyor. Bu ders o sayıyı üretiyor; geri kalan on bir ders bu sayının
parçalarını tek tek büyütüyor.

#### Yapı Etiketleri: Alan Adı Bağlamasının Tarifi

Bir Go yapısının alan adları (`Ad`, `Adet`) derleme sırasında var olan, çalışma zamanında da
yansıma (`reflect`) ile erişilebilen bilgiler. Ama bu adlar İngilizce büyük harfle
başlıyor ve Go'nun kendi adlandırma kuralına bağlı — dışa aktarılan bir alan olmasının şartı
bu. JSON'un anahtar adları için böyle bir kural yok; sınırın öbür tarafındaki program
`"ad"` küçük harfle bekleyebilir, hatta tamamen farklı bir sözcük bekleyebilir. Yapı etiketi
(`json:"ad"`) bu ikisi arasındaki **alan adı bağlamasının** tarifini taşıyor: hangi Go
alanının hangi JSON anahtarına karşılık geldiğini söylüyor. Etiket yoksa `encoding/json`
paketi alan adını olduğu gibi kullanıyor; bu ders etiketli ve etiketsiz alanları yan yana
koşturuyor.

Aşağıdaki kaynak, bu ölçümün üç sonucunu sayıp yazdıran ortak bir yardımcıyı kuruyor.

```go
// sinir.go — bir deger siniri gectiginde tip bilgisine ne oluyor: korunuyor mu, yeniden mi kuruluyor, kayboluyor mu
package main

import "fmt"

var korunan, yenidenKurulan, kaybolan int

func gozle(gecis, sonuc, kanit string) {
	switch sonuc {
	case "korunuyor":
		korunan++
	case "yeniden":
		yenidenKurulan++
	default:
		kaybolan++
	}
	fmt.Printf("%-36s %-10s %s\n", gecis, sonuc, kanit)
}

func ozet() {
	fmt.Printf("sinir: %d korunuyor, %d yeniden kuruluyor, %d kayboluyor\n",
		korunan, yenidenKurulan, kaybolan)
}
```

Ölçümün kendisi on üç sınır geçişini sırayla soruyor. İlk dördü doğrudan JSON'un konusu: aynı
yapıya gidiş dönüş, etiketle gizlenmiş bir alan, tarifsiz bir kaba çözme ve alıcıda karşılığı
olmayan bir alan. Geri kalan dokuzu (zaman, şablon, okuyucu, ağ, paylaşılan tarif, sürücü, komut
satırı) kursun ilerleyen derslerinde ayrı ayrı derinleşiyor — hangisinin nerede ele alındığı
aşağıda tek tek yazılı; burada yalnızca aynı üç sonuca yazılıyor.

```go
// olcum.go — on uc sinir gecisi, uc sonuca bolunur
package main

import (
	"bytes"
	"encoding/json"
	"flag"
	"fmt"
	"io"
	"net/http"
	"net/http/httptest"
	"sort"
	"strings"
	"text/template"
	"time"
)

type Kayit struct {
	Ad    string    `json:"ad"`
	Adet  int       `json:"adet"`
	Zaman time.Time `json:"zaman"`
	Gizli string    `json:"-"`
}

// Surucu, bir veritabani surucusunun tasidigi tip kumesini modeller: yalniz dort tip.
type Surucu struct{ satir []any }

func (s Surucu) Oku(i int) any { return s.satir[i] }

func main() {
	sabitZaman, _ := time.Parse(time.RFC3339, "2026-08-23T10:00:00Z")
	kayit := Kayit{Ad: "vida", Adet: 12, Zaman: sabitZaman, Gizli: "gorunmez"}

	fmt.Println("-- sinirda tip bilgisine ne oluyor --")

	// 1-2. JSON gidis donus: yapi -> metin -> yapi
	bayt, _ := json.Marshal(kayit)
	var geri Kayit
	json.Unmarshal(bayt, &geri)
	gozle("JSON gidis donus (ayni yapiya)", "yeniden",
		fmt.Sprint("adet tipi=", fmt.Sprintf("%T", geri.Adet), " esit=", geri.Adet == kayit.Adet))
	gozle("etiketsiz alan (json:\"-\")", "kayboluyor",
		fmt.Sprint("gizli alan geri geldi mi=", geri.Gizli != ""))

	// 3. JSON -> sozluk: tip tarifi yok
	var serbest map[string]any
	json.Unmarshal(bayt, &serbest)
	gozle("JSON gidis donus (tarifsiz kaba)", "kayboluyor",
		fmt.Sprint("adet tipi=", fmt.Sprintf("%T", serbest["adet"])))

	// 4. Bilinmeyen alan sessizce dusuyor
	var kismi Kayit
	json.Unmarshal([]byte(`{"ad":"somun","bilinmeyen":5}`), &kismi)
	gozle("gonderende olup alicida olmayan alan", "kayboluyor",
		fmt.Sprint("ad=", kismi.Ad, " hata=false"))

	// 5. Zaman: tip -> metin -> tip
	metin := sabitZaman.Format(time.RFC3339)
	tekrar, _ := time.Parse(time.RFC3339, metin)
	gozle("zaman degeri metne ve geri", "yeniden",
		fmt.Sprint("esit=", tekrar.Equal(sabitZaman), " tip=", fmt.Sprintf("%T", tekrar)))

	// 6. Sablon: cikti yalniz metin
	sab := template.Must(template.New("s").Parse("{{.Ad}} x{{.Adet}}"))
	var yazi bytes.Buffer
	sab.Execute(&yazi, kayit)
	gozle("sablon ciktisi", "kayboluyor", fmt.Sprintf("%q (%T)", yazi.String(), yazi.String()))

	// 7. Okuyucu/yazici: bayt duzeyi
	okunan, _ := io.ReadAll(strings.NewReader("vida"))
	gozle("okuyucudan gelen icerik", "kayboluyor", fmt.Sprintf("%T uzunluk=%d", okunan, len(okunan)))

	// 8-9. HTTP: govde bayt, tek ortak ipucu icerik tipi
	sunucu := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Content-Type", "application/json")
		json.NewEncoder(w).Encode(kayit)
	}))
	defer sunucu.Close()
	yanit, _ := http.Get(sunucu.URL)
	govde, _ := io.ReadAll(yanit.Body)
	yanit.Body.Close()
	gozle("HTTP govdesi", "kayboluyor", fmt.Sprintf("%T uzunluk>0=%t", govde, len(govde) > 0))
	var alinan Kayit
	json.Unmarshal(govde, &alinan)
	gozle("icerik tipi ipucuyla cozme", "yeniden",
		fmt.Sprint("icerik tipi=", yanit.Header.Get("Content-Type"), " adet=", alinan.Adet))

	// 10. Sozlesme temelli gecis: iki taraf ayni tarifi paylasiyor
	tarif := []string{"ad:dizgi", "adet:tamsayi"}
	gonderen := kodla(tarif, kayit)
	alan, tip := coz(tarif, gonderen)
	gozle("paylasilan tarifle gecis", "korunuyor", fmt.Sprint(alan, " ", tip))

	// 11. Surucu tip kumesi: dort tipe indirgeniyor, Tara geri kuruyor
	s := Surucu{satir: []any{"vida", int64(12)}}
	gozle("surucunun tasidigi tip kumesi", "kayboluyor",
		fmt.Sprintf("adet %T olarak geliyor", s.Oku(1)))
	var adet int
	tara(s.Oku(1), &adet)
	gozle("Tara ile geri kurma", "yeniden", fmt.Sprint("adet=", adet, " tip=", fmt.Sprintf("%T", adet)))

	// 12. Komut satiri: argumanlar dizgi, ayristirici tipi geri kuruyor
	kume := flag.NewFlagSet("olcum", flag.ContinueOnError)
	kume.SetOutput(io.Discard)
	sayi := kume.Int("adet", 0, "adet")
	kume.Parse([]string{"-adet", "12"})
	gozle("komut satiri argumani", "yeniden", fmt.Sprint("adet=", *sayi, " tip=", fmt.Sprintf("%T", *sayi)))

	ozet()
}

// kodla ve coz, iki tarafin paylastigi bir tarifle calisir: tip adi tarifte durur.
func kodla(tarif []string, k Kayit) []string {
	return []string{k.Ad, fmt.Sprint(k.Adet)}
}

func coz(tarif []string, alanlar []string) (string, string) {
	adlar := make([]string, 0, len(tarif))
	for _, t := range tarif {
		adlar = append(adlar, strings.SplitN(t, ":", 2)[1])
	}
	sort.Strings(adlar)
	return alanlar[0], strings.Join(adlar, "+")
}

// tara, surucunun dondurdugu genis tipi hedefin tipine indirger.
func tara(kaynak any, hedef *int) {
	if v, ok := kaynak.(int64); ok {
		*hedef = int(v)
	}
}
```

```
-- sinirda tip bilgisine ne oluyor --
JSON gidis donus (ayni yapiya)       yeniden    adet tipi=int esit=true
etiketsiz alan (json:"-")            kayboluyor gizli alan geri geldi mi=false
JSON gidis donus (tarifsiz kaba)     kayboluyor adet tipi=float64
gonderende olup alicida olmayan alan kayboluyor ad=somun hata=false
zaman degeri metne ve geri           yeniden    esit=true tip=time.Time
sablon ciktisi                       kayboluyor "vida x12" (string)
okuyucudan gelen icerik              kayboluyor []uint8 uzunluk=4
HTTP govdesi                         kayboluyor []uint8 uzunluk>0=true
icerik tipi ipucuyla cozme           yeniden    icerik tipi=application/json adet=12
paylasilan tarifle gecis             korunuyor  vida dizgi+tamsayi
surucunun tasidigi tip kumesi        kayboluyor adet int64 olarak geliyor
Tara ile geri kurma                  yeniden    adet=12 tip=int
komut satiri argumani                yeniden    adet=12 tip=int
sinir: 1 korunuyor, 5 yeniden kuruluyor, 7 kayboluyor
```

**AG14.** JSON'un kendi payı ilk dört satır. Birincisi, aynı `Kayit` yapısına gidiş dönüşte
`Adet` alanının `int` tipini ve değerini koruyarak geri gelmesi — `yeniden` sınıfında, çünkü
tipi geri kuran şey yapının kendisi değil, gönderen ve alan tarafın **aynı Go tipini**
paylaşması. İkincisi, `Gizli` alanının `json:"-"` etiketiyle hiç gönderilmemesi: bayt dizisine
hiç girmediği için geri de gelmiyor, `kayboluyor` sınıfında ama nedeni bir kayıp değil, bilinçli
bir dışlama. Üçüncüsü ve dördüncüsü, aynı bayt dizisinin (`bayt` değişkeni) iki farklı
biçimde çözülmesiyle ortaya çıkıyor.

**AG15.** Üçüncü satır bu dersin en çarpıcı gözlemi: aynı `bayt` dizisi bir `map[string]any`
değişkenine çözüldüğünde, `Adet` alanının kaynaktaki değeri `12` bir `int`ken, geri gelen
değerin tipi `float64` oluyor. Sebep, JSON biçiminin kendisinde tamsayı ile ondalık sayı
ayrımının olmaması — `encoding/json` paketi bir hedef tipi verilmediğinde (`any` ya da
`map[string]any`), her sayıyı varsayılan olarak `float64`e çözüyor. Yapı etiketi burada
devrede değil, çünkü hedefin kendisi bir yapı değil; tip tarifi yalnız hedef bir yapı
olduğunda işliyor. Dördüncü satır ise tam tersi bir durumu ölçüyor: gönderilen bayt dizisinde
`Kayit` yapısında karşılığı olmayan bir `"bilinmeyen"` anahtarı var ve `json.Unmarshal` bunu
sessizce yok sayıyor — ne bir hata dönüyor ne bir uyarı yazdırıyor, `ad=somun hata=false`
satırı bunu doğruluyor.

**AG16.** Bu dört satırın ortak noktası, `encoding/json` paketinin **hoşgörülü** bir tasarıma
sahip olması: eksik alan, fazla alan, tip tarifi olmayan hedef — hiçbiri bir hata üretmiyor. Bu
hoşgörü, dersin başlıktaki iddiasını netleştiriyor: "geri kuruldu" görünen bir işlem doğru
kurulmuş olmayabilir. `json.Unmarshal([]byte(...), &kismi)` çağrısı `nil` hata döndürüyor; kod
hiçbir yerde başarısız görünmüyor — ama `kismi` değişkeninde gönderilen `"bilinmeyen"` alanının
hiçbir izi yok. Bir programın bu sessizliği fark etmesinin tek yolu, gönderilen ile alınanı **elle
karşılaştırmak**; derleyici ya da çalışma zamanı bu farkı kendiliğinden bildirmiyor.

#### Geri Kalan Dokuz Geçiş: Kursun Tam Ölçeği

Ölçümün beşinci satırından on üçüncü satırına kadar olan bölüm, kursun geri kalan
derslerinin konusu. Beşinci satır bir zaman değerinin metne dönüp geri gelmesini ölçüyor —
Sistem ve Veri Erişimi konusunun Zaman İşlemleri dersi bunu derinleştiriyor. Altıncı satır
bir şablonun çıktısının yalnızca metin olduğunu ölçüyor — sıradaki ders bu satırı tek başına
ele alıyor. Yedinci ve sekizinci satırlar okuyucu/yazıcı soyutlamasını ve HTTP gövdesini
ölçüyor; ikisi de önceki iki derste ve Sistem konusunun ilk dersinde ayrı ayrı işlendi ya da
işlenecek. Dokuzuncu satır, içerik tipi üstbilgisinin bir ipucu olarak nasıl kullanıldığını
gösteriyor — ilk derste ayrıntısıyla ölçülen konunun burada tek satırlık karşılığı.

Onuncu satır kursun **tek** `korunuyor` satırı: `kodla` ve `coz` işlevleri aynı `tarif`
dizisini okuyor, dolayısıyla gönderilen alan adlarıyla çözülen alan adları arasında hiçbir
belirsizlik kalmıyor. Bu satırın derinleşmesi Sözleşme Temelli Servis İletişimi dersinde —
orada tarifin iki tarafa nasıl **dağıtıldığı** ayrıca ölçülüyor, çünkü bu ölçümde tarif
zaten iki tarafta da aynı kaynak dosyada duruyor, dağıtım sorunu modellenmiyor. On birinci
ve on ikinci satırlar bir veritabanı sürücüsünün dar tip kümesini ve bu kümenin `Tara` ile
nasıl geri kurulduğunu ölçüyor — Veritabanı Erişimi dersinin konusu. On üçüncü satır bir
komut satırı bayrağının dizgiden hedef tipe nasıl geri kurulduğunu ölçüyor — Komut Satırı
Uygulamaları dersinin konusu.

**AG17.** Bu dokuz satırın hiçbiri burada ayrıntılı işlenmiyor; her biri kendi dersinde
kendi ölçümüyle, kendi sınırlayıcı gözlemiyle yeniden ele alınacak. Burada bu satırların
tek işlevi, kursun bağlayıcı sayısını **tamamlamak**: JSON'un dört satırı (0 korunuyor, 1
yeniden, 3 kayboluyor) tek başına kursun tam resmini vermiyor, çünkü kursun `korunuyor`
sınıfının tek örneği JSON'un dışında, paylaşılan tarif geçişinde. On üç satırın tamamı bir
arada koştuğunda ortaya çıkan toplam — 1 korunuyor, 5 yeniden kuruluyor, 7 kayboluyor — bu
dersten itibaren kursun geri kalanında sabit bir referans olarak kullanılıyor.

Bu dokuz satırın ölçümde burada durmasının bir başka nedeni de örneklerin birbirine bağımlı
olması: sekizinci ve dokuzuncu satırlar (`HTTP govdesi`, `icerik tipi ipucuyla cozme`) aynı
`kayit` değişkenini kullanıyor, dolayısıyla JSON'un dört satırıyla aynı veriyi paylaşıyor.
Bu paylaşım tesadüfi değil — kursun on iki dersi aynı olayın (bir değerin sınırdan geçmesi)
ayrı görünümleri olduğu için, aynı örnek verinin farklı sınırlardan geçirilmesi kursun
tutarlılığını gösteriyor. Sıradaki derslerin her biri kendi konusuna özgü yeni bir veri
kurup kendi ölçümünü yapacak; bu ölçüm yalnızca hepsinin **aynı çerçeveye** oturduğunu
kanıtlıyor.

**AG18.** Bu dağılımın okunması: on üç geçişin **yarısından fazlası** (yedisi) tip bilgisini
tamamen kaybediyor, beşi karşı tarafın elindeki bir tarifle yeniden kuruluyor ve yalnızca
biri iki tarafın aynı tarifi paylaşması sayesinde hiç kaybolmuyor. Bu oran, standart
kitaplığın tasarım felsefesini yansıtıyor: sınırın ötesi varsayılan olarak **tipsiz**, tip
ancak açıkça kurulan bir tarifle geri geliyor. Hiçbir geçiş kendiliğinden, otomatik olarak
tip taşımıyor.

Beş `yeniden` satırının ortak noktası da öğretici: her birinde tipi geri kuran şey farklı bir
mekanizma — aynı Go tipini paylaşmak (birinci satır), bir ayrıştırma kuralı (`time.Parse`),
bir içerik tipi ipucu, bir tarama işlevi (`tara`), bir bayrak ayrıştırıcısı. Ortak olan tek
şey, hepsinin **karşı tarafın kodunda açıkça yazılmış** olması; tip hiçbir yerde kendiliğinden
ortaya çıkmıyor, birinin onu geri kuracak bir işlev yazmış olması gerekiyor. Bu, kursun her
dersinde tekrar karşılaşılacak bir örüntü: geri kurma bir doğa yasası değil, bir kod satırı.

#### Özet

- Yapı etiketi (`json:"ad"`), bir Go alanının adıyla bir JSON anahtarının adı arasındaki
  alan adı bağlamasının tarifini taşıyor; etiket yoksa alan adı olduğu gibi kullanılıyor.
- `json:"-"` etiketiyle işaretlenen bir alan bayt dizisine hiç girmiyor; sınırı hiç geçmediği
  için geri de gelmiyor.
- Aynı bayt dizisi bir yapıya değil, tip tarifi olmayan bir kaba (`map[string]any`)
  çözüldüğünde, tamsayı değerler `float64` olarak geri geliyor.
- Gönderende olup alıcı yapıda karşılığı olmayan bir alan sessizce düşüyor; `json.Unmarshal`
  bu durumda hiçbir hata döndürmüyor.
- Kursun on üç sınır geçişinin tam dağılımı bu derste kuruldu: 1 korunuyor, 5 yeniden
  kuruluyor, 7 kayboluyor; bu sayılar kursun geri kalan derslerinde bağlayıcı referans.
- Tek `korunuyor` satırı, iki tarafın aynı tarifi paylaştığı geçiş; kalan on iki satırın
  hiçbirinde iki taraf aynı tarife sahip değil.

#### Sonraki Adım

Bu ders yapı etiketinin bir alan adını karşıya taşıyan bir **tarif** olduğunu ve tarif yoksa
geri kurmanın da olmadığını ölçtü. Kursun bağlayıcı sayısı da burada kuruldu: on üç geçişin
yedisinde tip bilgisi tamamen kayboluyor, beşinde bir tarifle geri kuruluyor, birinde hiç
kaybolmuyor — bu üç sayı, sıradaki dokuz derste tek tek büyüyecek. Sıradaki konu tarifin hiç var olmadığı bir çıkışa bakıyor:
bir şablon, aldığı yapıdan yalnızca metin üretiyor ve o metnin içinde hangi alanın nereden
geldiğine dair hiçbir iz kalmıyor — bu ölçümün altıncı satırı bunu tek cümleyle özetlemişti,
sıradaki ders aynı satırı tek başına, ayrıntısıyla ele alıyor.

### Şablonlar

Source: https://academia.sh/tr/kurslar/go-uygulamalar/sablonlar

Aynı şablon metni iki farklı Go tipinden (bir tamsayı, bir ondalıklı sayı) geçirildiğinde bayt bayt aynı çıktıyı üretir; kaynaktaki tip bilgisi çıkışta tümüyle kayboluyor. Bir alan adı çıktıda yalnızca şablonun kendi metninde yazılmışsa görünür — tip sisteminden gelmez. Metin ve HTML şablonları aynı veriyi kaçırma bakımından farklı bayt dizilerine çevirir. Sınırlayıcı ölçüm: şablon alan adlarını çalışma zamanında arar, olmayan bir alan derlemede değil çalıştırmada belli olur ve hata çıktının ortasında doğar.

Önceki dersin son ölçümü bir yapı etiketinin ne işe yaradığını gösterdi: yapı etiketi
alan adını sınırın karşı tarafına taşıyan bir tarifti ve o tarif olmadan geri kurma da
yoktu. Bu ders aynı soruyu tersinden soruyor. JSON dizileştirme bir yapıyı bayta çevirip
geri getirdi — gidiş ile dönüş arasında bir tarif vardı ve tarif, dört alandan beşinin
tipini yeniden kurdu. Şablonlar bu geri dönüşü hiç vaat etmiyor: bir şablonun ürettiği
şey yalnızca metin ve o metnin bir daha hiçbir tarifle okunmayacağı varsayılıyor. Kursun
sorusu aynı kalıyor — bu değer sınırı geçerken tip bilgisine ne oluyor? — ama bu kez sınırın
karşı tarafı bir program değil, bir ekran ya da bir dosya.

Go'nun standart kitaplığı iki şablon paketi taşır: `text/template` ve `html/template`.
İkisi de aynı şablon dilini kullanır — `{{.Alan}}` bir alanı okur, `{{if}}` ve `{{range}}`
dallanma ve yineleme sağlar — ve ikisi de bir Go değerini alıp bir `io.Writer`'a metin
yazar. Aralarındaki tek fark, birazdan ölçülecek olan kaçırma davranışı. Her ikisi de
paket adıyla, kod bloğunda çağrılıyor; düzyazıda yalnızca "metin şablonu" ve "HTML şablonu"
olarak anılıyorlar.

`Execute` yönteminin ikinci parametresi bir `io.Writer`dır — bu ders arabellek (`bytes.Buffer`)
kullanıyor, ama aynı çağrı bir dosyaya, standart çıktıya ya da bir ağ bağlantısına da yazabilirdi;
Sistem ve Veri Erişimi konusunun ilk dersi aynı arayüzün dosya ile arabellek arasında
paylaşıldığını ayrıca ölçecek. Şablon paketinin kendisi hedefin ne olduğunu bilmiyor ve bilmesi de
gerekmiyor — yalnızca bir dizi bayt üretip yazıyor. Bu ders tam olarak bu üretilen baytların
içinde neyin durup neyin durmadığını ölçüyor: kaynak veri sınırı geçerken, tip sistemi Go'nun
kendi tarafında kalıyor mu, yoksa şablonun ürettiği metne kadar mı taşınıyor?

Kursun ortak ölçümü on üç sınır geçişini üç sınıfa ayırmıştı: birinde tip korunuyor, beşinde
yeniden kuruluyor, yedisinde kayboluyor (03. derste kurulan tam dağılım). Şablon çıktısı bu
yedi kayıp geçişten biri — ama kayboluşun **biçimi** JSON'daki kayıptan farklı, çünkü JSON
bir geri dönüş biçimidir (bayta çevrilir, sonra geri okunabilir) ve şablon hiç geri dönmeyi
vaat etmez; ürettiği metnin bir daha ayrıştırılacağı bile varsayılmaz.

#### Şablonun Tek Çıktı Tipi: Metin

**AG19.** Bir şablonun `Execute` çağrısının ürettiği şey, kaynak verinin tipi ne olursa
olsun, her zaman bir bayt dizisidir — ve o bayt dizisi bir dizgiye dönüştürüldüğünde
kaynaktaki hiçbir tip bilgisini taşımaz. Bunu göstermenin en doğrudan yolu, aynı şablon
metnini iki farklı Go tipinden geçirip çıktıları karşılaştırmaktır.

```go
// sablonlar.go — sablonun urettigi metnin tipini, alan izini, kacirma farkini ve calisma zamani aramasini olcer
package main

import (
	"bytes"
	"fmt"
	"html/template"
	"strings"
	textTemplate "text/template"
)

type Urun struct {
	Ad    string
	Adet  int
	Yorum string
}

type UrunTamsayi struct{ Adet int }
type UrunOndalikli struct{ Adet float64 }

func main() {
	urun := Urun{Ad: "vida", Adet: 12, Yorum: "iyi <b>ama</b> pahali & yavas"}

	sabitSablon := textTemplate.Must(textTemplate.New("t").Parse("{{.Adet}}"))
	var bTamsayidan, bOndaliklidan bytes.Buffer
	sabitSablon.Execute(&bTamsayidan, UrunTamsayi{Adet: 12})
	sabitSablon.Execute(&bOndaliklidan, UrunOndalikli{Adet: 12})
	fmt.Println("-- ayni sablon, iki farkli kaynak tipi --")
	fmt.Println("tamsayidan gelen cikti:", bTamsayidan.String())
	fmt.Println("ondalikli sayidan gelen cikti:", bOndaliklidan.String())
	fmt.Println("iki cikti bayt bayt ayni mi:", bTamsayidan.String() == bOndaliklidan.String())

	dar := textTemplate.Must(textTemplate.New("dar").Parse("{{.Ad}} x{{.Adet}}"))
	var bDar bytes.Buffer
	dar.Execute(&bDar, urun)
	fmt.Println()
	fmt.Println("-- alan adi ciktida yalniz sablon metninde yazilmissa gorunur --")
	fmt.Println("dar sablon ciktisi:", bDar.String())
	fmt.Println("ciktida \"Ad\" adi geciyor mu:", strings.Contains(bDar.String(), "Ad"))
	fmt.Println("ciktida \"Adet\" adi geciyor mu:", strings.Contains(bDar.String(), "Adet"))

	genis := textTemplate.Must(textTemplate.New("genis").Parse("Ad: {{.Ad}}, Adet: {{.Adet}}"))
	var bGenis bytes.Buffer
	genis.Execute(&bGenis, urun)
	fmt.Println("genis sablon ciktisi:", bGenis.String())
	fmt.Println("ciktida \"Ad\" adi geciyor mu:", strings.Contains(bGenis.String(), "Ad"))

	metinSablon := textTemplate.Must(textTemplate.New("m").Parse("{{.Yorum}}"))
	var bMetin bytes.Buffer
	metinSablon.Execute(&bMetin, urun)
	htmlSablon := template.Must(template.New("h").Parse("{{.Yorum}}"))
	var bHtml bytes.Buffer
	htmlSablon.Execute(&bHtml, urun)
	fmt.Println()
	fmt.Println("-- ayni veri, metin ve html sablonundan gecince farkli bayt uretiyor --")
	fmt.Printf("metin sablonu: %s\n", bMetin.String())
	fmt.Printf("html sablonu:  %s\n", bHtml.String())
	fmt.Println("iki cikti ayni mi:", bMetin.String() == bHtml.String())

	hatali := textTemplate.Must(textTemplate.New("hatali").Parse("on: {{.Ad}}, sonra: {{.OlmayanAlan}}"))
	var bHatali bytes.Buffer
	hata := hatali.Execute(&bHatali, urun)
	fmt.Println()
	fmt.Println("-- olmayan alan calisma zamaninda hata veriyor, once yazilan kismi yazidirilmis oluyor --")
	fmt.Println("hata olustu mu:", hata != nil)
	fmt.Printf("hatadan once yaziya gecen kisim: %q\n", bHatali.String())
}
```

```
-- ayni sablon, iki farkli kaynak tipi --
tamsayidan gelen cikti: 12
ondalikli sayidan gelen cikti: 12
iki cikti bayt bayt ayni mi: true

-- alan adi ciktida yalniz sablon metninde yazilmissa gorunur --
dar sablon ciktisi: vida x12
ciktida "Ad" adi geciyor mu: false
ciktida "Adet" adi geciyor mu: false
genis sablon ciktisi: Ad: vida, Adet: 12
ciktida "Ad" adi geciyor mu: true

-- ayni veri, metin ve html sablonundan gecince farkli bayt uretiyor --
metin sablonu: iyi <b>ama</b> pahali & yavas
html sablonu:  iyi &lt;b&gt;ama&lt;/b&gt; pahali &amp; yavas
iki cikti ayni mi: false

-- olmayan alan calisma zamaninda hata veriyor, once yazilan kismi yazidirilmis oluyor --
hata olustu mu: true
hatadan once yaziya gecen kisim: "on: vida, sonra: "
```

**AG20.** `UrunTamsayi.Adet` bir `int`, `UrunOndalikli.Adet` bir `float64` — ikisi de
aynı `{{.Adet}}` şablonundan geçtiğinde çıktı ikisinde de `12` dizgisidir, bayt bayt eşit.
Şablon çalışırken kaynağın hangi tipten geldiğini biliyordu (`Execute` bir arayüz değeri
alır ve alanına yansımayla erişir), ama ürettiği metnin içinde bu bilgiden hiçbir iz
kalmıyor. Çıktıyı okuyan bir program `12` dizgisinden geriye doğru "bu bir tamsayı mıydı,
ondalıklı mıydı" sorusunu yanıtlayamaz.

Bu, önceki dersteki tarifsiz sözlük gözlemiyle aynı yüzeye iniyor ama farklı bir yoldan.
Bir JSON değeri tarifsiz bir kaba çözüldüğünde sayı hep `float64` oluyordu — kayıp vardı,
ama kayıp **belirli bir yöndeydi**: her sayı aynı tek tipe düşüyordu ve bu düşüş
`encoding/json`'ın kendi kuralıydı. Şablon çıktısında böyle bir kural bile yok, çünkü
ortada hedef bir tip hiç yok — `int` de `float64` de aynı `string`e düşüyor, ama bu
düşüşün "tek bir hedef tipe toplanma" gibi bir yapısı yok; ikisi de sıradan metin
oldu ve metnin kendisi hiçbir tipe ait değil. JSON'un kaybı bir **dönüştürücünün**
kararıydı; şablonun kaybı, ortada dönüştürülecek bir hedef tipin **hiç bulunmamasının**
sonucu.

**AG21.** Dar şablon (`{{.Ad}} x{{.Adet}}`) ürettiği `vida x12` metninde ne `"Ad"` ne
`"Adet"` sözcüğü geçiyor — alan adları da tip bilgisi kadar kayboluyor, çünkü şablonun
yazdığı şey alanın **değeri**, alanın **adı** değil. Geniş şablon (`Ad: {{.Ad}}, Adet:
{{.Adet}}`) çıktısında `"Ad"` görünüyor, ama bunun nedeni Go'nun yapı alanının adını
otomatik olarak taşıması değil — şablonu yazan kişi `"Ad: "` dizgisini kendi eliyle,
düz metin olarak şablonun içine yazdı. Alan adının çıktıda görünmesi, tip sisteminden
gelen bir garanti değil, şablon metnini yazan kişinin bir kararı.

Şablonun çıktısının saf metin olması bir sınırlama olduğu kadar bir esneklik de taşıyor:
hedefin bir HTML belgesi olması zorunlu değil. `text/template` bir yapılandırma dosyası,
bir e-posta gövdesi, bir günlük satırı, hatta başka bir dilde yazılmış bir kaynak dosyası
üretmek için de kullanılabilir — üretilen şey ne olursa olsun, şablon paketinin gözünde
fark etmiyor, çünkü paket yalnızca bir metni bir başka metne, alan değerlerini yerine
koyarak çeviriyor. Kursun bir sonraki dersi bu son kullanımı, kaynak kod üretimini,
tam olarak bu noktadan devralıyor.

`UrunTamsayi` ve `UrunOndalikli` bilinçli olarak tek alanlı, birbirine paralel iki tip
olarak tanımlandı: aralarındaki tek fark `Adet` alanının tipi, başka hiçbir şey değişmiyor.
Bu, ölçümün gösterdiği şeyin yalnızca "tip bilgisi kayboluyor" olduğunu, başka bir yapısal
farktan (alan sayısı, alan sırası, alan adı) kaynaklanmadığını netleştiriyor — iki tip aynı
şablon eylemine (`{{.Adet}}`) aynı adla yanıt veriyor ve aradaki tek değişken tip olduğu
için, çıktıdaki eşitlik doğrudan bu tek değişkene bağlanabiliyor.

#### Metin ile HTML Şablonunun Kaçırma Farkı

**AG22.** Aynı `Urun` değeri, aynı `{{.Yorum}}` şablon metniyle, iki farklı pakete
verildiğinde iki farklı bayt dizisi üretiyor. `text/template`, `Yorum` alanındaki
`<b>` ve `&` karakterlerine hiç dokunmadan olduğu gibi yazıyor. `html/template` aynı
karakterleri `&lt;`, `&gt;` ve `&amp;` dizgilerine çeviriyor — bu dönüşüm **kaçırma**
(M14/K01 Web'in Temelleri ve HTML kursunda kurulan terim). `html/template` bunu
otomatik yapıyor çünkü çıktısının bir HTML belgesinin içine yerleşeceğini varsayıyor
ve `<`, `>`, `&` karakterlerinin HTML'de özel bir anlamı olduğunu biliyor; `text/template`
hiçbir varsayım yapmıyor, çünkü çıktısının nereye gideceğini bilmiyor.

M14/K01 kursu HTML üretimini ve kaçırma davranışını tarayıcı tarafında, sunucudan gelen bir
belgenin işlenmesi bağlamında kurdu; burada tekrarlanmıyor, tek fark bu iki paketin aynı standart
kitaplığın içinde, sunucu tarafında bir üretim adımı olarak durması. İki paket arasındaki seçim
bir stil tercihi değil: çıktı bir HTML sayfasına gidecekse `html/template` kullanılmalı, çünkü
kaçırmayı atlayan bir metin şablonu, veri içinde `<script>` gibi bir dizi geçtiğinde bunu olduğu
gibi yazan bir çıktı üretir.

`html/template`in kaçırması bu ölçümde göründüğünden daha ayrıntılı çalışıyor: paket bir
alanın şablonun **neresinde** kullanıldığını (bir etiket gövdesinde mi, bir öznitelik
değerinde mi, bir bağlantı adresinde mi) ayrıştırma sırasında saptıyor ve her bağlama
uygun kaçırma kuralını uyguluyor — bir öznitelik değerindeki tırnak işareti ile bir etiket
gövdesindeki `<` karakteri farklı kurallarla kaçırılıyor. Bu ders yalnızca en basit bağlamı
(bir etiket gövdesi) ölçüyor; bağlam farkındalığının kendisi ayrı bir konu ve burada
derinleştirilmiyor. Önemli olan sonuç şu: `text/template` çıktısını hangi bağlamda
kullanacağını hiç bilmiyor ve hiçbir karakteri değiştirmiyor, `html/template` ise çıktısının
bir HTML belgesine yerleşeceğini varsayıyor ve buna göre davranıyor. Kaçırılmamış bir
kullanıcı girdisi bir HTML belgesinin içine olduğu gibi yerleştiğinde, tarayıcı onu düz
metin değil **yapı** olarak okuyabilir — kaçırmanın kendisi, aradan geçen değerin görünümünü
değil, karşı tarafın onu nasıl yorumlayacağını değiştiren bir adım.

#### Alan Adı Çalışma Zamanında Aranır

**AG23.** `{{.OlmayanAlan}}` ifadesi `Urun` tipinde var olmayan bir alanı istiyor.
Bu şablon derleniyor — çünkü şablon metni Go'nun derleyicisinin hiç görmediği, ayrı bir
dilde yazılmış bir dizgi; derleyici `Urun` tipinin hangi alanlara sahip olduğunu bilir
ama bir dizginin içindeki `{{.OlmayanAlan}}` ifadesini hiç okumaz. Alan araması,
`Execute` çağrıldığında, yansıma üzerinden **çalışma zamanında** yapılıyor ve o an
alan bulunamayınca `Execute` bir hata döndürüyor. Kursun "yazılmayanın karşılığı"
sorusuna (M09/K01'de kurulan eksen, burada tekrar kullanılmıyor) benzer bir soru burada
da geçerli: derleyicinin hiç bakmadığı bir katman, kendi hatasını kendi zamanında üretiyor.

Şablonun kendi hata zamanları da tek değil. `template.Must` çağrısı, `Parse`in döndürdüğü
hatayı sarıp panikliyor — bu ders boyunca hiç tetiklenmedi, çünkü her şablonun metni
sözdizimsel olarak geçerliydi (`{{` her zaman bir `}}` ile kapandı, her eylem tanınan bir
eylemdi). `Parse` hatası, örneğin kapanmamış bir `{{` parçası için, programın en başında,
şablon kurulurken çıkar. `Execute` hatası ise sözdizimi tamamen geçerliyken, yalnızca
istenen alan yokken çıkıyor. İkisi de çalışma zamanında (Go'nun derleyicisi ikisini de
görmüyor), ama biri şablon kurulur kurulmaz, öbürü ancak gerçek veriyle çalıştırıldığında
ortaya çıkıyor — ve bu ders yalnız ikinciyi ölçtü, çünkü ilkini tetikleyecek hiçbir örnek
kullanılmadı.

**AG24.** `hatali` şablonu `"on: {{.Ad}}, sonra: {{.OlmayanAlan}}"` metnini taşıyor ve
iki ayrı eylem içeriyor: önce `{{.Ad}}` başarıyla değerlendiriliyor ve `"on: vida, sonra:
"` metni `bHatali` arabelleğine yazılıyor; sonra `{{.OlmayanAlan}}` başarısız oluyor ve
`Execute` bir hata dönüyor. Ama arabellek geri alınmıyor — hatadan önce yazılmış olan
kısım orada kalıyor, çünkü `Execute` metni **ilerleyerek** yazıyor, bir sonuç biriktirip
en sonda tek seferde yazmıyor. Bu, ölçümün son satırında görünen `"on: vida, sonra: "`
dizgisinin nereden geldiğini açıklıyor: hata, çıktının ortasında, ikinci alan aranırken
doğuyor ve o ana kadar üretilmiş her şey `io.Writer`'a zaten ulaşmış oluyor.

**AG25.** Bu, kursun "reddedildi" ile "açıkta kaldı" ayrımına yeni bir görünüm ekliyor.
Derleyici alan adı hatasını hiç görmüyor — kaynak her koşulda derleniyor — ve hata ne
derleme zamanında ne de şablon ayrıştırılırken (`Parse` çağrısında) çıkıyor; yalnızca
`Execute` çalıştığında ve o çalıştırma anında yazılmış olan çıktı kalıcı oluyor. Bir
şablonu üretime almadan önce sınamanın tek yolu, onu gerçek verilerle **çalıştırmaktır**;
alan adlarının doğruluğunu statik olarak denetleyen bir araç bu ikilinin dışında kalıyor.

Bunun pratik sonucu, bir şablonun içindeki nadiren tetiklenen bir dalın (bir `{{if}}`
koşulunun yalnızca belirli bir veriyle geçtiği bir satırın) aylarca hiç çalıştırılmadan
durabilmesi. Go Temelleri kursunun Go Komut Ailesi dersi, inceleyicinin derleyicinin
gözden kaçırdığı bir kalıbı yakaladığını göstermişti — biçim dizgisi ile argüman tipinin
uyuşmaması. Şablon alan adları için böyle bir ikinci denetleyici yok; şablon dilinin
kendisi Go'nun tip sisteminin dışında, ayrı bir dilde yaşıyor ve derleyicinin de
inceleyicinin de erişemediği bir katmanda çalışıyor. Bu yüzden bir şablonun doğruluğu,
derlemenin verdiği güvenceyle değil, o şablonu gerçek veriyle en az bir kez çalıştırmış
olmanın verdiği güvenceyle ölçülüyor — ve bu ders bu ölçümü tam olarak koşturarak yaptı.

#### Özet

- Bir şablonun ürettiği şey her zaman metindir; kaynağın tipi ne olursa olsun (`int`,
  `float64`) aynı şablon aynı dizgiyi üretir ve çıktıdan geriye doğru kaynağın tipine
  ulaşılamaz.
- Alan adları da çıktıda otomatik olarak yer almaz — bir ad çıktıda görünüyorsa, bunun
  nedeni şablonu yazan kişinin o adı düz metin olarak şablona yazmış olmasıdır.
- `text/template` ve `html/template` aynı şablon dilini paylaşır, ama `html/template`
  çıktısını HTML kaçırmasından geçirir; aynı veri iki pakette iki farklı bayt dizisi
  üretir.
- Şablondaki bir alan adı derleme zamanında değil, `Execute` çalışırken, yansımayla
  aranır; olmayan bir alan şablonun derlenmesini değil çalıştırılmasını durdurur.
- Hata, çıktının ortasında doğar: `Execute` hata döndürmeden önce yazdığı kısım
  `io.Writer`'da kalıcı olarak durur, geri alınmaz.

#### Sonraki Adım

Bu derste bir şablonun ürettiği çıktının tip taşımayan, saf metin olduğu ölçüldü — ve
bu metnin okunması için karşı tarafta hiçbir tarif kurulmamıştı. Kursun bir önceki
dersi (JSON dizileştirme) ile bu ders arasındaki fark tam olarak burada duruyor: biri
tarifle geri dönen bir gidiş, öbürü hiç geri dönmeyen bir çıkış. Ama iki dersin de ortak
bir noktası var: ikisinde de tarif, varsa, sınırın yalnızca **bir tarafında** duruyordu —
JSON'da alıcıdaki hedef tip, şablonda hiç kimse. Sıradaki ders bu iki ucu birleştiriyor:
iki ayrı taraf, aynı tarifi paylaştığında sınırı geçen tip bilgisine ne olur? Sözleşme
temelli servis iletişimi bu soruyu, tarifin kendisini iki tarafın kaynağını üretecek
biçimde kurarak yanıtlıyor — ve kursun on üç sınır geçişi arasında tip bilgisinin
korunduğu tek satırı orada kuruyor.

### Sözleşme Temelli Servis İletişimi

Source: https://academia.sh/tr/kurslar/go-uygulamalar/sozlesme-temelli-servis-iletisimi

İki bağımsız sürecin kaynağı aynı paylaşılan tariften (alan adı, alan tipi, JSON etiketi listesi) text/template ile üretiliyor ve ayrı ayrı derlenip çalıştırılıyor. Tarif ortakken üç alanın üçü de tipini koruyor — kursun on üç sınır geçişi arasında korunuyor sınıfına giren tek satır. Tarif ayrışınca (alıcı tarafın üreticisi güncel değilse) geçiş sessizce bozuluyor: üç alandan ikisi korunuyor, biri hiç hata vermeden kayboluyor.

Önceki ders bir şablonun ürettiği metnin hiçbir tarafta okunmayı beklemediğini gösterdi:
çıktı yalnızca metindi ve o metni geri okuyup bir tipe bağlayacak hiçbir taraf yoktu. Bu
ders tam tersi bir uçtan başlıyor. İki bağımsız program (iki ayrı süreç, iki ayrı derleme)
bir değeri birbirine geçirecek ve bu kez sınırın **iki tarafında da** aynı tarif duruyor
olacak. Kursun sorusu değişmiyor — bu değer sınırı geçerken tip bilgisine ne oluyor? — ama
bu ders, on üç sınır geçişinin **korunuyor** sınıfına giren tek satırını kuruyor.

Bir tarif burada, iki tarafın da bir alan listesi üzerinde anlaştığı bir açıklamadır: her
alanın adı, Go tipi ve JSON etiketi. Önceki derslerde tarif hep sınırın **bir** tarafında
duruyordu — JSON'da alıcının elindeki hedef tip, kod ailesinde inceleyicinin bildiği kural.
Bu derste tarif, sınırın hiçbir tarafına ait olmayan, üçüncü bir yerde (bir Go dilimi olarak
programın kendi belleğinde) tutuluyor ve **aynı tarif** iki tarafın da kaynağını üretmek
için kullanılıyor. Üretim standart kitaplığın kendi şablon paketiyle yapılıyor; hiçbir
üçüncü taraf araç, protokol ya da veri biçimi devreye girmiyor.

İki tarafın kaynağını elle, ayrı ayrı yazmak da mümkündü — iki `Kayit` tipi, iki dosyada,
aynı alan adlarıyla. Ama bu durumda "aynı" olmaları bir **disiplin** meselesine dönüşürdü:
biri değişirken öbürü unutulabilir ve bu unutuluş kaynak kodunun kendisinde görünmezdi.
Tarifi tek bir yerde tutup iki kaynağı ondan üretmek bu riski ortadan kaldırmıyor — birazdan
görüleceği gibi, üreticinin kendisi güncellenmezse aynı sorun geri geliyor — ama riski
**tek bir noktaya** topluyor: artık iki elle yazılmış kaynağın senkron kalması değil, tek
bir tarifin iki üretime de aynı anda verilmesi yeterli.

#### Aynı Tariften İki Tarafın Kaynağını Üretmek

**AG26.** Aşağıdaki yordamlar bir tarifi (`AlanTarifi` dilimini) alıp, `Rol` alanına göre
iki farklı kaynak üretebilen tek bir şablonu çalıştırıyor: `"gonderen"` rolü bir değeri
JSON'a çevirip standart çıktıya yazan bir program üretiyor, `"alici"` rolü standart girdiden
okuyup aynı yapıya çözen bir program üretiyor. `calistir` her iki üretilmiş kaynağı da
**ayrı bir geçici modülde**, `go run .` ile, ayrı bir süreç olarak çalıştırıyor — bu iki
"taraf" birbirinin kaynağını hiç görmüyor, yalnızca standart girdi/çıktı üzerinden bayt
alışverişi yapıyor.

```go
// sozlesme.go — paylasilan tarifi iki tarafin kaynagini uretecek sekilde calistiran yordamlar
package main

import (
	"bytes"
	"os"
	"os/exec"
	"path/filepath"
	"text/template"
)

type AlanTarifi struct {
	Ad     string
	Tip    string
	Etiket string
	Ornek  string
}

type SablonVerisi struct {
	Alanlar []AlanTarifi
	Rol     string
}

func uret(sablon *template.Template, veri SablonVerisi) string {
	var b bytes.Buffer
	sablon.Execute(&b, veri)
	return b.String()
}

func calistir(kaynak string, girdi []byte) string {
	dizin, _ := os.MkdirTemp("", "sozlesme")
	defer os.RemoveAll(dizin)
	os.WriteFile(filepath.Join(dizin, "go.mod"), []byte("module taraf\n\ngo 1.24\n"), 0o644)
	os.WriteFile(filepath.Join(dizin, "main.go"), []byte(kaynak), 0o644)

	cmd := exec.Command("go", "run", ".")
	cmd.Dir = dizin
	cmd.Env = append(os.Environ(), "GOTOOLCHAIN=local")
	if girdi != nil {
		cmd.Stdin = bytes.NewReader(girdi)
	}
	cikti, _ := cmd.Output()
	return string(cikti)
}
```

`uret` şablonu bir `bytes.Buffer`e yazdırıp dizgi olarak döndürüyor — bir önceki dersin
ölçtüğü tam olarak bu adım, burada bir kaynak kodu üretmek için kullanılıyor. `calistir`
ise Go Temelleri kursunun Go Komut Ailesi dersinde kurulan desenin aynısı: kaynağı geçici
bir modüle yazıp derleyiciyi (burada `go run` üzerinden) program içinden çağırmak. Fark
şurada — orada tek bir kaynak biçimlendiriliyor ya da inceleniyordu, burada iki farklı
kaynak, aynı tariften türeyip, birbirinden habersiz iki ayrı süreçte çalıştırılıyor.

Şablonun kendisi ikinci bir blokta duruyor; şablon metni, tarif dilimindeki her alan için
bir yapı alanı ve rolüne göre ya bir kodlama ya bir çözme gövdesi üretiyor. İçe aktarım
listesi de role göre dallanıyor: `"fmt"` ile `"io"` yalnızca `alici` rolünde ekleniyor,
çünkü `gonderen`in gövdesi ikisini de hiç kullanmıyor. Bu ayrım rastgele değil — kullanılmayan
bir içe aktarımın Go'da derlemeyi durdurduğu, Go Temelleri kursunda kurulmuş bir kuraldı;
şablon bu kuralı bilmiyor ama üretici (yani bu ders) biliyor ve iki farklı kaynak metni
üretirken her ikisinin de kendi başına derlenebilir kalmasını rolüne göre dallanan bir
`{{if}}` bloğuyla sağlıyor.

```go
// olcum.go — ortak tarifle ve ayrisan tarifle sozlesme temelli gecisin olcumu
package main

import (
	"fmt"
	"strings"
	"text/template"
)

const tikTik = "`"

var sablonMetni = `package main

import (
	"encoding/json"
{{if eq .Rol "alici"}}	"fmt"
	"io"
{{end}}	"os"
)

type Kayit struct {
{{range .Alanlar}}	{{.Ad}} {{.Tip}} ` + tikTik + `json:"{{.Etiket}}"` + tikTik + `
{{end}}}

func main() {
{{if eq .Rol "gonderen"}}	k := Kayit{
{{range .Alanlar}}		{{.Ad}}: {{.Ornek}},
{{end}}	}
	bayt, _ := json.Marshal(k)
	os.Stdout.Write(bayt)
{{else}}	bayt, _ := io.ReadAll(os.Stdin)
	var k Kayit
	json.Unmarshal(bayt, &k)
{{range .Alanlar}}	fmt.Printf("%s tip=%T deger=%v\n", "{{.Ad}}", k.{{.Ad}}, k.{{.Ad}})
{{end}}{{end}}}
`

func main() {
	sablon := template.Must(template.New("taraf").Parse(sablonMetni))

	ortakTarif := []AlanTarifi{
		{Ad: "Ad", Tip: "string", Etiket: "ad", Ornek: `"vida"`},
		{Ad: "Adet", Tip: "int", Etiket: "adet", Ornek: "12"},
		{Ad: "Fiyat", Tip: "float64", Etiket: "fiyat", Ornek: "19.90"},
	}

	gonderenKaynagi := uret(sablon, SablonVerisi{Alanlar: ortakTarif, Rol: "gonderen"})
	fmt.Println("-- uretilen gonderen kaynagi --")
	fmt.Println(gonderenKaynagi)

	tasinanBayt := calistir(gonderenKaynagi, nil)

	aliciKaynagi := uret(sablon, SablonVerisi{Alanlar: ortakTarif, Rol: "alici"})
	aliciCiktisi := calistir(aliciKaynagi, []byte(tasinanBayt))
	fmt.Println("-- tarif ortak: alici uc alani da okuyor --")
	fmt.Print(aliciCiktisi)
	korunanOrtak := strings.Count(strings.TrimRight(aliciCiktisi, "\n"), "\n") + 1

	eskiTarif := ortakTarif[:2]
	eskiAliciKaynagi := uret(sablon, SablonVerisi{Alanlar: eskiTarif, Rol: "alici"})
	eskiAliciCiktisi := calistir(eskiAliciKaynagi, []byte(tasinanBayt))
	fmt.Println()
	fmt.Println("-- tarif ayristi: alici tarafin uretici tarifi guncel degil --")
	fmt.Print(eskiAliciCiktisi)
	korunanEski := strings.Count(strings.TrimRight(eskiAliciCiktisi, "\n"), "\n") + 1

	fmt.Println()
	fmt.Printf("tarif ortak: %d/%d alanin tipi koruniyor\n", korunanOrtak, len(ortakTarif))
	fmt.Printf("tarif ayristi: %d/%d alanin tipi koruniyor, %d alan sessizce kayboldu\n",
		korunanEski, len(ortakTarif), len(ortakTarif)-korunanEski)
}
```

```
-- uretilen gonderen kaynagi --
package main

import (
	"encoding/json"
	"os"
)

type Kayit struct {
	Ad string `json:"ad"`
	Adet int `json:"adet"`
	Fiyat float64 `json:"fiyat"`
}

func main() {
	k := Kayit{
		Ad: "vida",
		Adet: 12,
		Fiyat: 19.90,
	}
	bayt, _ := json.Marshal(k)
	os.Stdout.Write(bayt)
}

-- tarif ortak: alici uc alani da okuyor --
Ad tip=string deger=vida
Adet tip=int deger=12
Fiyat tip=float64 deger=19.9

-- tarif ayristi: alici tarafin uretici tarifi guncel degil --
Ad tip=string deger=vida
Adet tip=int deger=12

tarif ortak: 3/3 alanin tipi koruniyor
tarif ayristi: 2/3 alanin tipi koruniyor, 1 alan sessizce kayboldu
```

**AG27.** `ortakTarif` üç öğeli bir Go dilimi ve `main` fonksiyonu içinde tam olarak **bir
kez** tanımlanıyor. `gonderenKaynagi` ve `aliciKaynagi` ikisi de bu **aynı değişkenden**
üretiliyor — `uret` çağrısına ikisinde de `ortakTarif` geçiyor, başka bir kopya, başka bir
elle yazılmış liste yok. Bu, SINIR ortak tanımının koyduğu kuralı karşılıyor: bir ders
"korunuyor" satırı yazacaksa, tarifin iki tarafa nasıl ulaştığını da göstermek zorunda —
burada gösterilen yol, aynı bellek değerinin iki ayrı üretim çağrısına girdi olması.
Üretilen kaynaklar sonra birbirinden tamamen bağımsız: ayrı geçici dizinlerde, ayrı `go.mod`
dosyalarıyla, ayrı süreçler olarak derlenip çalıştırılıyor. `gonderen` tarafının kaynağı
`alici` tarafının kaynağını hiç görmüyor, ikisi yalnızca `tasinanBayt` üzerinden, düz bir
bayt dizisiyle konuşuyor.

Ayrı süreç olarak çalıştırma burada bir ayrıntı değil, ölçümün gövdesi. Eğer `gonderen` ve
`alici`, aynı Go paketinden içe aktarılan **tek** bir `Kayit` tipini paylaşsaydı, tip
korunması Go'nun derleyicisinin garantisi olurdu — iki taraf zaten aynı bellek düzenine
bakıyor olurdu ve bu ders ölçmek istediği şeyi baştan varsaymış olurdu. Ayrı derlenen,
ayrı çalışan iki ikili arasında yalnızca JSON baytlarıyla konuşmak, sınırı gerçekten bir
sınır yapıyor: iki taraf, tarif ortak olduğu için değil, tarif ortak olduğunu **her ikisinin
de bağımsızca ürettiği** için aynı tipte anlaşıyor.

**Kör nokta.** Bu ölçümün "korunuyor" sınıfı, tarifin iki tarafa da gerçekten ulaştığı
varsayımı üzerine kurulu — ve bu ders bu ulaşmayı, iki üretim çağrısının aynı Go dilimini
okuması olarak **modelliyor**. Üretimde iki tarafın aynı tarifi paylaşması genellikle bir
dağıtım adımı gerektirir (bir tarafın güncellenmiş tarifi öbürüne bir şekilde ulaştırması);
bu ders o dağıtım adımının kendisini ölçmüyor, yalnızca dağıtım **başarılı olduğunda** ne
kazanıldığını ve **başarısız olduğunda** (aşağıdaki bölüm) ne kaybedildiğini ölçüyor.

#### Tarif Ortakken Üç Alanın Üçü de Tipini Koruyor

**AG28.** `alici` kaynağı, `ortakTarif`ten üretildiğinde, `gonderen`in yazdığı JSON'u kendi
`Kayit` tipine çözüyor ve üç alanın üçünü de doğru tipiyle okuyor: `Ad` bir `string`,
`Adet` bir `int`, `Fiyat` bir `float64` olarak geri geliyor — kaynak taraftaki tiplerle
birebir aynı. Bu satır kursun on üç sınır geçişinin tam dağılımındaki (1 korunuyor, 5
yeniden kuruluyor, 7 kayboluyor; 03. derste kurulan sayı) tek **korunuyor** satırı. Önceki
derslerdeki "yeniden kuruluyor" satırları da bir tarif kullanıyordu (yapı etiketi, hedef
tip, biçim dizgisindeki damga), ama o tarif hep sınırın **bir** tarafındaydı ve karşı taraf
kendi tarifini kendi başına, ayrı bir kaynaktan biliyordu. Burada ikisi de **aynı** tarifin
ürünü — bu fark, "yeniden kuruluyor" ile "korunuyor" arasındaki bütün mesafe.

**AG29.** Gönderen ve alıcının kaynak kodları birbirinden farklı: biri `json.Marshal`
çağırıyor, öbürü `json.Unmarshal`; biri `os.Stdout`a yazıyor, öbürü `os.Stdin`den okuyor.
Ama ikisinin `Kayit` tipi bayt bayt aynı alan adlarını ve aynı JSON etiketlerini taşıyor,
çünkü ikisi de `{{range .Alanlar}}` döngüsünü **aynı** `ortakTarif` dilimi üzerinde
çalıştırdı. Kodun kendisi aynı değil, ama kodu üreten **tarif** aynı — ve JSON'un alan adı bağlaması
(bir etiketin dize karşılığı üzerinden kurulur) yalnızca bu ikisinin etiketleri aynı olduğu
sürece çalışıyor. Tarif tek bir kaynaktan geldiği için etiketlerin aynı olması bir tesadüf
değil, üretim biçiminin doğrudan sonucu.

#### Tarif Ayrışınca: Sessiz Kayıp

**AG30.** `eskiTarif`, `ortakTarif`in ilk iki öğesi — `Fiyat` alanı yok. Bu, alıcı tarafın
üreticisinin güncellenmediği, gönderen tarafın ise güncel kaldığı bir durumu modelliyor:
iki taraf artık aynı tarifi paylaşmıyor. `eskiAliciKaynagi` bu eksik tariften üretiliyor ve
kendi `Kayit` tipinde yalnızca `Ad` ile `Adet` alanları var. Bu kaynak **derleniyor** —
`Fiyat`in yokluğu Go'nun tip sisteminde hiçbir sorun değil, çünkü eksik alan başka bir
tipin parçası olmayı hiç talep etmiyor — ve **çalışıyor**, `json.Unmarshal` çağrısı hiçbir
hata döndürmüyor.

**AG31.** `tasinanBayt` üç alanı da taşıyor (`{"ad":"vida","adet":12,"fiyat":19.9}`), ama
`eskiAliciKaynagi`nin çözdüğü yapı yalnızca ikisini okuyabiliyor. `fiyat` anahtarı JSON
metninde duruyor, `json.Unmarshal` onu görüyor ve hedef yapıda karşılığı olmadığı için
**sessizce atlıyor** — üçüncü paydaki JSON dersinin "alıcıda karşılığı olmayan alan sessizce
düşüyor" gözlemiyle birebir aynı düzenek, burada tek farkla: kayıp, bir programlama
hatasından değil, iki tarafın tarifinin **birbirinden ayrışmasından** doğuyor. Ölçümün son
satırı bunu sayıyla veriyor: tarif ortakken 3/3, tarif ayrışınca 2/3 alanın tipi korunuyor
ve kayıp alan hakkında hiçbir uyarı, hiçbir hata çıkmıyor.

Yönün önemi burada ortaya çıkıyor: eksik olan taraf **alıcı**. Gönderen tarafın kaynağı güncel
kaldığı, yani `Fiyat`i hâlâ gönderdiği sürece, hatanın hiçbir belirtisi gönderen tarafta
görünmüyor — `gonderenKaynagi` bu derste hiç değişmedi; `calistir(gonderenKaynagi, nil)` çağrısı
iki senaryoda da aynı baytı üretti. Sorun yalnızca alıcı tarafın ürettiği kaynakta ve bu kaynağı
üreten tarafın (kendi `eskiTarif`ini kullanan üretici) güncellenmemiş olmasında. Eğer roller
tersine çevrilseydi — gönderen eski tarifle, alıcı güncel tarifle üretilseydi — sonuç farklı
olurdu: alıcının beklediği `Fiyat` alanı JSON'da hiç bulunmazdı ve yapı bu alanı kendi sıfır
değeriyle (`0`) doldururdu, çünkü `encoding/json` eksik bir anahtarı da tıpkı bilinmeyen bir
anahtar gibi sessizce görmezden geliyor. İki yön de aynı sınıfa (kayboluyor) düşüyor, ama kaybın
**nerede** göründüğü değişiyor: bir yönde alıcının okuduğu yapıda alan hiç yok, öbür yönde alan
var ama hep sıfır.

**AG32.** Bu, sözleşme temelli iletişimin bütün iddiasını tersinden kanıtlıyor: koruma
tarifin **kendisinden** gelmiyor, tarifin **dağıtımından** geliyor. Aynı tarif iki tarafa
aynı anda ulaştığı sürece geçiş korunuyor sınıfında kalıyor; taraflardan biri güncellenip
öbürü kalırsa, geçiş hiçbir hata vermeden yeniden "kayboluyor" sınıfına düşüyor. Tek bir
paylaşılan tarif kaynağı (bu derste `ortakTarif` değişkeni) korumanın **önkoşulu**; tarifin
kendisi hiçbir zaman bir garanti değil, yalnızca iki tarafın ne kadar senkron kaldığına
bağlı bir olasılık.

Bu sonucun kursun genel iddiasıyla ilişkisi dolaylı değil, doğrudan: on üç sınır geçişinin
beşinde tip "yeniden kuruluyordu" ve her birinde geri kuran taraf, karşı tarafın elinde
bulunan bir tarifti — ama o tarif hep karşı tarafın **kendi** tarifiydi, gönderen tarafın
tarifiyle aynı olduğunu doğrulayan hiçbir mekanizma yoktu. Bu ders o iki tarifi tek bir
tarife indirgeyerek "yeniden kurma" ile "koruma" arasındaki farkı ortaya koydu: yeniden
kurma, karşı tarafın **tahmininin** doğru çıkmasına dayanıyordu; koruma, tahmine hiç gerek
kalmamasına.

#### Özet

- İki bağımsız sürecin kaynağı, aynı paylaşılan tarif dilimi üzerinden `text/template` ile
  üretilip ayrı ayrı geçici modüllerde derlenip çalıştırıldı; taraflar birbirinin kaynağını
  görmedi, yalnızca bayt alışverişi yaptı.
- Tarif ortakken üç alanın üçü de tipini koruyor: `Ad`, `Adet`, `Fiyat` — kaynak ve hedef
  taraftaki tipler birebir eşleşti. Bu, kursun on üç sınır geçişi arasında `korunuyor`
  sınıfına giren tek satır.
- Koruma, tarifin `uret` çağrısına **aynı değerden** geçirilmesiyle gösterildi; kanıt olmadan
  bir "korunuyor" satırı yazılamayacağı kuralı burada tam olarak karşılandı.
- Tarif iki tarafta ayrışınca (bir alan eksik üretilince) kaynak yine derleniyor, program
  yine çalışıyor, ama eksik alan hiçbir hata vermeden düşüyor — 3/3'ten 2/3'e sessiz bir kayıp.
- Sözleşme temelli iletişimin koruması bir garanti değil, iki tarafın tarifinin senkron
  kalmasına bağlı bir sonuç.

#### Sonraki Adım

Bu derste ölçülen paylaşım, tarifin ne olduğuyla değil, **ne zaman** kurulduğuyla ilgiliydi:
gönderen tarafın kaynağı üretilip çalıştırıldı, sonra alıcı tarafın kaynağı ayrı bir süreçte
başladı ve gönderenin bıraktığı baytı okudu — iki taraf hiç aynı anda çalışmadı ve aralarında
tek bir alışveriş oldu: bir istek gibi görünen bir yazma, bir yanıt gibi görünen bir okuma.
Sıradaki ders bu varsayımı kaldırıyor: bağlantı **uzun sürdüğünde**, tek bir alışveriş
yerine art arda birçok geçiş aynı bağlantı üzerinden aktığında ve taraflardan biri karşı
taraftan bir istek gelmeden veri **itebildiğinde**, sınır ne değişiyor?

Çerçeveleme, mesajın nerede başlayıp bittiğini taşıyan tek bilgi olarak devreye giriyor —
bir akışın neresinin bir mesajın sonu, neresinin bir sonrakinin başı olduğunu söyleyen tek
düzenek. Ama bu ders tam olarak gösterdi ki bir sınırı doğru çizmek, içindeki değerin tipini
bilmekle aynı şey değil: çerçeveleme bir mesajın **nerede** bittiğini söyler, o mesajın
içinde `Ad`, `Adet`, `Fiyat` gibi alanların durup durmadığını değil. O soruyu yanıtlamak
yine bir tarif gerektirecek — bu dersin ölçtüğü türden bir tarif.

### Gerçek Zamanlı İletişim

Source: https://academia.sh/tr/kurslar/go-uygulamalar/gercek-zamanli-iletisim

Uzun süreli bir bağlantı, gerçek bağlantı noktası açılmadan, io.Reader ve io.Writer'ı gerçekleştiren bir bayt arabelleğiyle modelleniyor. Sunucu üç mesajı art arda, aralarında bir istek beklemeden itiyor. Çerçevesiz bir akışta üç itme tek bir bayt dizisine karışıyor ve sabit boyutlu okumalar hiçbir mesajı doğru ayıramıyor (0/3); uzunluk ön ekiyle çerçevelenen akışta üçü de doğru geri ayrışıyor (3/3). Sınırlayıcı ölçüm: çerçeveleme mesajın nerede bittiğini veriyor, içindeki değerin tipini vermiyor.

Önceki ders iki tarafın tek bir alışveriş yaptığı bir sınırı ölçtü: gönderen bir kez yazdı,
alıcı bir kez okudu, iki taraf hiç aynı anda çalışmadı. Bu ders bu varsayımı kaldırıyor.
Bir bağlantı **uzun sürdüğünde** — açılıp kapanmadan art arda birçok geçişi taşıdığında —
sınırı geçen tek şey artık bir bayt dizisinin **içeriği** değil, o bayt dizisinin **nerede
bittiği**. Kursun sorusu aynı kalıyor, ama bu kez sorulduğu yer bir tek değerin sınırı değil,
zaman içinde uzayan bir akış.

Gerçek bir ağ bağlantısı bu derste hiç açılmıyor. Bunun yerine bir `bytes.Buffer`
kullanılıyor — standart kitaplığın bayt arabelleği, `io.Reader` ve `io.Writer`
arayüzlerinin ikisini de gerçekleştiriyor, tıpkı gerçek bir ağ bağlantısının
gerçekleştireceği gibi. Bu ders bağlantıyı bu arabellekle **modelliyor**; ölçülen kod
yalnızca bu iki arayüzü kullanıyor ve aynı kod gerçek bir bağlantı üzerinde de değişmeden
çalışırdı, çünkü ikisi de aynı sözleşmeyi karşılıyor. Adres, bağlantı noktası ve süre
hiçbir yerde basılmıyor, çünkü hiçbiri bu modelde hiç var olmuyor.

Bu, kursun HTTP Sunucusu ve HTTP İstemcisi derslerinin izlediği stratejinin bir devamı.
O iki ders de gerçek bir bağlantı noktası açmadan, bir test yardımcısıyla ölçüm yapmıştı;
burada aynı fikir farklı bir katmanda uygulanıyor. HTTP'nin kendi test yardımcısı tek bir
istek–yanıt çiftini modellemek için yeterliydi, çünkü HTTP'nin sınırı zaten netti — bir
istek başlar, bir yanıt biter, bağlantı kapanır ya da bir sonraki isteğe hazır bekler. Uzun
süreli bir bağlantıda böyle bir sınır yok; sunucu istediği zaman yazabiliyor, istemci
istediği zaman okuyabiliyor ve tam da bu yüzden bu dersin ölçtüğü şey "bir isteğin ne
taşıdığı" değil, "akışın neresinde bir mesajın bittiği".

Bu fark ölçüm aracının seçimini de belirliyor. HTTP dersleri gerçek bir istemci ve sunucu
nesnesi kurup aralarında tek bir alışverişi ölçtü; bu ders tek bir paylaşılan bayt
arabelleğini hem yazan hem okuyan tarafın ortak zemini olarak kullanıyor, çünkü ölçülmek
istenen şey iki ayrı tarafın davranışı değil, aradaki **akışın kendisinin** yapısı — kaç
mesajın art arda geldiği ve bu mesajların akış içinde nasıl ayırt edildiği.

#### Bağlantı Modelleniyor: Aynı Arayüz, Farklı Somut Uygulama

**AG33.** `bytes.Buffer`, `io.Reader` ile `io.Writer`ı gerçekleştiriyor — tam olarak Go'nun
standart kitaplığının bir ağ bağlantısı için tanımladığı arayüzlerin ikisi. Sistem ve Veri
Erişimi konusunun ilk dersi bu iki arayüzü dosya, arabellek ve dizgi arasında paylaşan bir
olguyu ölçecek; bu ders aynı olgunun bağlantı tarafını, ağ hiç açılmadan, önceden kuruyor.
Aşağıdaki yordamlar bir mesajı önce dört baytlık büyük uçlu bir uzunluk öneki, sonra
mesajın kendi baytlarıyla yazıyor; okuma tarafı önce bu dört baytı okuyup uzunluğu
öğreniyor, sonra tam o kadar baytı istiyor.

Bu modelin bir sınırı var ve açıkça yazılması gerekiyor: gerçek bir ağ bağlantısında bir
`Write` çağrısının karşı tarafa ne zaman ulaştığı, tek bir çağrının kaç parçaya bölünerek
ilerlediği, gecikme ve sıralama gibi etkenlere bağlı olabilir. Bu ders bunların hiçbirini
modellemiyor — `bytes.Buffer` üzerindeki bir yazma anında tamamlanıyor ve bir sonraki okuma
her zaman en güncel hâli görüyor. Ölçülen şey gecikme ya da sıralama değil, yalnızca
**çerçevelemenin kendisi**: bir akışın içine gömülü mesajların, akışı okuyan tarafından
nasıl ayrıştırılacağı. Bu dar kapsam bilinçli; genişletilmiş bir model, aynı çerçeveleme
kuralının üzerine kurulur, onu değiştirmez.

```go
// cerceveleme.go — uzun sureli baglantida cercevenin mesaj sinirini nasil tasidiginin olcumu
package main

import (
	"bytes"
	"encoding/binary"
	"fmt"
	"io"
)

func cerceveleYaz(baglanti *bytes.Buffer, mesaj string) {
	var uzunluk [4]byte
	binary.BigEndian.PutUint32(uzunluk[:], uint32(len(mesaj)))
	baglanti.Write(uzunluk[:])
	baglanti.WriteString(mesaj)
}

func cerceveOku(baglanti *bytes.Buffer) (string, bool) {
	var uzunluk [4]byte
	if _, err := io.ReadFull(baglanti, uzunluk[:]); err != nil {
		return "", false
	}
	boyut := binary.BigEndian.Uint32(uzunluk[:])
	govde := make([]byte, boyut)
	if _, err := io.ReadFull(baglanti, govde); err != nil {
		return "", false
	}
	return string(govde), true
}

func main() {
	itilenler := []string{"merhaba", "veri", "kapanis"}

	var cercevesizBaglanti bytes.Buffer
	for _, m := range itilenler {
		cercevesizBaglanti.WriteString(m)
	}
	fmt.Println("-- cercevesiz baglantida uc itme tek bir bayt dizisine karisiyor --")
	dogruAyrisan := 0
	for {
		parca := make([]byte, 6)
		n, err := cercevesizBaglanti.Read(parca)
		if n == 0 {
			break
		}
		parcaMetni := string(parca[:n])
		fmt.Printf("okunan parca: %q\n", parcaMetni)
		for _, m := range itilenler {
			if parcaMetni == m {
				dogruAyrisan++
			}
		}
		if err != nil {
			break
		}
	}
	fmt.Printf("dogru ayrisan mesaj: %d/%d\n", dogruAyrisan, len(itilenler))

	var cerceveliBaglanti bytes.Buffer
	for _, m := range itilenler {
		cerceveleYaz(&cerceveliBaglanti, m)
	}
	fmt.Println()
	fmt.Println("-- cerceveli baglantida uc itme de sunucunun yazdigi sinirla geri ayrisiyor --")
	dogruAyrisanCerceveli := 0
	for i := 0; i < len(itilenler); i++ {
		m, tamam := cerceveOku(&cerceveliBaglanti)
		if !tamam {
			break
		}
		fmt.Printf("okunan mesaj: %q\n", m)
		if m == itilenler[i] {
			dogruAyrisanCerceveli++
		}
	}
	fmt.Printf("dogru ayrisan mesaj: %d/%d\n", dogruAyrisanCerceveli, len(itilenler))

	var sayisalItme bytes.Buffer
	cerceveleYaz(&sayisalItme, "42")
	govde, _ := cerceveOku(&sayisalItme)
	fmt.Println()
	fmt.Println("-- sinirlayici olcum: cerceve dogru govdeyi veriyor ama govdenin tipini vermiyor --")
	fmt.Printf("govde: %q, tipi: %T\n", govde, govde)
}
```

```
-- cercevesiz baglantida uc itme tek bir bayt dizisine karisiyor --
okunan parca: "merhab"
okunan parca: "averik"
okunan parca: "apanis"
dogru ayrisan mesaj: 0/3

-- cerceveli baglantida uc itme de sunucunun yazdigi sinirla geri ayrisiyor --
okunan mesaj: "merhaba"
okunan mesaj: "veri"
okunan mesaj: "kapanis"
dogru ayrisan mesaj: 3/3

-- sinirlayici olcum: cerceve dogru govdeyi veriyor ama govdenin tipini vermiyor --
govde: "42", tipi: string
```

**AG34.** `itilenler` dizisindeki üç mesaj `cercevesizBaglanti` ile `cerceveliBaglanti`ya
art arda yazılıyor — aralarında hiçbir okuma, hiçbir "istek" yok. Bu, HTTP sunucusu ve
istemcisi derslerinde kurulan istek–yanıt kalıbından farklı: orada her yanıt bir isteği
takip ediyordu, burada sunucu tarafı üç kez de kendi inisiyatifiyle **itiyor**. Uzun süreli
bir bağlantının veri itme özelliği tam olarak bu — karşı taraftan bir tetikleyici gelmeden
yazabilme.

Sunucunun bir isteği nasıl karşıladığı Backend Geliştirme müfredatının sekiz kursunda kavram
olarak kuruldu ve burada tekrarlanmıyor; bu dersin payı yalnızca Go'nun standart
kitaplığındaki iki arayüzün (`io.Reader`, `io.Writer`) böyle bir itmeyi taşıyıp taşımadığı.
Yanıt burada evet: `bytes.Buffer.Write` çağrısı hiçbir isteğe bağlı değil, yalnızca
çağrıldığı anda arabelleğin sonuna ekleme yapıyor — bu, bir sunucunun bir istemciye önceden
haber vermeden veri göndermesinin, standart kitaplık düzeyinde ne kadar sade bir işlem
olduğunu gösteriyor. Karmaşıklık başka bir yerde: karşı tarafın bu itmelerin nerede bittiğini
bilmesi gerekiyor ve bu bilgiyi taşıyan şey `Write`in kendisi değil, birazdan ölçülecek olan
çerçeveleme.

#### Çerçevesiz Akış: Üç İtme Tek Bir Bayt Dizisine Karışıyor

**AG35.** `cercevesizBaglanti`ya yazılan üç mesaj (`"merhaba"`, `"veri"`, `"kapanis"`)
aralarında hiçbir ayraç olmadan doğrudan art arda ekleniyor — sonuç, on sekiz baytlık tek
bir dizi. Okuyan taraf bu dizide bir mesajın nerede bitip ötekinin nerede başladığını **hiç
bilmiyor**; bildiği tek şey sabit boyutlu (altı baytlık) parçalar okumak. Bu parçalardan
hiçbiri özgün üç mesajdan biriyle eşleşmiyor — `"merhab"`, `"averik"`, `"apanis"` — çünkü
altı bayt sınırı mesaj sınırlarıyla hiçbir ilişkisi olmayan, tamamen keyfi bir bölme. Ölçüm
bunu `0/3` olarak veriyor: sabit boyutlu okuma, mesaj sınırlarını korumanın **hiçbir**
garantisini taşımıyor.

Altı bayt seçimi ölçümün bir ayrıntısı değil, tam olarak bu keyfiliği göstermek için
seçildi: üç mesajın uzunlukları (7, 4, 7) hiçbiri altıya bölünmüyor, bu yüzden hiçbir okuma
bir mesajın tam sınırında durmuyor. Okuma boyutu dört olsaydı ya da sekiz olsaydı sonuç yine
`0/3` olurdu, çünkü sorun okuma boyutunun büyüklüğü değil, okuma boyutunun mesaj
uzunluklarından **bağımsız**, dışarıdan seçilen bir sayı olması. Bu, uzun süreli bir
bağlantının temel bir özelliğine işaret ediyor: `io.Reader`ın bir `Read` çağrısı, kaç bayt
istendiyse o kadarını (ya da o an elde ne kadar varsa) döndürüyor — hangi baytların bir
mesajın başı, hangilerinin ortası olduğuna dair hiçbir bilgi taşımıyor. Bu bilgi akışın
kendisinde yoksa, okuyan tarafın onu **icat etmesi** gerekir; sabit boyutlu okuma böyle bir
icat ve bu ölçüm onun neden işe yaramadığını gösteriyor.

#### Çerçeveli Akış: Sınır Bilgisiyle Geri Ayrışıyor

**AG36.** `cerceveliBaglanti`ya yazılan her mesajdan önce, o mesajın uzunluğunu taşıyan
dört baytlık bir önek duruyor. Okuyan taraf artık tahmin etmiyor: önce dört baytı okuyup
uzunluğu öğreniyor, sonra tam o uzunlukta bir okuma yapıyor — ne eksik, ne fazla. Üç mesajın
üçü de bayt bayt doğru geri geliyor, `3/3`. Aradaki fark okuma boyutunun büyüklüğü değil
(çerçevesiz denemede altı bayt, mesajlardan bazılarından uzun bazılarından kısaydı); fark,
okuma boyutunun **her seferinde mesajın kendisinden geldiği**, dışarıdan sabit bir sayı
olarak dayatılmadığı.

`cerceveOku` iki okumasında da `io.ReadFull`ı kullanıyor, düz bir `Read` çağrısını değil.
Bu seçim rastgele değil: `io.Reader` arayüzünün sözleşmesi, istenen bayt sayısından **daha
az** döndürmeye izin veriyor, elde daha fazla veri olsa bile — `bytes.Buffer` bu ölçümde
her zaman istenen kadarını verdi, ama bu, arayüzün genel sözleşmesinin bir parçası değil,
bu belirli somut tipin bir davranışı. `io.ReadFull`, istenen bayt sayısı tam olarak
karşılanana kadar (ya akış bitene ya da bir hata çıkana kadar) okumayı tekrarlıyor; dört
baytlık uzunluk önekinin ya da değişken uzunluklu gövdenin tek bir `Read` çağrısında parça
parça, eksik gelmesi ihtimaline karşı bu bir güvence. Çerçeveleme kuralının kendisi kadar,
onu okuyan kodun bu ayrıntıya dikkat etmesi de doğru ayrışmanın bir parçası.

Uzunluk öneki, çerçevelemenin tek olası biçimi değil — bir mesajın sonunu işaretleyen özel bir
bayt (bir ayraç) da aynı işi görebilirdi. Ama ayraç tabanlı bir çerçeveleme, mesajın içeriğinde
ayracın kendisiyle aynı baytın geçme ihtimalini taşır: `"veri"` mesajı `"i"` harfiyle bitiyor;
ayraç olarak `"i"` seçilmiş olsaydı bu mesaj kendi içinde erken kesilmiş gibi görünürdü. Uzunluk
öneki bu riski taşımıyor, çünkü mesajın içeriğine hiç bakmıyor — önce "kaç bayt geleceğini"
söylüyor, sonra o kadar baytı, içinde ne olursa olsun, tek bir birim olarak okuyor. Bu ders bu iki
stratejiyi karşılaştırmıyor, yalnızca ikincisini ölçüyor, ama tercihin gerekçesi bu.

Kursun on üç sınır geçişinin tam dağılımı (1 korunuyor, 5 yeniden kuruluyor, 7 kayboluyor;
03. derste kurulan sayı) bu on üç geçişin dışında kalan, ayrı bir alt problemi çözüyor:
o dağılım tek bir değerin sınırı geçerken tipine ne olduğunu sayıyordu, bu ders ise bir
akışın **neresinin** bir değer olduğunu belirliyor. Çerçeveleme bu sorunun cevabı olmadan,
önceki derslerin JSON çözücüsü de tarif üretici de çalışamazdı — ikisi de baştan "burada bir
mesaj bitti" bilgisine ihtiyaç duyuyor ve bu bilgiyi taşıyan tek şey çerçeveleme.

Bilgisayar Ağları müfredatının Uygulama Katmanı Protokolleri kursu ile Frontend Geliştirme
müfredatının Uygulama Mimarisi: Yönlendirme, Durum ve Veri kursu, uzun süreli bağlantılar
ve gerçek zamanlı veri akışını kavram olarak zaten kurdu; burada tekrarlanmıyor, ölçülen
yalnızca Go'nun standart kitaplığının bir bayt akışını arayüz düzeyinde nasıl taşıdığı.

**AG37.** Son ölçüm bir uyarı: `cerceveleYaz` ile yazılıp `cerceveOku` ile geri gelen
gövde, `"42"` dizgisi — çerçeveleme burada da kusursuz çalışıyor, gövde bayt bayt doğru.
Ama `govde`nin Go tipi `string`, `int` değil. Çerçeveleme mesajın **nerede bittiğini**
söylüyor, içindeki baytların bir tamsayıyı mı, bir dizgiyi mi, yoksa bir yapıyı mı temsil
ettiğini söylemiyor. Bu soruyu yanıtlamak, önceki dersin ölçtüğü türden bir tarif
gerektiriyor — çerçeveleme ile tarif, uzun süreli bir bağlantıda birbirini tamamlayan iki
ayrı katman: biri sınırı çiziyor, öbürü sınırın içindekini adlandırıyor.

Bu ayrımın önemi, `"42"` gibi bir gövdenin **iki türlü de** okunabilir olmasında ortaya
çıkıyor. Karşı taraf bu gövdeyi bir tamsayıya çevirmeye çalışırsa (`strconv.Atoi` ile)
başarılı olur ve `42` sayısını elde eder; hiç çevirmeden dizgi olarak tutarsa yine geçerli
bir değeri olur. Çerçeveleme her iki yorumu da **eşit derecede mümkün** bırakıyor, çünkü
gövdenin baytları kendi başına hangi yorumun doğru olduğunu söylemiyor. Önceki derste
görülen sözleşme temelli tarif tam olarak bu belirsizliği kapatan şeydi: alan adı ve tipini
önceden sabitleyerek, gövdenin nasıl okunacağı konusunda iki tarafı da aynı karara
bağlıyordu. Uzun süreli bir bağlantıda bu iki katman genellikle birlikte kuruluyor —
çerçeveleme her mesajın sınırını, tarif her mesajın içindeki alanların anlamını taşıyor —
ama bu ders onları bilinçli olarak ayırdı, çünkü ikisi birbirinden bağımsız başarabilir ya
da başarısız olabilir: bir akış doğru çerçevelenip yine de yanlış yorumlanabilir.

#### Özet

- Uzun süreli bir bağlantı, gerçek bir ağ bağlantısı açılmadan, `io.Reader` ve `io.Writer`ı
  gerçekleştiren bir `bytes.Buffer` ile modellendi; ölçülen kod yalnızca bu iki arayüzü
  kullandığı için aynı mantık gerçek bir bağlantı üzerinde de değişmeden çalışırdı.
- Sunucu tarafı üç mesajı aralarında bir istek beklemeden itti — uzun süreli bir bağlantının
  veri itme özelliği, önceki derslerin istek–yanıt kalıbından bu yönüyle ayrılıyor.
- Çerçevesiz bir akışta üç mesaj tek bir bayt dizisine karıştı ve sabit boyutlu okumalar
  hiçbirini doğru ayıramadı (`0/3`); uzunluk ön ekiyle çerçevelenen akışta üçü de bayt bayt
  doğru geri geldi (`3/3`), çünkü okuma boyutu her seferinde mesajın kendisinden geldi.
- `io.ReadFull` kullanımı, `io.Reader` sözleşmesinin istenen bayt sayısından azını
  döndürmeye izin verdiği durumlar için bir güvenceydi — çerçeveleme kuralının doğru
  çalışması, onu okuyan kodun bu ayrıntıyı gözetmesine de bağlıydı.
- Sınırlayıcı ölçüm: çerçeveleme doğru gövdeyi verdi ama gövdenin tipini vermedi — `"42"`
  dizgisi doğru ayrıştı, ama bunun bir tamsayı mı bir dizgi mi olduğu çerçevelemenin dışında
  kalan, ayrı bir tarif sorusu.
- Çerçeveleme ve tarif, uzun süreli bir bağlantıda birbirini tamamlayan iki ayrı katman:
  biri mesajın sınırını, öbürü sınırın içindekinin anlamını taşıyor.

#### Sonraki Adım

Bu ders bir akışın neresinin bir mesaj olduğunu belirleyen tek bilgiyi — çerçevelemeyi —
ölçtü ve bu bilginin bir mesajın **tipini** hiç taşımadığını gösterdi: aynı çerçeve içinde
ne olduğu yine bir tarif gerektiriyor. Kursun bu konusu (Ağ ve Veri Biçimleri) altı dersi
boyunca sınırı hep ağ üzerinden — bir istek, bir yanıt, bir şablon çıktısı, iki sürecin
paylaştığı bir tarif, uzun süreli bir bağlantı — geçen değerleri ölçtü. Sıradaki konu aynı
soruyu programın kendi sınırlarında soruyor: dosya, arabellek ve gömülü bir kaynak da,
tıpkı bu dersteki bağlantı gibi, bayt düzeyinde konuşuyor — ve orada da tip bilgisinin
sınırın hangi tarafında durduğu, hangi tarafta durmadığı yeniden ölçülecek.

## Sistem ve Veri Erişimi

Okuyucu ve yazıcı soyutlamalarının bayt düzeyinde çalışması, varlıkların ikili dosyaya gömülmesiyle kaynağın derleme zamanında bağlanması, süre ve saat dilimi işlemlerinin metne dönüşürken kaybettiği, anahtar–değer temelli günlüğün alan adlarını taşıması, sürücü arayüzünün dar tip kümesi ve bağlantı havuzunun yaşam döngüsü, bayrak ayrıştırmanın dizgiden tip geri kurması.

### Dosya ve Girdi/Çıktı Arayüzleri

Source: https://academia.sh/tr/kurslar/go-uygulamalar/dosya-ve-girdi-cikti-arayuzleri

Dört ayrı kaynak (dosya, arabellek, dizgi ve daha önce ölçülen ağ gövdesi) aynı iki yöntemden — okuma ve yazma — geçirilir ve hiçbirinin sınırı aşarken tip bilgisi taşımadığı gösterilir. Sınırlayıcı ölçüm, küçük bir arabellekle okumanın veriyi ve dosya sonunu ayrı adımlarda döndürdüğünü ve bu ayrımı yönetmenin çağıranın işi olduğunu koşturarak ortaya koyuyor.

Ağ ve Veri Biçimleri konusunun son dersi bir soruyla kapandı: bir çerçeveleme kuralı
mesajın nerede başlayıp nerede bittiğini veriyor, ama o sınırın içindeki baytların **ne**
olduğunu vermiyor. Bu konu aynı soruyu programın dışına, ağa taşımadan soruyor. Bir
dosyadan okunan bayt, bir arabellekte biriken bayt ve az önce ağdan gelen bayt — üçü de
aynı sorunun cevabını arıyor: karşı taraftan gelen bir bayt dizisi, hangi tipe ait olduğunu
kendi başına söylüyor mu?

Yanıt hayır ve bu konunun ilk dersi bu "hayır"ın nereden geldiğini gösteriyor: Go'nun standart
kitaplığı dosyayı, arabelleği, ağ gövdesini ve dizgiyi tek bir ortak sözleşmeyle konuşturuyor —
okuma ve yazma. Bu sözleşme o kadar dar ki, kaynağın kendisi hakkında hiçbir şey söylemiyor;
yalnızca bayt akıtıyor. Ağ ve Veri Biçimleri konusunda HTTP gövdesinin de bu sözleşmeye uyduğu
ölçülmüştü; burada tekrar kurulmuyor ve bu ders üç **yeni** kaynağı — dosya, arabellek, dizgi —
aynı sözleşmeden geçirip aynı sonucu (tip bilgisinin taşınmadığını) ayrı ayrı gösteriyor.

**Belirlenimcilik notu:** dosya kaynağı `os.MkdirTemp` ile kurulan geçici bir dizinde
duruyor; dizinin kendi yolu hiçbir yerde basılmıyor, yalnızca içeriğiyle ilgili sayılar
kullanılıyor.

#### Okuma ve Yazma İki Yöntemdir

Go'nun standart kitaplığında bir kaynağın "okunabilir" sayılması için tek bir koşul var:
`Read(p []byte) (n int, err error)` imzasına sahip bir yöntem taşımak. Arayüzlerin kendisi —
örtük gerçekleştirim modeli, bir tipin "bu arayüzü karşılıyorum" diye ayrıca bir bildirim
yapmadan sadece doğru yöntemleri taşıyarak arayüzü karşılaması — Arayüzler, Hatalar ve
Genellikler kursunda kurulmuştu ve burada tekrar kurulmuyor. Bu ders o modelin standart
kitaplıktaki en yaygın örneğini ölçüyor: `io.Reader` arayüzü tek bir `Read` yöntemi istiyor
ve dosya, ağ, sıkıştırma, şifreleme ve metin işleme paketlerinin **hepsi** bu tek arayüzün
üstüne kuruluyor. Yazma tarafında da simetrik bir arayüz var: `io.Writer`, tek yöntemi
`Write(p []byte) (n int, err error)`.

Bu iki arayüzün küçüklüğü bir eksiklik değil, tam tersi bir tasarım kararı. Bir kaynağın
"okunabilir" olması için taşıması gereken tek şey bu iki dönüş değeri olduğundan, `os.File`,
`bytes.Buffer`, `strings.Reader` ve bir ağ gövdesi gibi birbiriyle hiçbir ortak soyu paylaşmayan
tipler aynı arayüzü **bağımsız olarak** gerçekleştirebiliyor. Hiçbiri ötekini taklit etmek
zorunda değil; her biri kendi `Read` yöntemini kendi iç verisine göre yazıyor ve dışarıdan
bakan kod hangi tipin elinde olduğunu hiç bilmeden aynı döngüyü çalıştırabiliyor. Örtük
gerçekleştirim burada özellikle görünür oluyor: `os.File` tipinin tanımında `io.Reader`
sözcüğü hiç geçmiyor, ama `Read` yöntemini doğru imzayla taşıdığı için arayüzü karşılıyor —
bağlantı, bir bildirimle değil, imza eşleşmesiyle kuruluyor.

#### Dört Kaynak, Aynı Arayüz

**SS1.** — Aşağıdaki üç kaynağa (dosya, arabellek, dizgi) bilerek aynı dört karakterlik metin
(`"vida"`) yerleştirildi; amaç kaynakların **içeriğini** değil, aracın **davranışını**
karşılaştırmak. Dördüncü kaynak — ağ gövdesi — Ağ ve Veri Biçimleri konusunda aynı sonuçla
(kayboluyor) zaten koşturuldu ve burada tekrar kurulmuyor.

```go
// oku.go — uc ayri kaynagi ayni okuyucu arayuzunden geciren yardimci
package main

import (
	"bytes"
	"fmt"
	"io"
	"os"
	"strings"
)

func oku(r io.Reader) (int, string) {
	b, _ := io.ReadAll(r)
	return len(b), fmt.Sprintf("%T", b)
}

type kaynak struct {
	ad string
	r  io.Reader
}

func main() {
	dizin, _ := os.MkdirTemp("", "ss1")
	defer os.RemoveAll(dizin)
	os.WriteFile(dizin+"/icerik.txt", []byte("vida"), 0o644)
	dosya, _ := os.Open(dizin + "/icerik.txt")
	defer dosya.Close()

	var arabellek bytes.Buffer
	arabellek.WriteString("vida")

	dizgi := strings.NewReader("vida")

	kaynaklar := []kaynak{
		{"dosya", dosya},
		{"arabellek", &arabellek},
		{"dizgi", dizgi},
	}
	for _, k := range kaynaklar {
		uzunluk, tip := oku(k.r)
		fmt.Printf("%-10s uzunluk=%d tip=%s\n", k.ad, uzunluk, tip)
	}
}
```

```
dosya      uzunluk=4 tip=[]uint8
arabellek  uzunluk=4 tip=[]uint8
dizgi      uzunluk=4 tip=[]uint8
```

Üç kaynak, birbirinden tamamen ayrı gerçekleştirmelere sahip: `*os.File` işletim sistemi
çağrılarıyla konuşuyor, `*bytes.Buffer` bellekte büyüyen bir dilimi yönetiyor, `*strings.Reader`
değiştirilemez bir dizginin üzerinde ilerliyor. `oku` işlevi bu farkı hiç görmüyor — parametresi
`io.Reader`, yani yalnızca `Read` yöntemini bekliyor. Üçünün de sonucu birebir aynı: dört
bayt ve tipi her zaman `[]uint8` (Go'da `byte`, `uint8`'in bir takma adı, `%T` bunu kendi
adıyla gösteriyor). Kaynağın orijinal tipi — dosya mı, arabellek mi, dizgi mi — bu noktadan
sonra hiçbir yerde durmuyor; okuma işleminin ürettiği tek şey ham bir bayt dilimi.

Bu, konunun ölçü ekseninin ilk somut örneği: sınırı geçen değer (`Read`'in
döndürdüğü veri) **kayboluyor** sınıfına giriyor, çünkü karşı tarafta onu geri kim gönderdiyse
göndersin sonuç aynı ham tipte kalıyor. Bir dosyadan okunan dört bayt ile bir dizgiden okunan
dört bayt, okuma işleminden sonra ayırt edilemez.

Örtük gerçekleştirimin gücü, yalnızca standart kitaplığın kendi tiplerinde durmuyor. `Read`
imzasını doğru yazan **herhangi bir tip**, `io.Reader` arayüzünü karşılıyor — dosya sistemine
ya da belleğe hiç dokunmayan, elle yazılmış bir tip bile.

```go
// elle-yazilan.go — Read yontemini kendi yazan bir tip de io.Reader arayuzunu karsilar
package main

import (
	"fmt"
	"io"
)

// TekrarOkuyucu, bir bayti istenen sayida tekrarlayan, elle yazilmis bir okuyucu.
type TekrarOkuyucu struct {
	Bayt  byte
	Kalan int
}

func (t *TekrarOkuyucu) Read(p []byte) (int, error) {
	if t.Kalan == 0 {
		return 0, io.EOF
	}
	n := 0
	for n < len(p) && t.Kalan > 0 {
		p[n] = t.Bayt
		t.Kalan--
		n++
	}
	return n, nil
}

func main() {
	var kaynak io.Reader = &TekrarOkuyucu{Bayt: 'x', Kalan: 5}
	veri, _ := io.ReadAll(kaynak)
	fmt.Printf("kendi yazdigimiz okuyucudan gelen: %q tip=%T\n", veri, veri)
}
```

```
kendi yazdigimiz okuyucudan gelen: "xxxxx" tip=[]uint8
```

`TekrarOkuyucu` ne bir dosya açıyor ne bir dizgiyi sarmalıyor; kendi `Kalan` sayacına göre bayt
üretiyor. Yine de `io.ReadAll` onu öteki üç kaynaktan hiç farklı görmüyor, çünkü tek aradığı şey
`Read` yönteminin doğru imzayla var olması. Bu, dört kaynağın ortak noktasının aslında beşinci,
altıncı, herhangi bir sayıda yeni kaynağa da açık olduğunu gösteriyor — arayüz sabit kalıyor;
gerçekleştirenlerin sayısı sınırsız.

#### Yazma Tarafı: io.Writer

Yazma yönü de aynı simetriye sahip. `io.Writer`i gerçekleştiren bir hedefe veri yazıldığında,
hedefin dosya mı arabellek mi olduğu yazan tarafın umurunda değil; önemli olan yalnızca kaç
bayt yazıldığı.

**SS2.** — Aynı beş baytlık veri (`"somun"`) hem bir dosyaya hem bir arabelleğe yazılıyor;
ardından ikisi de geri okunup birbirine ve kaynağa eşit olup olmadığı sınanıyor.

```go
// yaz.go — ayni veri iki farkli yazici hedefine yaziliyor
package main

import (
	"bytes"
	"fmt"
	"io"
	"os"
)

func yaz(w io.Writer, veri []byte) (int, string) {
	n, _ := w.Write(veri)
	return n, fmt.Sprintf("%T", w)
}

func main() {
	dizin, _ := os.MkdirTemp("", "ss2")
	defer os.RemoveAll(dizin)
	dosya, _ := os.Create(dizin + "/cikti.txt")

	var arabellek bytes.Buffer

	n1, t1 := yaz(dosya, []byte("somun"))
	dosya.Close()
	n2, t2 := yaz(&arabellek, []byte("somun"))

	fmt.Printf("dosyaya yazilan    n=%d tip=%s\n", n1, t1)
	fmt.Printf("arabellege yazilan n=%d tip=%s\n", n2, t2)

	geriOkunan, _ := os.ReadFile(dizin + "/cikti.txt")
	fmt.Println("dosyadan geri okunan     :", string(geriOkunan))
	fmt.Println("arabellekten geri okunan :", arabellek.String())
	fmt.Println("ikisi ayni mi            :", string(geriOkunan) == arabellek.String())
}
```

```
dosyaya yazilan    n=5 tip=*os.File
arabellege yazilan n=5 tip=*bytes.Buffer
dosyadan geri okunan     : somun
arabellekten geri okunan : somun
ikisi ayni mi            : true
```

`yaz` işlevinin imzası `oku`ya simetrik: parametresi somut bir hedef değil, `io.Writer`
arayüzü. `*os.File` ve `*bytes.Buffer` birbirine hiç benzemeyen iki tip olsa da ikisi de
`Write` yöntemini taşıdığı için aynı işlevden geçebiliyor. Sonuç da simetrik: ikisine de
aynı beş bayt yazılıyor, ikisinden de geri okunan içerik birbirine eşit çıkıyor. Bu satırın
kendisi konunun sorusuna bir yanıt daha ekliyor — yazma yönünde de sınırı geçen veri, hedefin
kendi tipine bakılmaksızın aynı ham bayt dizisi olarak taşınıyor; hedefin `*os.File` ya da
`*bytes.Buffer` olması yazılan içeriği hiç etkilemiyor.

#### Bileşim: io.Copy ve bufio Aynı Arayüzün Üstüne Kurulu

Arayüzlerin dar olmasının pratik sonucu, standart kitaplığın kaynak ile hedef arasında
**genel** araçlar yazabilmesi. `io.Copy(dst io.Writer, src io.Reader)` işlevi, parametrelerinin
somut tipini hiç bilmeden bir kaynaktan bir hedefe veri taşıyor — kaynak dosya, arabellek,
dizgi ya da başka herhangi bir `io.Reader` olabilir; hedef de aynı şekilde herhangi bir
`io.Writer`. Aynı fikir `bufio` paketinde de geçerli: `bufio.NewScanner` bir `io.Reader` alıp
onu satır satır (ya da kelime kelime) okuyan bir sarmalayıcıya çeviriyor, sardığı kaynağın
dosya mı dizgi mi olduğunu hiç sormadan.

```go
// birlestir.go — io.Copy ve bufio, somut tipi bilmeden herhangi bir okuyucu/yazici uzerinde calisir
package main

import (
	"bufio"
	"bytes"
	"fmt"
	"io"
	"os"
	"strings"
)

func main() {
	dizin, _ := os.MkdirTemp("", "ss4")
	defer os.RemoveAll(dizin)
	os.WriteFile(dizin+"/kaynak.txt", []byte("civi ve vida"), 0o644)
	dosya, _ := os.Open(dizin + "/kaynak.txt")
	defer dosya.Close()

	var hedef bytes.Buffer
	n, _ := io.Copy(&hedef, dosya)
	fmt.Println("dosyadan arabellege tasinan bayt:", n)
	fmt.Println("arabellegin icerigi              :", hedef.String())

	satirOkuyucu := bufio.NewScanner(strings.NewReader("ilk satir\nikinci satir"))
	satirSayisi := 0
	for satirOkuyucu.Scan() {
		satirSayisi++
	}
	fmt.Println("bufio ile sayilan satir sayisi:", satirSayisi)
}
```

```
dosyadan arabellege tasinan bayt: 12
arabellegin icerigi              : civi ve vida
bufio ile sayilan satir sayisi: 2
```

`io.Copy` çağrısının imzasında dosyanın adı, arabelleğin türü, hiçbir somut tip geçmiyor —
yalnızca iki arayüz. Bu işlev standart kitaplığın içinde **bir kez** yazılmış ve dosyadan
ağa, ağdan arabelleğe, arabellekten başka bir dosyaya her kombinasyonda yeniden kullanılabilir
durumda; kaynak ile hedefin somut tipi kaç tane olursa olsun `io.Copy`nin kendisi hiç
değişmiyor. `bufio.NewScanner` de aynı ilkeyi izliyor: girdisi bir `io.Reader` ve kendisi de
bir `io.Reader` sarmalıyor olmasına rağmen daha zengin bir arayüz (satır satır tarama)
sunuyor — küçük arayüzlerin üst üste **bileşebilmesi**, tek bir büyük "dosya okuyucu"
tipi yazmak yerine bu tür küçük, birbirine takılabilen parçalar üretmenin doğrudan sonucu.

#### Sınırlayıcı Ölçüm: Kısmi Okuma ve Dosya Sonu

Buraya kadarki iki ölçüm `io.ReadAll` kullandı — kaynak tükenene kadar döngüyü kendi içinde
yürüten bir yardımcı işlev. Ama `Read` yönteminin kendisi bunu garanti etmiyor: tek bir
çağrı, istenen baytın **tamamını** döndürmek zorunda değil ve verinin bitmesi ile hatanın
gelmesi **aynı çağrıda** olmak zorunda değil. Bu ayrım standart kitaplığın belgesinde açıkça
yazılı, ama koşturmadan görülmesi zor.

**SS3.** — Beş baytlık bir dizgi, kasıtlı olarak iki baytlık bir arabellekle okunuyor; her
adımda kaç bayt geldiği ve hatanın hangi adımda ortaya çıktığı ayrı ayrı basılıyor.

```go
// kismi.go — kucuk arabellekle okuma, veri ve dosya sonu ayri adimlarda geliyor
package main

import (
	"fmt"
	"strings"
)

func main() {
	kaynak := strings.NewReader("govde")
	arabellek := make([]byte, 2)
	toplam := 0
	adim := 0
	for {
		n, hata := kaynak.Read(arabellek)
		adim++
		fmt.Printf("adim=%d okunan=%d parca=%q hata=%v\n", adim, n, arabellek[:n], hata)
		toplam += n
		if hata != nil {
			break
		}
	}
	fmt.Println("toplam okunan bayt:", toplam)
}
```

```
adim=1 okunan=2 parca="go" hata=<nil>
adim=2 okunan=2 parca="vd" hata=<nil>
adim=3 okunan=1 parca="e" hata=<nil>
adim=4 okunan=0 parca="" hata=EOF
toplam okunan bayt: 5
```

Beş baytlık `"govde"` dizgisi, iki baytlık bir arabellekle okunduğunda dört ayrı `Read`
çağrısı gerektiriyor — üçüncü adımda tek bayt kalıyor ve o adımda hata hâlâ `nil`. Dosya
sonu sinyali (`io.EOF`) yalnızca **dördüncü** çağrıda, hiç veri gelmeden geliyor. Bu, kaynağın
davranışının bir sözleşmesi: `Read` bir seferde `0`'dan büyük bir bayt sayısı **ve** `nil`
olmayan bir hata aynı anda döndürebilir, ya da veriyi ve hatayı ayrı çağrılara bölebilir.
Yalnızca `io.ReadAll` gibi bir yardımcıya güvenen kod bu ayrımı hiç görmüyor, çünkü yardımcı
döngüyü kendi içinde tamamlıyor. Ama `Read`i doğrudan çağıran kod, verinin bitmesi ile hatanın
gelmesini **aynı adımda** varsayarsa üçüncü adımdaki son baytı atlayabilir.

Bu, konunun ölçü ekseninin bu derse özgü sınırı: okuma arayüzü **kayıp** taşımıyor — beş
baytın hepsi doğru sırayla geldi — ama kısmi okuma ile dosya sonunu **ayırma işini** hiç
üstlenmiyor. O ayrım, `Read`i çağıran kodun sorumluluğunda kalıyor; standart kitaplığın
`io.ReadFull` ve `io.ReadAll` gibi yardımcıları bu sorumluluğu üstlenen, ama kaynağın kendisi
tarafından zorunlu kılınmayan araçlar.

`strings.NewReader` gibi bellek içi bir kaynak için bu ayrımı elle yönetmek gereksiz görünebilir
— zaten tüm veri bellekte hazır, `Read`in tek seferde her şeyi vermemesi bir tercih meselesi.
Ama arayüzün sözleşmesi tüm gerçekleştirenler için aynı; bir ağ soketinden ya da bir borudan
okuyan bir `io.Reader`de kısmi okuma bir tercih değil, kaynağın doğasından geliyor — karşı
taraf veriyi parça parça gönderiyorsa, `Read`in tek çağrıda her şeyi döndürmesi zaten mümkün
değil. `io.Reader`in sözleşmesi bu iki durumu (bellek içi kaynak ile parça parça gelen kaynak)
ayırmıyor; ikisi de aynı imzayı taşıyor ve kısmi okumayı doğru yöneten kod ikisinde de
çalışıyor.

#### Özet

- `io.Reader` ve `io.Writer`, dilin en dar arayüzlerinden ikisi; sırasıyla tek bir `Read` ve
  tek bir `Write` yöntemi istiyorlar ve bu yüzden dosya, arabellek ve dizgi gibi birbirine
  hiç benzemeyen tipler aynı sözleşmeyi bağımsız olarak gerçekleştirebiliyor.
- Dosyadan, arabellekten ve dizgiden okunan aynı içerik, okuma işleminden sonra ayırt
  edilemez hâle geliyor: üçü de aynı uzunlukta, aynı `[]uint8` tipinde bir sonuç veriyor —
  sınırı geçen veri tip bilgisini taşımıyor.
- Aynı simetri yazma yönünde de geçerli: `*os.File` ve `*bytes.Buffer` aynı `yaz` işlevinden
  geçiyor ve ikisine yazılan içerik birbirine birebir eşit çıkıyor.
- `io.Copy` ve `bufio.NewScanner` gibi araçlar somut tipi hiç bilmeden çalışıyor; küçük
  arayüzler bu yüzden dosya, arabellek ve dizgi arasında serbestçe **bileşebiliyor**.
- Kısmi okuma ile dosya sonu ayrı olaylar: küçük bir arabellekle okuma, veriyi birkaç adıma
  bölebiliyor ve dosya sonu sinyali veriden bağımsız, kendi adımında geliyor.
- Bu ayrımı yönetmek çağıranın işi; `io.ReadAll` gibi yardımcılar bu sorumluluğu üstleniyor,
  ama arayüzün kendisi bunu zorunlu kılmıyor.

#### Sonraki Adım

Bu derste ölçülen üç kaynağın hepsi, çalışırken erişilebilir bir yerde duruyordu: bir dosya
sistemi yolunda, ya da programın kendi belleğinde. Ama bir programın ihtiyaç duyduğu veri
her zaman koşum sırasında bulunabilir olmayabilir — bir yapılandırma dosyası, bir HTML
şablonu, bir statik varlık, programın **kendi ikili dosyasına** taşınabilir. Sıradaki ders bu
bağı derleme zamanına taşıyan düzeneğe bakıyor: bir kaynak dosya, koşum sırasında hiç
aranmadan, doğrudan derlenmiş programın içinde nasıl duruyor?

### Gömülü Kaynaklar

Source: https://academia.sh/tr/kurslar/go-uygulamalar/gomulu-kaynaklar

Bir kaynak dosyanın go:embed yönergesiyle üç ayrı Go tipine (dizgi, bayt dilimi, dosya sistemi) aynı içerikle bağlandığı ve bu içeriğin diskte duran dosyayla birebir eşleştiği ölçülür. Sınırlayıcı ölçüm, bir kez derlenmiş ikilinin kaynak dosya diskten silindikten sonra bile aynı içeriği ürettiğini koşturarak gösteriyor: bağ derleme zamanında kuruluyor, koşum zamanında dosyaya hiç ihtiyaç kalmıyor.

Önceki ders üç kaynağı (dosya, arabellek, dizgi) aynı okuyucu arayüzünden geçirdi ve hepsinin
aynı sonucu — ham bir bayt dilimi — verdiğini gösterdi. O üç kaynağın ortak bir özelliği vardı:
programın kendisi hâlâ çalışırken erişilebilir kalıyorlardı — dosya diskte duruyordu, arabellek
bellekte. Bu ders farklı bir soru soruyor: bir programın ihtiyaç duyduğu bir veri, koşum
sırasında hiç erişilebilir olmasa bile programın elinde bulunabilir mi? Yanıt evet ve yöntem
bir okuma işlemi değil, bir **bağlama** işlemi: kaynak dosyanın içeriğini, derleme sırasında
doğrudan ikili dosyanın içine yerleştirmek.

**Terim notu:** bu derste geçen "gömme" sözcüğü, Bileşik Tipler konusunda ölçülen **yapı
gömme**yle (bir tipin başka bir tipin alanları arasına adsız olarak yerleştirilmesi) aynı
kelimeyi kullanıyor ama ayrı bir düzenek. Yapı gömme kaynak kodun **içinde**, iki Go tipi
arasında kurulan bir ilişkiydi; bu derste ölçülen **gömülü kaynak** ise bir dosyanın içeriğini
kaynak kodun **dışından**, derleyicinin kendisi aracılığıyla bir değişkene taşıyor. İkisinin
ortak yanı yalnızca isim; biri iki Go tipini birbirine, öteki bir dosyayı bir Go değerine
bağlıyor.

**Belirlenimcilik notu:** bu derste hiçbir dosya yolu basılmıyor; ölçülen tek şey içeriğin
uzunluğu, tipi ve iki temsilin birbirine eşit olup olmadığı.

Bu düzeneğin tipik kullanım yerleri, koşum sırasında değişmesi beklenmeyen ama programın
kendisiyle birlikte taşınması gereken veriler: bir yapılandırma dosyasının varsayılan
değerleri, bir komut satırı aracının yardım metni, bir web sunucusunun statik varlıkları.
Hepsinin ortak noktası, içeriğin **programın bir parçası** olması — ayrı bir kurulum adımıyla
yanına kopyalanması gereken bir veri değil.

#### Kaynak Dosya

Gömme yönergesi (`//go:embed`) bir `var` bildiriminin hemen üstüne yazılan özel bir yorum;
derleyici bunu okuyup, belirtilen dosyanın içeriğini o değişkene derleme sırasında yerleştiriyor.
Yönergenin çalışabilmesi için önce gömülecek dosyanın var olması gerekiyor — aşağıdaki kabuk
bloğu, dersin geri kalanında gömülecek `tarif.txt` dosyasını oluşturuyor ve içeriğini
doğrulamak için geri okuyor.

```bash
# tarif-dosyasi.sh — go:embed ile gomulecek kaynak dosyayi olusturur
cat > tarif.txt <<'EOF'
kaynak paketin icine kod ile birlikte tasinir
EOF
cat tarif.txt
```

```
kaynak paketin icine kod ile birlikte tasinir
```

#### Aynı Dosya, Üç Tip

**SS4.** — Aynı `tarif.txt` dosyası üç ayrı değişkene gömülüyor: bir dizgiye, bir bayt
dilimine, bir de dosya sistemi türüne (`embed.FS`). Amaç, yönergenin hedef tipe göre farklı
davrandığını ve üçünün de aynı içeriği taşıdığını göstermek.

```go
// gomulu.go — ayni dosya string, []byte ve embed.FS olarak gomuluyor, icerikleri karsilastiriliyor
package main

import (
	"embed"
	"fmt"
	"os"
)

//go:embed tarif.txt
var tarifMetni string

//go:embed tarif.txt
var tarifBaytlari []byte

//go:embed tarif.txt
var tarifSistemi embed.FS

func main() {
	sistemdenOkunan, _ := tarifSistemi.ReadFile("tarif.txt")
	diskten, hataDosya := os.ReadFile("tarif.txt")

	fmt.Printf("tarifMetni tipi=%T uzunluk=%d\n", tarifMetni, len(tarifMetni))
	fmt.Printf("tarifBaytlari tipi=%T uzunluk=%d\n", tarifBaytlari, len(tarifBaytlari))
	fmt.Println("metin == bayt donusumu:", tarifMetni == string(tarifBaytlari))
	fmt.Println("metin == embed.FS okumasi:", tarifMetni == string(sistemdenOkunan))
	fmt.Println("metin == dosya sisteminden okunan:", tarifMetni == string(diskten), hataDosya == nil)
}
```

```
tarifMetni tipi=string uzunluk=46
tarifBaytlari tipi=[]uint8 uzunluk=46
metin == bayt donusumu: true
metin == embed.FS okumasi: true
metin == dosya sisteminden okunan: true true
```

Üç `//go:embed` yönergesi birbirinden bağımsız; her biri aynı `tarif.txt`yi okuyor ama hedef
değişkenin tipine göre farklı bir temsil üretiyor. `tarifMetni` bir dizgi, `tarifBaytlari` bir
bayt dilimi, `tarifSistemi` ise `Open` ve `ReadFile` gibi yöntemleri olan küçük bir dosya
sistemi soyutlaması. Üçü de aynı kaynaktan geldiği için içerikleri birebir eşleşiyor — bayt
dönüşümüyle karşılaştırıldığında da, `embed.FS` üzerinden okunduğunda da, dosyanın kendisinden
`os.ReadFile` ile canlı okunduğunda da aynı 46 bayt geliyor. Bu son karşılaştırma önceki
dersle bir köprü kuruyor: dosya hâlâ diskteyken, gömülen içerik ile diskteki içerik ayırt
edilemiyor — sanki dosya iki kez okunmuş gibi, ama biri derleme sırasında, biri koşum
sırasında gerçekleşiyor.

Aynı dosyanın üç ayrı temsile bölünmesi rastgele değil; her temsil farklı bir kullanım
biçimine uyuyor. Metni doğrudan bir dizgi olarak kullanacak kod (bir sabit metin, bir şablon
kaynağı) `tarifMetni`yi tercih ediyor. İçeriği başka bir ikili biçime (bir sıkıştırma
algoritmasına, bir ağ gövdesine) aktaracak kod `tarifBaytlari`yı tercih ediyor, çünkü `[]byte`
o tür arayüzlerin beklediği tip. Birden fazla dosyayı ya da bir dizin yapısını taşımak
gereken kod ise `embed.FS`e ihtiyaç duyuyor — bu ihtiyaç bir sonraki bölümde ölçülüyor.

#### Sınırlayıcı Ölçüm: Kaynak Dosya Koşumda Hiç Aranmaz

Buraya kadarki ölçüm `tarif.txt` diskteyken çalıştı, bu yüzden "gömülü içerik dosyayla aynı"
iddiası ile "program dosyaya bağımlı" iddiası birbirinden ayrılamıyordu. Aşağıdaki adım bu
ikisini ayırıyor: aynı kaynak önce **derlenip** bağımsız bir ikili hâline getiriliyor, sonra
kaynak dosya diskten **siliniyor**, sonra da yalnızca o ikili çalıştırılıyor.

**SS5.** — Derleme adımı `go build` ile ayrı bir ikili üretiyor; `go run` kullanılmıyor,
çünkü `go run` her çağrıda yeniden derliyor ve kaynak dosya o anda hâlâ diskte olurdu. Burada
amaç, derleme ile çalıştırmayı **birbirinden ayırmak**.

```bash
# derle-ve-sil.sh — once derlenir, sonra kaynak dosya silinir, sonra yalniz ikili calistirilir
go build -o govde_programi .
rm tarif.txt
./govde_programi
```

```
tarifMetni tipi=string uzunluk=46
tarifBaytlari tipi=[]uint8 uzunluk=46
metin == bayt donusumu: true
metin == embed.FS okumasi: true
metin == dosya sisteminden okunan: false false
```

Çıktının ilk dört satırı bir önceki koşumla birebir aynı: `tarifMetni`, `tarifBaytlari` ve
`tarifSistemi` hâlâ 46 baytlık içeriği doğru biçimde veriyor — `tarif.txt` artık diskte
olmamasına rağmen. Değişen tek satır sonuncusu: `os.ReadFile("tarif.txt")` artık dosyayı
bulamıyor, bu yüzden hem karşılaştırma hem hata denetimi `false`a dönüyor. Bu fark, gömme
yönergesi ile canlı dosya okuması arasındaki gerçek ayrımı gösteriyor — ilk üç değişken
derleme sırasında içeriği **kendi içine aldı** ve o andan sonra kaynağı bir daha hiç
okumadı; dördüncü satır ise her çalıştırmada dosyayı **yeniden** arıyor.

Bu, konunun ölçü ekseninde şöyle okunuyor: kaynak dosyadan gömülen değişkene giden bağ,
programın koşum ânında **kurulmuyor** — derleme ânında zaten kurulmuş durumda. Bir dosyayı
okumaktan (önceki dersin konusu) farkı da tam burada: okuma her çağrıda kaynağa yeniden
gidiyor, gömme ise kaynağa yalnızca **bir kez**, derleme sırasında gidiyor ve sonucu ikilinin
içine taşıyor. Bu, bir kısıt olduğu kadar bir güvence de: dağıtılan ikili, yanına ayrı bir
veri dosyası taşımak zorunda kalmıyor ve o dosyanın koşum ânında yanlışlıkla değiştirilmesi
ya da silinmesi programın davranışını hiç etkilemiyor.

Bunun bedeli de var ve bedel tam olarak güvencenin ters yüzü: içerik güncellendiğinde, ikili
de yeniden derlenmek zorunda. Önceki dersteki dosya okuma, koşum sırasında değişen bir kaynağı
hemen görebiliyordu — dosyayı her seferinde yeniden okumak yeterliydi. Gömülü bir kaynak için
bu yol kapalı; içeriği güncellemenin tek yolu kaynak dosyayı değiştirip programı **yeniden
derlemek**. Hangi yaklaşımın doğru olduğu içeriğin kendi doğasına bağlı: sık değişen bir veri
için okuma, hiç değişmemesi gereken bir veri için gömme daha uygun bir seçim.

#### Birden Çok Dosya: Örüntü ile Gömme

Yönergenin hedefi tek bir dosya adı değil, bir **örüntü** de olabiliyor. `string` ve `[]byte`
yalnızca tek bir dosyanın içeriğini taşıyabildiğinden, birden çok dosyayı gömmenin tek yolu
`embed.FS`; sonuç, ikilinin içine gömülmüş küçük bir dizin ağacı oluyor.

**SS6.** — İki dosyalık küçük bir dizin (`kaynaklar/`) örüntüyle (`kaynaklar/*.txt`) tek bir
`embed.FS` değişkenine gömülüyor; ölçüm, gömülen dosya sayısını ve her birinin uzunluğunu
okuyor.

```bash
# kaynaklar-dizini.sh — orunu ile gomulecek iki dosyalik bir dizin olusturur
mkdir kaynaklar
printf 'birinci dosyanin icerigi\n' > kaynaklar/birinci.txt
printf 'ikinci dosyanin icerigi, biraz daha uzun\n' > kaynaklar/ikinci.txt
ls kaynaklar
```

```
birinci.txt
ikinci.txt
```

```go
// dizin.go — orunu ile gomulen bir dizinin icerigi listeleniyor ve okunuyor
package main

import (
	"embed"
	"fmt"
	"sort"
)

//go:embed kaynaklar/*.txt
var kaynakDizini embed.FS

func main() {
	girdiler, _ := kaynakDizini.ReadDir("kaynaklar")
	adlar := make([]string, 0, len(girdiler))
	for _, g := range girdiler {
		adlar = append(adlar, g.Name())
	}
	sort.Strings(adlar)
	fmt.Println("gomulu dosya sayisi:", len(adlar))
	for _, ad := range adlar {
		icerik, _ := kaynakDizini.ReadFile("kaynaklar/" + ad)
		fmt.Printf("%s uzunluk=%d\n", ad, len(icerik))
	}
}
```

```
gomulu dosya sayisi: 2
birinci.txt uzunluk=25
ikinci.txt uzunluk=41
```

`kaynakDizini.ReadDir("kaynaklar")` çağrısı, sanki gerçek bir dizini listeliyormuş gibi
davranıyor — dosya adlarını, dosya olup olmadıklarını, boyutlarını veriyor — ama listelediği
şey diskte değil, ikilinin içinde. Bu, `embed.FS` tipinin standart kitaplığın dosya sistemi
soyutlamasını (`io/fs` paketindeki `fs.FS` arayüzü) karşıladığı için mümkün; bir dosya sistemi
gibi davranan her araç, kaynağın gerçek disk mi yoksa ikilinin içine gömülmüş bir kopya mı
olduğunu bilmeden çalışabiliyor. Bu arayüz ayrımı Ağ ve Veri Biçimleri konusunda kurulan
sunucu tarafı ayrıntılarla burada tekrar kurulmuyor; ölçülen tek şey, gömülü dizinin gerçek
bir dizin gibi listelenip okunabilmesi.

Bu, önceki dersteki `io.Reader`/`io.Writer` bileşiminin bir başka örneği: `fs.FS` da tek
birkaç yöntemlik dar bir arayüz ve onu bekleyen bir işlev `os.DirFS("bir/gercek/yol")`
ile de, `embed.FS` ile de, testler için elle yazılmış bir sahte dosya sistemiyle de
çalışabiliyor. `kaynakDizini` değişkeninin somut tipi `embed.FS`, ama onu `fs.FS` bekleyen
bir işleve verdiğinizde o işlev içeriğin gerçek diskte mi yoksa ikilinin içinde mi
durduğunu hiç bilmiyor — tıpkı bir önceki dersteki `oku` işlevinin kaynağın dosya mı
arabellek mi olduğunu bilmemesi gibi.

#### Derleme Zamanı Sınırları

Gömme yönergesinin çalışabilmesi için hedef dosyanın derleme **sırasında**, paketin kendi
dizininde bulunması gerekiyor. Bu iki kısıtı ayrı ayrı sınamak, Go'nun Tasarım Amaçları dersinde
kurulup kurs boyunca kullanılan `derlenirMi` biçimindeki yardımcıyla mümkün: kaynağı geçici bir
dizine yazıp derlemeyi deneyen, sonucu `derlendi`/`derlenmedi` diye sınıflayan bir işlev.

**SS7.** — Üç kaynak deneniyor: dosyası gerçekten var olan bir gömme, dosyası hiç var olmayan
bir gömme ve paketin kendi dizininin dışına (`../`) çıkmaya çalışan bir gömme. Derleyicinin
hata metni burada da basılmıyor; yalnızca ret sınıfı okunuyor.

```go
// derlenir-mi.go — gomme yonergesinin derleme zamani kisitlari, derlenirMi ile sinaniyor
package main

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

func derlenirMi(kaynak string, digerDosyalar map[string]string) bool {
	dizin, err := os.MkdirTemp("", "sinama")
	if err != nil {
		return false
	}
	defer os.RemoveAll(dizin)
	os.WriteFile(filepath.Join(dizin, "go.mod"), []byte("module sinama\n\ngo 1.24\n"), 0o644)
	os.WriteFile(filepath.Join(dizin, "kod.go"), []byte(kaynak), 0o644)
	for ad, icerik := range digerDosyalar {
		os.MkdirAll(filepath.Dir(filepath.Join(dizin, ad)), 0o755)
		os.WriteFile(filepath.Join(dizin, ad), []byte(icerik), 0o644)
	}
	cmd := exec.Command("go", "build", "./...")
	cmd.Dir = dizin
	cmd.Env = append(os.Environ(), "GOTOOLCHAIN=local")
	return cmd.Run() == nil
}

func kaynakUret(dosyaAdi string) string {
	return fmt.Sprintf(`package main
import (
	_ "embed"
)
//go:embed %s
var veri string
func main() {}
`, dosyaAdi)
}

func main() {
	varOlan := derlenirMi(kaynakUret("var.txt"), map[string]string{"var.txt": "icerik"})
	olmayan := derlenirMi(kaynakUret("yok.txt"), nil)
	paketDisi := derlenirMi(kaynakUret("../disari.txt"), nil)

	fmt.Println("var olan dosya gomuluyor    :", varOlan)
	fmt.Println("olmayan dosya gomuluyor     :", olmayan)
	fmt.Println("paket disi dosya gomuluyor  :", paketDisi)
}
```

```
var olan dosya gomuluyor    : true
olmayan dosya gomuluyor     : false
paket disi dosya gomuluyor  : false
```

Üç denemenin ilki derleniyor, öteki ikisi derlenmiyor — ama iki ret de aynı nedenden değil.
Olmayan dosya, yönergenin en temel koşulunu karşılamıyor: gömülecek içerik hiç yok. Paket
dışı dosya ise içerik var olsa bile reddediliyor, çünkü gömme yönergesi yalnızca **kendi
paketinin dizini ve altındaki** dosyalara erişebiliyor; bir üst dizine çıkan bir yol, dosya
gerçekten orada dursa bile derleme zamanında kabul edilmiyor. Bu, dosya okuma ile gömme
arasındaki bir başka fark: `os.ReadFile` çağıran kodun erişebildiği her yola gidebilirken,
gömme yönergesi paketin kendi sınırlarının dışına hiç çıkamıyor.

Bu sınırın kendisi keyfi değil: bir paket, kendi dizininin dışına referans veren bir gömme
yönergesi taşırsa, o paketi başka bir yerden (başka bir modülden, başka bir makineden) alıp
derleyen kişi kaynağın nerede durduğunu bilemez — üst dizin, paketle birlikte taşınan bir
şey değil. Paketin kendi dizinine hapsedilmiş bir gömme, paketin **kendi kendine yeterli**
olmasını garanti ediyor: paketi kopyalayan biri, gömülü kaynakların tamamını da otomatik
olarak kopyalamış oluyor.

Bir başka kısıt burada koşturulmuyor ama belirtmeye değer: adı nokta ya da alt çizgiyle
başlayan dosyalar (`.gizli`, `_yardimci`) yönergenin varsayılan örüntüsüne dahil olmuyor;
onları da gömmek isteyen bir yönergenin başına `all:` öneki eklenmesi gerekiyor. Bu da aynı
ailenin bir parçası: gömme yönergesi, hangi dosyaların "gerçekten kaynağın parçası" sayılacağı
konusunda kendi varsayılan kurallarını taşıyor.

#### Özet

- `//go:embed` yönergesi bir dosyanın içeriğini derleme sırasında bir Go değişkenine
  yerleştiriyor; hedef tipe göre (`string`, `[]byte`, `embed.FS`) farklı bir temsil üretiyor.
  Sınırdan geçen şey yine bayttır; tipi **hedef tipin kendisi yeniden kuruyor**.
- Aynı dosyaya bağlanan üç değişken birbirine ve diskteki dosyanın canlı okumasına birebir
  eşit içerik veriyor — dosya hâlâ diskteyken.
- Sınırlayıcı ölçüm: derlenmiş bir ikili, kaynak dosya silindikten sonra da gömülü içeriği
  doğru üretiyor — bağ derleme zamanında kuruluyor, koşum zamanında kaynağa bir daha hiç
  ihtiyaç duyulmuyor ve yalnızca canlı bir disk okuması (`os.ReadFile`) başarısız oluyor.
- Bir örüntü, birden çok dosyayı tek bir `embed.FS` değişkenine gömebiliyor; sonuç `fs.FS`
  arayüzünü karşılayan, gerçek bir dizin gibi listelenip okunabilen bir yapı oluyor.
- Gömme yönergesi derleme zamanında iki kısıtla sınırlı: hedef dosya gerçekten var olmalı ve
  paketin kendi dizininin dışına çıkamaz; ikisi de derlemeyi durduruyor.
- "Gömme" burada Bileşik Tipler konusundaki yapı gömmeden ayrı bir düzenek: biri kod içi bir
  ilişki, öteki derleyicinin dosyadan değişkene kurduğu bir bağ.

#### Sonraki Adım

Bu derste ölçülen bağ zamanla ilgiliydi ama zamanın kendisi hiç ölçülmedi — yalnızca "önce
derlendi, sonra silindi, sonra çalıştırıldı" sırası önemliydi. Bu sıralama, sınırı geçen bir
değerin ne taşıdığı sorusuna konunun ilk iki dersinin verdiği yanıtı tamamlıyor: dosya, arabellek
ve dizgi tip bilgisini hiç taşımıyordu; gömülü kaynak ise içeriği taşıyor ama bunu sınırı
**koşum sırasında** değil, **derleme sırasında** geçerek yapıyor — sınırın kendisi zamanla
kayıyor.

Sıradaki ders zamanın kendisini, bir değerin metne dönüp geri geldiğinde neyin taşındığını ve
neyin kaybolduğunu soruyor: bir an, bir saat dilimine bağlandığında ne kazanıyor, metne
yazılıp geri okunduğunda bu kazanımdan ne kalıyor? Burada gömme yönergesinin verdiği "içerik
sabit" güvencesinin bir karşılığı yok — zaman, tanımı gereği ölçüldüğü ânın kendisiyle
sınırlı ve o ânı bir dizgiye yazıp geri okumak, gömülü bir dosyayı okumaktan farklı bir kayıp
türü doğuruyor.

### Zaman İşlemleri

Source: https://academia.sh/tr/kurslar/go-uygulamalar/zaman-islemleri

Bir anın metne dönüp geri gelmesi ölçülür: an kendisi Equal ile korunuyor, ama sayısal saat dilimi ofseti geri kurulsa bile bölgenin adı taşınmıyor. time.Duration'ın bir sayı değil kendi tipiyle taşınan bir değer olduğu gösterilir. Sınırlayıcı ölçüm, aynı anı gösteren iki değerin Equal ile eşit çıkarken == ve String() ile farklı çıktığını koşturarak ortaya koyuyor.

Önceki ders bir bağın koşum zamanına değil derleme zamanına kurulabileceğini gösterdi: bir
kaynak dosyanın içeriği, programın kendisiyle birlikte donuyordu. Zaman bunun tam tersi bir
örnek sunuyor — hiçbir değer, "an" kadar koşumun kendisine bağlı. Bir an, ölçüldüğü ânın
kendisiyle tanımlanıyor; onu bir metne yazıp geri okumak, önceki iki derste ölçülen
kayıplardan farklı bir tür kayba yol açıyor.

Bu farkın somut sonucu şu: dosya ve arabellek dersinde sınırı geçen veri hiçbir zaman tip
bilgisi taşımıyordu — kayıp her defasında tamdı. Zaman değerinde durum daha ince: sınırı geçen
metin bazı bilgiyi taşıyor, bazısını taşımıyor ve hangisinin hangi kategoriye girdiği
kullanılan yazım düzenine bağlı. Bu ders üç ayrı değeri (an, saat dilimi, süre) aynı
"metne dönüp geri gelme" sınavından geçirip her birinin ne kadarının hayatta kaldığını ayrı
ayrı ölçüyor.

**Belirlenimcilik notu:** bu derste gerçek saat hiç okunmuyor (`time.Now` hiçbir yerde
çağrılmıyor). Bütün örnekler sabit bir metinden ayrıştırılan tek bir ana dayanıyor;
karşılaştırmalar da bu sabit anın üzerine kuruluyor.

#### Sabit Bir Andan Başlamak

Go'nun standart kitaplığında bir anı temsil eden tip `time.Time`; `time.Parse` bir dizgiyi
belirli bir **düzene** (`layout`) göre okuyup bir `time.Time` değeri üretiyor. Bu derste
kullanılan düzen RFC 3339 — ağ ve dosya biçimlerinde yaygın kullanılan, saat dilimini de
taşıyan bir zaman yazımı.

```go
// sabit-an.go — gercek saat okunmuyor, an sabit bir metinden ayristiriliyor
package main

import (
	"fmt"
	"time"
)

func main() {
	sabitAn, err := time.Parse(time.RFC3339, "2026-08-23T10:00:00Z")
	if err != nil {
		panic(err)
	}
	fmt.Printf("ayristirilan deger tipi=%T\n", sabitAn)
	fmt.Println("saat dilimi (Z = UTC)  :", sabitAn.Location())
}
```

```
ayristirilan deger tipi=time.Time
saat dilimi (Z = UTC)  : UTC
```

Dizginin sonundaki `Z` harfi, RFC 3339 düzeninde "UTC, sıfır ofset" anlamına geliyor;
`time.Parse` bunu tanıyıp sonucu doğrudan `time.UTC` konumuna bağlıyor. Bu ders boyunca
kullanılan tek "gerçek" veri bu sabit dizgi; ondan sonraki her adım bu değerden türetiliyor.
`time.Parse`in ikinci argümanı ayrıştırılacak metin, ilk argümanı ise beklenen **düzen** —
metnin kendisi değil, metnin biçiminin bir örneği. Bu örnek yalnızca yer tutucu değil, gerçek
bir tarih: `time.RFC3339` sabiti Go'nun standart kitaplığında `2006-01-02T15:04:05Z07:00`
biçiminde tanımlı ve bu belirli tarih (ay/gün/saat/dakika/saniye/yıl'ın Go'nun kendi iç
sıralamasında karşılık gelen rakamları) düzeni belirtmenin standart yolu; başka bir dilin
`YYYY-MM-DD` gibi soyut yer tutucularından farklı bir yaklaşım.

Bir `time.Time` değeri her zaman aynı türden veri taşımıyor. Yalnızca `time.Now()`
çağrısından gelen değerler, saat farkı ölçümlerinde kullanılan bir **tek yönlü sayaç**
okuması (`duvar saati`nden ayrı, yalnızca ileri akan bir sayım) taşıyor; `time.Parse` gibi
bir metinden türeyen değerler bu ek okumayı hiç taşımıyor, yalnızca düz bir duvar saati
değeri oluyor. Bu ders yalnızca `time.Parse` kullandığından, ölçülen her değer bu ikinci,
"tek düze" türden.

#### Zaman Metne Dönüp Geri Geldiğinde

**SS8.** — Sabit an `Format` ile metne çevrilip aynı düzenle geri `Parse` ediliyor. Bu, Ağ ve Veri
Biçimleri konusunun ortak çekirdeğinde de bir satır olarak geçen geçiş (zaman değeri metne ve
geri: yeniden kuruluyor); o konunun bağlayıcı 1/5/7 dağılımına burada tek cümleyle atıf yapılıyor;
çekirdek yeniden kurulmuyor. Bu ders o satırı doğrulayıp saat dilimine özgü bir ayrıntıyla
genişletiyor.

```go
// metne-geri.go — bir an metne yazilip ayni duzenle geri okunuyor
package main

import (
	"fmt"
	"time"
)

func main() {
	sabitAn, _ := time.Parse(time.RFC3339, "2026-08-23T10:00:00Z")

	metin := sabitAn.Format(time.RFC3339)
	geri, _ := time.Parse(time.RFC3339, metin)

	fmt.Println("metne donen yazim :", metin)
	fmt.Println("ayni an mi (Equal):", geri.Equal(sabitAn))
	fmt.Printf("geri gelen tip    : %T\n", geri)
}
```

```
metne donen yazim : 2026-08-23T10:00:00Z
ayni an mi (Equal): true
geri gelen tip    : time.Time
```

Bir `time.Time` değeri bir dizgiye yazıldığında tip bilgisi tamamen kayboluyor — sonuç sıradan
bir metin, içinde "bu bir zaman" diyen hiçbir işaret yok. Ama `time.Parse` çağıran taraf
elindeki dizginin bir zaman **olduğunu zaten biliyor** ve doğru düzeni vererek onu geri
kuruyor; kayan bilgi, çağıranın elindeki bu bilgiyle **yeniden kuruluyor**. `Equal` sonucu
`true`: geri kurulan an, orijinal anla birebir aynı. Konunun ölçü ekseninde bu satır
"yeniden kuruluyor" sınıfına giriyor — tıpkı bir yapı etiketinin JSON'dan geri kurulması gibi,
burada da geri kurmayı sağlayan şey dizginin kendisi değil, çağıranın bildiği **düzen**.

#### Saat Dilimi Adı Taşınmıyor

Yukarıdaki ölçüm bir ayrıntıyı gizliyor: sabit an tam olarak UTC'de, yani sıfır ofsette.
RFC 3339 düzeni sıfır ofseti `Z` harfiyle özel olarak işaretlediği için, geri kurulan değer de
tam olarak `time.UTC`'ye bağlanıyor. Sıfırdan farklı bir ofset söz konusu olduğunda tablo
değişiyor.

**SS9.** — Sabit an, adı **`BolgeA`** olan (gerçek bir coğrafi bölgeye değil, yalnızca +03:00
ofsetine sahip **kurgusal** bir saat dilimine) çevriliyor, metne yazılıp geri okunuyor. Ölçüm
hem sayısal ofseti hem bölge adını ayrı ayrı karşılaştırıyor.

```go
// bolge-adi.go — sayisal ofset korunuyor, bolgenin adi tasinmiyor
package main

import (
	"fmt"
	"time"
)

func main() {
	sabitAn, _ := time.Parse(time.RFC3339, "2026-08-23T10:00:00Z")
	bolgeA := time.FixedZone("BolgeA", 3*3600)
	yerel := sabitAn.In(bolgeA)

	metin := yerel.Format(time.RFC3339)
	geri, _ := time.Parse(time.RFC3339, metin)

	yerelAd, yerelOfset := yerel.Zone()
	geriAd, geriOfset := geri.Zone()

	fmt.Println("metne donen yazim :", metin)
	fmt.Println("ayni an mi (Equal):", yerel.Equal(geri))
	fmt.Println("ofset korunuyor mu:", yerelOfset == geriOfset)
	fmt.Println("bolge adi tasindi mi:", yerelAd == geriAd)
}
```

```
metne donen yazim : 2026-08-23T13:00:00+03:00
ayni an mi (Equal): true
ofset korunuyor mu: true
bolge adi tasindi mi: false
```

RFC 3339 düzeni bir saat diliminin **adını** hiç taşımıyor, yalnızca sayısal ofsetini
(`+03:00`) yazıyor. Geri okunduğunda `time.Parse` bu ofseti doğru biçimde geri kuruyor — an
hâlâ aynı, `Equal` hâlâ `true`, ofset karşılaştırması da `true`. Ama bölge **adı**
(`"BolgeA"`) hiçbir yerde metne yazılmadığı için geri gelmiyor; `Parse`in ürettiği yeni
konumun adı, orijinal `"BolgeA"`dan farklı çıkıyor. Bu, kaydın önceki dersle aynı biçimde
okunuyor: sayısal ofset **yeniden kuruluyor**, çünkü metnin kendisi onu taşıyor; bölge adı ise
**kayboluyor**, çünkü metnin kendisi onu hiç taşımıyor. Aynı geçiş, taşınan bilginin **parçasına**
göre iki farklı sınıfa giriyor.

#### time.Duration Bir Tiptir, Bir Sayı Değil

Bir sürenin kendisi de Go'da ayrı bir tip taşıyor: `time.Duration`, altında bir `int64`
saklayan ama kendi başına bir tip olan, nanosaniye cinsinden bir süre. Bir sayıdan farkı,
bir sayının aksine kendi biçimlendirmesini (`1h30m0s` gibi) taşıması ve başka bir süreyle
karıştırılmasının derleyici tarafından reddedilmesi.

**SS10.** — Bir süre dizgisi (`"1h30m"`) ayrıştırılıp tipi ve biçimlendirilmiş yazımı
ölçülüyor; ardından aynı değerin ham bir tam sayı karşılığıyla eşit olup olmadığı ve iki katına
katlandığında biçimlendirmenin nasıl değiştiği koşturuluyor.

```go
// sure-tipi.go — time.Duration kendi tipini tasiyor, ham sayidan ayri
package main

import (
	"fmt"
	"time"
)

func main() {
	sure, _ := time.ParseDuration("1h30m")
	fmt.Printf("sure tipi=%T deger=%s\n", sure, sure)
	fmt.Println("int64 karsiligina esit mi:", int64(sure) == 5400_000_000_000)

	ikiKat := sure * 2
	fmt.Println("iki kati:", ikiKat)
	fmt.Println("saat cinsinden:", ikiKat.Hours())
}
```

```
sure tipi=time.Duration deger=1h30m0s
int64 karsiligina esit mi: true
iki kati: 3h0m0s
saat cinsinden: 3
```

`sure` değişkeninin altında yatan veri bir `int64` — nanosaniye sayısı — ama `%T` onu ham bir tam
sayı olarak değil, `time.Duration` olarak gösteriyor ve `fmt.Println` onu doğrudan yazdırdığında
`5400000000000` değil `1h30m0s` çıkıyor. Bu, tam sayı ile süre arasında derleyicinin kurduğu bir
ayrım: `sure`ün altındaki `int64` değeri ile başka bir `int64` değişken arasında örtük dönüşüm
yok; ikisi ayrı tipler. `ikiKat := sure * 2` çarpımı süre tipini koruyor ve biçimlendirmesi de
otomatik olarak güncelleniyor — `3h0m0s`, `Hours()` yöntemiyle sorgulandığında da `3` veriyor.

Süre de, tıpkı bir an gibi, metne dönüp geri gelebiliyor — ve bu geçiş bir anın geçişinden
daha az kayıplı çıkıyor.

**SS11.** — `sure` değişkeni `String()` ile metne çevrilip aynı metin `ParseDuration` ile
geri okunuyor; ayrıca sözdizimi bozuk bir metin de deneniyor.

```go
// sure-metne-geri.go — sure metne donup geri geliyor, gecersiz metin reddediliyor
package main

import (
	"fmt"
	"time"
)

func main() {
	sure, _ := time.ParseDuration("1h30m")
	metin := sure.String()
	geri, hataGeri := time.ParseDuration(metin)
	fmt.Println("sure metne donusumu:", metin)
	fmt.Println("geri ayni deger mi :", geri == sure)
	fmt.Println("geri okuma hatasiz mi:", hataGeri == nil)

	_, hataGecersiz := time.ParseDuration("1h30mx")
	fmt.Println("gecersiz metin hata verdi mi:", hataGecersiz != nil)
}
```

```
sure metne donusumu: 1h30m0s
geri ayni deger mi : true
geri okuma hatasiz mi: true
gecersiz metin hata verdi mi: true
```

Bir anın metne dönüşünden farklı olarak, `time.Duration`ın yazımı hiçbir bilgi kaybetmiyor:
`geri == sure` karşılaştırması `true`, çünkü `String()`ün ürettiği metin (`1h30m0s`) süreyi
oluşturan her birimi (saat, dakika, saniye) tam olarak taşıyor — bir sürenin bir "adı" ya da
"bağlamı" yok, taşıdığı tek bilgi kendi uzunluğu ve bu bilgi metne tam olarak sığıyor. Bu,
saat diliminin adını taşıyamayan bir önceki ölçümle tam bir karşıtlık: kaybın olup olmaması,
taşınan değerin **kendisinin** ne kadar bilgi içerdiğine bağlı, kullanılan yazım biçiminin
kendisine değil. Son satır ayrı bir gözlem ekliyor: sözdizimi bozuk bir süre metni sessizce
`0`a düşmüyor, `ParseDuration` bunu bir hatayla reddediyor — süre burada da derleme zamanı
değil ama en azından ayrıştırma zamanı bir denetimden geçiyor.

#### Sınırlayıcı Ölçüm: Aynı An, İki Yazım

Buraya kadarki ölçümler bir anı **tek** bir saat diliminde inceledi. Aşağıdaki adım aynı anı
**iki farklı** kurgusal bölgede temsil edip üç ayrı karşılaştırma aracını (`Equal`, `==`,
`String`) yan yana koşturuyor.

**SS12.** — İki kurgusal bölge (`BolgeA` +03:00, `BolgeB` −05:00) aynı sabit ana uygulanıyor.
Üçü de "aynı mı" sorusuna farklı bir yanıt veriyor.

```go
// iki-yazim.go — ayni an iki bolgede, uc ayri karsilastirma araciyla olculuyor
package main

import (
	"fmt"
	"time"
)

func main() {
	sabitAn, _ := time.Parse(time.RFC3339, "2026-08-23T10:00:00Z")
	bolgeA := time.FixedZone("BolgeA", 3*3600)
	bolgeB := time.FixedZone("BolgeB", -5*3600)

	gorunumA := sabitAn.In(bolgeA)
	gorunumB := sabitAn.In(bolgeB)

	fmt.Println("BolgeA yerel saat :", gorunumA.Format("15:04"))
	fmt.Println("BolgeB yerel saat :", gorunumB.Format("15:04"))
	fmt.Println("Equal ile ayni mi :", gorunumA.Equal(gorunumB))
	fmt.Println("== ile ayni mi    :", gorunumA == gorunumB)
	fmt.Println("String() ayni mi  :", gorunumA.String() == gorunumB.String())
}
```

```
BolgeA yerel saat : 13:00
BolgeB yerel saat : 05:00
Equal ile ayni mi : true
== ile ayni mi    : false
String() ayni mi  : false
```

Üç karşılaştırma aracı üç farklı soruya yanıt veriyor. `Equal`, iki değerin temsil ettiği
**anı** karşılaştırıyor — ikisi de aynı ana ait olduğu için sonuç `true`. `==` işleci ise
`time.Time`in **iç yapısını** birebir karşılaştırıyor; bu yapı saat dilimi bilgisini de
içerdiğinden, iki farklı bölgeye bağlı iki değer `==` ile eşit çıkmıyor. `String()` de aynı
nedenle farklı: her biri kendi yerel saatini ve ofsetini yazıyor, biri "13:00 +03:00" biri
"05:00 -05:00" — metin düzeyinde hiç örtüşmüyorlar.

Bu üçü arasındaki fark, Go'nun belgesinde açıkça uyarılan bir tuzak: `==` işleci `time.Time`
değerleri için **kullanılmaması gereken** bir karşılaştırma, çünkü onu kullanan kod "aynı an
mı" sorusunu değil, "aynı temsil mi" sorusunu yanıtlıyor — çoğu zaman istenen ilk soru, ama
işlecin kendisi ikinciye yanıt veriyor. Konunun ölçü ekseninde bu, bu derse özgü bir sınır
çiziyor: iki değer aynı anı gösterdiğinde bile, karşılaştırma aracının **kendisi** seçilen
soruyu belirliyor; "aynı" sözcüğü tek başına hangi karşılaştırmanın kastedildiğini söylemiyor.

Aynı ayrım sıralama için de geçerli. `Before` ve `After` yöntemleri, tıpkı `Equal` gibi,
temsile değil ana bakıyor; iki değer farklı bölgelerde yazılmış olsa bile hangisinin
zamanda daha önce geldiğini doğru söylüyor.

```go
// siralama.go — Before ve After da temsile degil ana bakiyor
package main

import (
	"fmt"
	"time"
)

func main() {
	sabitAn, _ := time.Parse(time.RFC3339, "2026-08-23T10:00:00Z")
	birSaatSonra := sabitAn.Add(time.Hour)

	bolgeA := time.FixedZone("BolgeA", 3*3600)
	gorunumA := sabitAn.In(bolgeA)
	gorunumSonraB := birSaatSonra.In(time.FixedZone("BolgeB", -5*3600))

	fmt.Println("gorunumA, gorunumSonraB'den once mi:", gorunumA.Before(gorunumSonraB))
	fmt.Println("gorunumSonraB, gorunumA'dan sonra mi:", gorunumSonraB.After(gorunumA))
}
```

```
gorunumA, gorunumSonraB'den once mi: true
gorunumSonraB, gorunumA'dan sonra mi: true
```

İki değer birbirinden tamamen farklı bölgelerde ve tamamen farklı yerel saatlerde yazılı
olmasına rağmen `Before` ve `After` doğru sırayı buluyor, çünkü ikisi de karşılaştırmayı
temsil edilen **an** üzerinden yapıyor — `==` işlecinin yaptığı gibi iç yapının birebir
eşleşmesini aramıyor. Bu, önceki paragrafın genel kuralını doğruluyor: `time.Time`
değerlerini karşılaştırırken `Equal`, `Before` ve `After` güvenli bir taban oluşturuyor, `==`
ise yalnızca iki değerin **aynı bölgede, aynı biçimde** üretildiği bilindiğinde güvenli.

#### Özet

- Bir an metne dönüp aynı düzenle geri okunduğunda `Equal` ile korunuyor; bu, konunun ortak
  çekirdeğindeki "yeniden kuruluyor" satırının doğrulanmış hâli.
- RFC 3339 düzeni bir saat diliminin sayısal ofsetini taşıyor ama adını taşımıyor: geri
  kurulan değerin ofseti orijinaliyle eşleşirken bölge adı eşleşmiyor.
- `time.Duration` altında bir `int64` taşısa da kendi tipini koruyor; biçimlendirmesi ve
  aritmetiği bu tipe göre işliyor, ham bir sayıyla karıştırılmıyor. Metne dönüp geri gelmesi
  de bilgi kaybetmiyor, çünkü taşıdığı tek şey kendi uzunluğu.
- Sınırlayıcı ölçüm: aynı anı gösteren iki değer `Equal` ile eşit çıkarken `==` ve `String()`
  ile farklı çıkıyor — üç araç üç farklı soruya yanıt veriyor.
- `Before` ve `After` da `Equal` gibi ana bakıyor, temsile değil; `==` işleci ise yalnızca iki
  değerin aynı bölgede ve biçimde üretildiği bilindiğinde güvenli bir karşılaştırma.

#### Sonraki Adım

Bu derste bir zaman değerinin metne dönüşürken alan adı taşımadığı, yalnızca ham bir yazım
ürettiği görüldü — `2026-08-23T13:00:00+03:00` metninin hangi alanın hangi değeri olduğunu
söyleyen hiçbir işaret yok, yalnızca konumdan çıkarılabiliyor. Bir okuyucu bu metni doğru
yorumlayabiliyor çünkü RFC 3339 düzenini zaten biliyor — tıpkı bir önceki dersteki gömme
yönergesinin, hedef tipi zaten bilen bir değişkene içerik yerleştirmesi gibi. Ama her biçim bu
kadar cömert değil: konumdan çıkarılan bir alan, konum kaybolduğunda ya da biçim değiştiğinde
bir daha çıkarılamıyor.

Sıradaki ders bu boşluğu tam olarak dolduran bir biçimi ölçüyor: bir olayı günlüğe yazarken
alan adlarını da taşımak, aynı kaydı hem insan hem başka bir program için ayrıştırılabilir
kılıyor mu? Bu dersin sabit zaman damgası, sıradaki dersin günlük kayıtlarında da aynen
kullanılacak — zaman burada da gerçek saatten değil, sabit bir metinden gelecek.

### Yapılandırılmış Günlükleme

Source: https://academia.sh/tr/kurslar/go-uygulamalar/yapilandirilmis-gunlukleme

Aynı olay iki biçimde günlüğe yazılır: düz metin ile anahtar-değer temelli günlük. Anahtar-değer biçiminin, alan sırası değişse bile alan adlarını taşıdığı ve düz metnin kaybettiği ayrıştırılabilirliği koruduğu ölçülür. Sınırlayıcı ölçüm, alan adlarının korunmasına rağmen tiplerin korunmadığını gösteriyor: bir tam sayı da metin olarak geliyor ve geri kurmak okuyan tarafın kendi tarifini gerektiriyor.

Önceki ders bir zaman değerinin metne dönüştüğünde alan adı taşımadığını, yalnızca konumdan
çıkarılabilen ham bir yazım ürettiğini gösterdi. Bu ders aynı sınırı bir günlük kaydı üzerinde
soruyor: bir olayı metne dökerken alan adlarını da taşımak mümkün mü ve taşımak neyi
değiştiriyor? Yapılandırılmış günlükleme kavramının kendisi — neden günlük tutulur, düzey
nedir, bir günlük hattı nasıl kurulur — Gözlemlenebilirlik ve İşletim kursu ile Derleme, Test
ve Ekosistem kursunda kurulmuştu ve burada tekrar kurulmuyor. Bu dersin payı dar: aynı olayın
iki yazımını yan yana koyup, hangi yazımın hangi alanı **ayrıştırılabilir** bıraktığını
ölçmek.

Bu ayrımın pratikte önemli olduğu yer, günlüğü **yazan** kod değil, onu sonradan **okuyan**
taraf. Bir günlük kaydı yazıldığı anda genelde bir insan tarafından hemen okunmuyor;
biriktiriliyor, aranıyor, süzülüyor — bir arama aracı "adet 10'dan büyük olan kayıtları
göster" gibi bir soru sorduğunda, o aracın günlük satırının **hangi kelimesinin** "adet"
olduğunu bilmesi gerekiyor. Düz metin bu soruyu insana bırakıyor, anahtar-değer biçimi ise
aracın kendisine bir tutamak veriyor.

**Belirlenimcilik notu:** günlüklenen olayın zaman damgası önceki dersle aynı sabit metin
(`2026-08-23T10:00:00Z`); gerçek saat bu derste de hiç okunmuyor.

#### Aynı Olay, İki Biçim

Aşağıdaki `Olay` yapısı, günlüğe yazılacak tek bir olayı taşıyor: bir kullanıcı adı, bir
sayı, bir başarı durumu, bir zaman damgası. Bu olay iki ayrı işlevle metne dönüştürülüyor —
biri doğal dilde bir cümle kuruyor, öteki alan adlarını `ad=değer` biçiminde art arda
diziyor.

**SS13.** — Aynı `Olay` değeri iki biçimde yazdırılıyor ve her iki satırın boşluklarla
ayrıldığında kaç parçaya bölündüğü sayılıyor.

```go
// iki-bicim.go — ayni olay duz metin ve anahtar-deger olarak yaziliyor
package main

import (
	"fmt"
	"strings"
)

type Olay struct {
	Kullanici string
	Adet      int
	Basarili  bool
	Zaman     string
}

func duzMetin(o Olay) string {
	return fmt.Sprintf("%s kullanicisi %d adet islem yapti, basarili=%v (%s)",
		o.Kullanici, o.Adet, o.Basarili, o.Zaman)
}

func anahtarDeger(o Olay) string {
	return fmt.Sprintf("zaman=%s kullanici=%s adet=%d basarili=%t",
		o.Zaman, o.Kullanici, o.Adet, o.Basarili)
}

func main() {
	olay := Olay{Kullanici: "ayse", Adet: 12, Basarili: true, Zaman: "2026-08-23T10:00:00Z"}

	duz := duzMetin(olay)
	kv := anahtarDeger(olay)

	fmt.Println("duz metin    :", duz)
	fmt.Println("anahtar-deger:", kv)

	duzParca := strings.Fields(duz)
	kvParca := strings.Fields(kv)
	fmt.Println("duz metin bosluk-ayrilmis parca sayisi     :", len(duzParca))
	fmt.Println("anahtar-deger bosluk-ayrilmis parca sayisi :", len(kvParca))

	adlandirilmisAlan := 0
	for _, parca := range kvParca {
		if _, _, tamamMi := strings.Cut(parca, "="); tamamMi {
			adlandirilmisAlan++
		}
	}
	fmt.Println("anahtar-deger icinde adi olan alan sayisi   :", adlandirilmisAlan)
}
```

```
duz metin    : ayse kullanicisi 12 adet islem yapti, basarili=true (2026-08-23T10:00:00Z)
anahtar-deger: zaman=2026-08-23T10:00:00Z kullanici=ayse adet=12 basarili=true
duz metin bosluk-ayrilmis parca sayisi     : 8
anahtar-deger bosluk-ayrilmis parca sayisi : 4
anahtar-deger icinde adi olan alan sayisi   : 4
```

Aynı dört alan (kullanıcı, adet, başarı durumu, zaman), düz metin sürümünde sekiz ayrı
boşlukla-ayrılmış parçaya dağılıyor — "12" ile "adet" ayrı parçalar, "yapti," noktalama
işaretiyle birlikte tek bir parça. Bu satırdan geriye dönük olarak "adet neydi" sorusunu
yanıtlamak, cümlenin **sözdizimini** bilmeyi gerektiriyor: kaçıncı kelimenin sayı olduğunu,
noktalamanın nereye düştüğünü. Anahtar-değer sürümü ise dört alanı dört parçaya bölüyor ve
her parçanın kendi adını taşıyor; okuyan taraf cümlenin yapısını bilmek zorunda değil, yalnızca
`=` işaretini arıyor.

Bu fark, tek bir günlük satırı elle okunurken küçük görünüyor — bir insan "12 adet" ifadesini
bağlamdan anlıyor. Ama bir günlük hattının gerçek boyutu binlerce satırlık bir akış ve o
akışı okuyan taraf çoğu zaman bir insan değil, alan adına göre süzen ya da sayan bir araç.
Böyle bir araç için düz metindeki her cümle kalıbı ayrı bir özel durum: "kullanicisi" sözcüğü
her zaman ikinci kelime mi, yoksa bazı olay türlerinde üçüncü mü? Anahtar-değer biçiminde bu
soru hiç sorulmuyor, çünkü aracın aradığı şey konum değil ad.

#### Sıra Bağımsızlığı

Bir günlük hattının gerçek dünyadaki bir özelliği: aynı olay türü, farklı kod yollarından
farklı sırayla yazılabiliyor — bir yerde önce zaman sonra kullanıcı, başka bir yerde tam
tersi. Bu farklılık kasıtlı bir tutarsızlık değil; günlükleme çağrısı genelde işlevin
ihtiyaç duyduğu değerleri elde ettiği sırayla diziliyor ve bu sıra çağıran işleve göre
değişiyor. Düz metinde bu, cümlenin **tamamen** değişmesi demek; anahtar-değer biçiminde alan
adları sabit kaldığı sürece sıra önemsiz kalıyor.

**SS14.** — Aynı olay iki farklı alan sırasıyla yazdırılıyor; bir arama işlevi her iki
satırdan da `adet` alanını adına göre buluyor.

```go
// sira-bagimsizligi.go — anahtar-deger bicimi alan sirasindan bagimsiz araniyor
package main

import (
	"fmt"
	"strings"
)

type Olay struct {
	Kullanici string
	Adet      int
	Basarili  bool
	Zaman     string
}

func anahtarDegerSirali(o Olay, sira []string) string {
	parcalar := make([]string, 0, len(sira))
	for _, ad := range sira {
		switch ad {
		case "zaman":
			parcalar = append(parcalar, "zaman="+o.Zaman)
		case "kullanici":
			parcalar = append(parcalar, "kullanici="+o.Kullanici)
		case "adet":
			parcalar = append(parcalar, fmt.Sprintf("adet=%d", o.Adet))
		case "basarili":
			parcalar = append(parcalar, fmt.Sprintf("basarili=%t", o.Basarili))
		}
	}
	return strings.Join(parcalar, " ")
}

func alanBul(gunluk, ad string) (string, bool) {
	for _, parca := range strings.Fields(gunluk) {
		k, v, tamamMi := strings.Cut(parca, "=")
		if tamamMi && k == ad {
			return v, true
		}
	}
	return "", false
}

func main() {
	olay := Olay{Kullanici: "ayse", Adet: 12, Basarili: true, Zaman: "2026-08-23T10:00:00Z"}

	satir1 := anahtarDegerSirali(olay, []string{"zaman", "kullanici", "adet", "basarili"})
	satir2 := anahtarDegerSirali(olay, []string{"basarili", "adet", "kullanici", "zaman"})

	fmt.Println("birinci kod yolunun gunlugu:", satir1)
	fmt.Println("ikinci kod yolunun gunlugu :", satir2)

	adet1, _ := alanBul(satir1, "adet")
	adet2, _ := alanBul(satir2, "adet")
	fmt.Println("adet alani iki siradan da ayni geldi mi:", adet1 == adet2)
}
```

```
birinci kod yolunun gunlugu: zaman=2026-08-23T10:00:00Z kullanici=ayse adet=12 basarili=true
ikinci kod yolunun gunlugu : basarili=true adet=12 kullanici=ayse zaman=2026-08-23T10:00:00Z
adet alani iki siradan da ayni geldi mi: true
```

`satir1` ile `satir2` metin olarak birbirinden tamamen farklı — hiçbir alan aynı konumda
değil. Yine de `alanBul` her ikisinden de aynı `adet` değerini buluyor, çünkü aradığı şey
konum değil **ad**. Düz metin biçiminde bu arama mümkün olmazdı: "12" kelimesinin adet mi,
başka bir sayı mı olduğunu ayırt etmenin tek yolu cümlenin tam yapısını bilmek ve iki farklı
kod yolu iki farklı cümle yapısı üretirse aynı ayrıştırıcı ikisinde de çalışamaz.

#### Değerin İçinde Boşluk Olduğunda

Yukarıdaki ölçüm alan değerlerinin hiçbirinin boşluk içermediğini varsaydı. `alanBul`
yalnızca `strings.Fields` ile boşluktan bölüyor; bir değerin **kendisi** boşluk içerdiğinde
bu varsayım bozuluyor ve sonuç bir hata değil, **sessiz** bir kırpılma.

**SS15.** — `kullanici` alanına iki kelimelik bir değer ("ayse yilmaz") veriliyor; aynı
`alanBul` işlevi hem bu alanı hem etkilenmeyen `adet` alanını arıyor.

```go
// bosluklu-deger.go — deger icinde bosluk olunca alan sessizce kirpiliyor
package main

import (
	"fmt"
	"strings"
)

func alanBul(gunluk, ad string) (string, bool) {
	for _, parca := range strings.Fields(gunluk) {
		k, v, tamamMi := strings.Cut(parca, "=")
		if tamamMi && k == ad {
			return v, true
		}
	}
	return "", false
}

func main() {
	gunlukSatiri := fmt.Sprintf("zaman=2026-08-23T10:00:00Z kullanici=%s adet=%d basarili=%t",
		"ayse yilmaz", 12, true)
	fmt.Println("gunluk satiri:", gunlukSatiri)

	kullanici, _ := alanBul(gunlukSatiri, "kullanici")
	fmt.Printf("cikarilan kullanici alani: %q (beklenen: %q)\n", kullanici, "ayse yilmaz")

	adet, _ := alanBul(gunlukSatiri, "adet")
	fmt.Printf("cikarilan adet alani     : %q\n", adet)
}
```

```
gunluk satiri: zaman=2026-08-23T10:00:00Z kullanici=ayse yilmaz adet=12 basarili=true
cikarilan kullanici alani: "ayse" (beklenen: "ayse yilmaz")
cikarilan adet alani     : "12"
```

`kullanici` alanı sessizce `"ayse"`ye kırpılıyor — `"yilmaz"` kelimesi kendi başına, adı
olmayan bir parça olarak kalıyor ve `alanBul` bir hata döndürmüyor. `adet` alanı bu
örnekte etkilenmiyor, çünkü ondan önceki kırpılma boşlukla ayrılmış parça sayısını
değiştiriyor ama sonraki alanların kendi `=` işaretleri yine doğru yerde duruyor — ama bu
şans eseri, biçimin kendi güvencesi değil. Anahtar-değer biçiminin adlandırılmış ve sıra
bağımsız olması, değerlerin **kendi içinde** ayırıcı karakter (bu örnekte boşluk)
taşımayacağını hiç garanti etmiyor; bu biçimi gerçekten güvenilir kılan üretim sistemleri,
değerleri tırnak içine alıp kaçış karakterleriyle koruyan bir yazım kullanıyor — bu ders
bunu göstermiyor, çünkü amaç biçimin kendisini değil, adsız düz metinle adlandırılmış
anahtar-değer arasındaki temel farkı ölçmek.

Bu kırılganlığı tamamen ortadan kaldıran bir yazım Ağ ve Veri Biçimleri konusunda zaten
ölçülmüştü: JSON, her alanı adıyla **ve** açık sınırlarla (tırnak, virgül, süslü parantez)
kodluyor, bu yüzden bir değerin içindeki boşluk hiçbir alanı kaydırmıyor. O ölçüm burada
tekrar kurulmuyor; burada vurgulanan, basit bir `ad=değer` diziminin bu güvenceyi **kendiliğinden**
taşımadığı — adlandırılmış olmak başka, sınırları açık olmak başka.

#### Sınırlayıcı Ölçüm: Alan Adları Korunuyor, Tipler Korunmuyor

Anahtar-değer biçiminin sıra bağımsız ve adlandırılmış olması, alan adlarının **korunduğunu**
gösteriyor. Ama korunan yalnızca ad; alanın kendi Go tipi (`int`, `bool`) günlüğe hiç
yazılmıyor, hepsi metne dönüşüyor.

**SS16.** — `adet` alanı günlükten çıkarılıyor ve tipi `%T` ile okunuyor; ardından yalnızca
alan adına bakan bir kod, olası hedef tipi (`int`) **kendi tarifiyle** varsayıp değeri geri
kuruyor.

```go
// tip-kaybi.go — alan adi korunuyor, tip korunmuyor; geri kurma okuyanin tarifine bakiyor
package main

import (
	"fmt"
	"strconv"
	"strings"
)

func alanBul(gunluk, ad string) (string, bool) {
	for _, parca := range strings.Fields(gunluk) {
		k, v, tamamMi := strings.Cut(parca, "=")
		if tamamMi && k == ad {
			return v, true
		}
	}
	return "", false
}

func main() {
	gunlukSatiri := "zaman=2026-08-23T10:00:00Z kullanici=ayse adet=12 basarili=true"

	adetMetin, bulunduMu := alanBul(gunlukSatiri, "adet")
	fmt.Printf("gunlukten cikan adet degeri: %q tip=%T bulundu=%t\n", adetMetin, adetMetin, bulunduMu)

	adetSayi, hataParse := strconv.Atoi(adetMetin)
	fmt.Printf("tarifle (strconv.Atoi) geri kurulan: tip=%T deger=%d hatasiz=%t\n",
		adetSayi, adetSayi, hataParse == nil)

	basariliMetin, _ := alanBul(gunlukSatiri, "basarili")
	_, hataYanlisTarif := strconv.Atoi(basariliMetin)
	fmt.Println("basarili alanini int tarifiyle okumak hata verdi mi:", hataYanlisTarif != nil)
}
```

```
gunlukten cikan adet degeri: "12" tip=string bulundu=true
tarifle (strconv.Atoi) geri kurulan: tip=int deger=12 hatasiz=true
basarili alanini int tarifiyle okumak hata verdi mi: true
```

İlk satır konunun ölçü ekseninde beklenen sonucu doğruluyor: `alanBul`ün döndürdüğü değer,
`adet` alanının Go tarafında bir `int` olmasına rağmen, günlükten `string` olarak geliyor —
`%T` bunu `string` diye gösteriyor. Alan **adı** günlükte durduğu için `alanBul` doğru satırı
buluyor, ama alanın **tipi** hiç günlüğe yazılmadığı için okuyan taraf onu yeniden **tahmin**
etmek zorunda. `strconv.Atoi` çağrısı bu tahmini somutlaştırıyor: metni bir `int`e çeviriyor
ve bu kez başarılı oluyor, çünkü çağıran taraf `adet` alanının bir sayı olduğunu **zaten
biliyor** — bu bilgi günlükten gelmiyor, günlüğü yazan kodun sözleşmesinden geliyor. Son satır
bu tahminin yanlış olabileceğini gösteriyor: `basarili` alanı metin olarak `"true"` ve onu
bir tam sayı tarifiyle okumaya çalışmak hata veriyor — yanlış tarif, günlüğün kendisinden değil
okuyan kodun varsayımından kaynaklanıyor. Bu hata en azından **açık**: `strconv.Atoi` bir hata
değeri döndürüyor, program çökmüyor ya da sessizce yanlış bir sayı üretmiyor. Ama bu açıklık
yalnızca yanlış tarif **denendiğinde** ortaya çıkıyor; günlüğün kendisine bakan bir kod, hangi
alanın hangi tarifle okunması gerektiğini önceden söyleyen hiçbir işaret bulamıyor.

Bu, konunun ölçü ekseninde açık bir sınır çiziyor: anahtar-değer biçimi alan **adlarını**
sınırın öbür tarafına taşıyor, ama alan **tiplerini** taşımıyor. Aynı sayı, bir kaydın
"korunuyor" değil "hiçbir zaman kaybedilmemiş gibi görünen ama aslında hiç taşınmamış"
sınıfına giriyor — düz metinden farkı, en azından hangi alanın hangi değer olduğunun
belirsiz olmaması; ama değerin tipi, tıpkı önceki dersteki saat dilimi adı gibi, yalnızca
okuyan tarafın elindeki bir tarifle geri kuruluyor.

Bu tarif nereden geliyor sorusunun yanıtı, günlüğün kendisinde değil. `alanBul`ü çağıran kod,
`adet` alanının bir sayı olduğunu günlük satırından öğrenmiyor — bunu, günlüğü **yazan**
kodun kaynağına bakarak, ya da bir sözleşme belgesinden öğreniyor. İki taraf (yazan ve okuyan)
bu sözleşmeyi paylaştığı sürece sistem doğru çalışıyor; sözleşme değiştiğinde (`adet` bir gün
ondalıklı bir sayıya dönüştürüldüğünde) günlüğün kendisi bunu hiçbir şekilde haber vermiyor —
`strconv.Atoi` yeni biçimi de "geçerli bir tam sayı değil" diye reddedecek, ama neden
değiştiğini söylemeyecek.

#### Özet

- Aynı olay düz metin olarak sekiz, anahtar-değer olarak dört boşlukla-ayrılmış parçaya
  dönüşüyor; anahtar-değer biçiminde dört parçanın dördü de adıyla birlikte geliyor.
- Anahtar-değer biçimi alan **sırasından bağımsız**: iki farklı sırayla yazılmış aynı olay,
  aynı alan adına göre arandığında aynı değeri veriyor.
- Düz metinde bir alanı adına göre bulmanın güvenilir bir yolu yok; cümlenin tam yapısını
  bilmek gerekiyor ve o yapı kod yoluna göre değişebiliyor.
- Basit bir `ad=değer` dizimi, bir değerin kendisi boşluk içerdiğinde sessizce kırpılabiliyor;
  adlandırılmış olmak, sınırların açık olmasıyla aynı şey değil.
- Sınırlayıcı ölçüm: anahtar-değer biçimi alan adlarını koruyor ama tiplerini korumuyor — bir
  `int` alan da metin olarak geliyor, geri kurmak okuyan tarafın kendi tarifini gerektiriyor.
- Yanlış bir tarif (bir `bool` alanı `int` diye okumaya çalışmak) sessizce geçmiyor, açık bir
  hatayla reddediliyor — ama bu ret ancak yanlış tarif fiilen denendiğinde ortaya çıkıyor.

#### Sonraki Adım

Bu derste alan adları korunuyordu ama tipler her zaman metne dönüşüyordu; okuyan taraf hangi
alanın hangi Go tipine karşılık geldiğini kendi bildiğinden çıkarmak zorundaydı. Bu tarif hep
kod dışında, insan bilgisinde ya da bir sözleşme belgesinde durdu — günlüğün kendisi hiçbir
zaman "bu alan bir tam sayı" diye bir şey söylemedi.

Sıradaki ve bu konunun son dersi, tipin bu kez metne değil, standart kitaplığın kendi tanıdığı
**dar bir tip kümesine** indirgendiği bir sınırı ölçüyor: bir veritabanı sürücüsünün elinden
geçen bir satır, hangi tiplerde gelebiliyor ve hedef tip bu satırdan neyi nasıl geri kuruyor?
Bu kez tarif metinde değil, çağıranın **Scan** çağrısına verdiği hedef değişkenin kendi
tipinde duracak — bu derste `strconv.Atoi`nin oynadığı rolü, orada standart kitaplığın kendisi
üstlenecek.

### Veritabanı Erişimi

Source: https://academia.sh/tr/kurslar/go-uygulamalar/veritabani-erisimi

Bir kukla sürücüyle beş sütunluk bir sorgu koşturulur: sürücünün dar tip kümesinden gelen değerlerin üçü, hedef değişkenin kendi Scan yöntemiyle özel bir Go tipine geri kurulur, ikisi zaten sürücünün doğal tipiyle örtüşür. Sınırlayıcı ölçüm, taramanın hedefin tipine baktığını ve uyumsuz bir hedefin çalışma zamanında hata verdiğini, bağlantı havuzu ölçümü ise kapatılmayan bir satır kümesinin havuzu tıkadığını koşturarak gösteriyor.

Önceki ders bir günlük satırından çıkarılan bir alanın tipinin metin olarak kaldığını,
geri kurmanın okuyan tarafın kendi tarifine bağlı olduğunu gösterdi. Bu konunun son dersi
aynı soruyu bir veritabanı sürücüsünün elinden geçen bir satır üzerinde soruyor — ama bu kez
tarif metinde değil, kodun kendisinde: çağıranın `Scan` çağrısına verdiği hedef değişkenin
tipinde duruyor.

Veritabanı satırı, konu boyunca ölçülen sınırların en katmanlısı. Dosya ve arabellek dersinde
sınır tek bir arayüzdü (`Read`/`Write`); burada üç katman var — sürücünün kendi dar tip
kümesi, `database/sql`in bu kümeyi taşıyan genel mekanizması ve çağıranın `Scan`e verdiği
hedef tip. Her katman kendi payına düşen dönüşümü yapıyor ve bu ders üç katmanın da nerede
durduğunu ayrı ayrı gösteriyor.

**Gerçek kaynak kullanılmaz.** Bu derste hiçbir gerçek veritabanı çalıştırılmıyor; standart
kitaplığın `database/sql/driver` paketindeki sürücü arayüzü, bu dersin kendi yazdığı bir
**kukla** ile gerçekleştiriliyor. Kukla sürücü sabit, elde önceden hazırlanmış iki satırlık
bir veri döndürüyor; hiçbir ağ bağlantısı, dosya ya da dış süreç yok.

#### Kukla Sürücü

Go'nun standart kitaplığı `database/sql` paketiyle **genel** bir veritabanı arayüzü sunuyor;
gerçek sürücüler (belirli bir veritabanı ürününe özgü bağlantı mantığı) her zaman üçüncü
taraf paketler olarak dağıtılıyor. Bu ders o ayrımı ciddiye alıyor: `database/sql/driver`
paketindeki arayüzleri (`Driver`, `Conn`, `Rows`) kendi yazdığı basit bir tiple
gerçekleştiriyor, hiçbir üçüncü taraf sürücü içe aktarmıyor.

```go
// kukla.go — database/sql/driver arayuzu kendi kuklasiyla gerceklestiriliyor, gercek veritabani yok
package main

import (
	"database/sql"
	"database/sql/driver"
	"fmt"
	"io"
	"time"
)

// KullaniciID, satirin ilk sutununu tasiyan tip: surucu bunu int64 olarak tasir.
type KullaniciID int64

func (k *KullaniciID) Scan(kaynak any) error {
	v, ok := kaynak.(int64)
	if !ok {
		return fmt.Errorf("KullaniciID.Scan: beklenmeyen kaynak tipi %T", kaynak)
	}
	*k = KullaniciID(v)
	return nil
}

// Tutar, kurus cinsinden bir tutari tasir; surucu bunu da int64 olarak tasir.
type Tutar int64

func (t *Tutar) Scan(kaynak any) error {
	v, ok := kaynak.(int64)
	if !ok {
		return fmt.Errorf("Tutar.Scan: beklenmeyen kaynak tipi %T", kaynak)
	}
	*t = Tutar(v)
	return nil
}

// ZamanDamgasi, surucunun dizgi olarak tasidigi bir zamani time.Time'a cevirir.
type ZamanDamgasi struct{ time.Time }

func (z *ZamanDamgasi) Scan(kaynak any) error {
	s, ok := kaynak.(string)
	if !ok {
		return fmt.Errorf("ZamanDamgasi.Scan: beklenmeyen kaynak tipi %T", kaynak)
	}
	ayristirilan, err := time.Parse(time.RFC3339, s)
	if err != nil {
		return err
	}
	z.Time = ayristirilan
	return nil
}

var satirVerisi = [][]driver.Value{
	{int64(1), "kalem", int64(1250), true, "2026-08-23T10:00:00Z"},
	{int64(2), "defter", int64(3990), false, "2026-08-20T09:30:00Z"},
}

type kuklaSurucu struct{}

func (kuklaSurucu) Open(ad string) (driver.Conn, error) { return &kuklaBaglanti{}, nil }

type kuklaBaglanti struct{}

func (kuklaBaglanti) Prepare(sorgu string) (driver.Stmt, error) {
	return nil, fmt.Errorf("kukla surucu: hazirlanmis ifade desteklenmiyor")
}
func (kuklaBaglanti) Close() error { return nil }
func (kuklaBaglanti) Begin() (driver.Tx, error) {
	return nil, fmt.Errorf("kukla surucu: islem desteklenmiyor")
}
func (kuklaBaglanti) Query(sorgu string, args []driver.Value) (driver.Rows, error) {
	return &kuklaSatirlar{veri: satirVerisi, konum: 0}, nil
}

type kuklaSatirlar struct {
	veri  [][]driver.Value
	konum int
}

func (kuklaSatirlar) Columns() []string {
	return []string{"id", "ad", "fiyat_kurus", "aktif", "eklenme"}
}
func (kuklaSatirlar) Close() error { return nil }
func (r *kuklaSatirlar) Next(dest []driver.Value) error {
	if r.konum >= len(r.veri) {
		return io.EOF
	}
	copy(dest, r.veri[r.konum])
	r.konum++
	return nil
}

func init() {
	sql.Register("kukla", kuklaSurucu{})
}
```

Bu dosya `func main` içermediği için diske yazılıyor ama çalıştırılmıyor; aşağıdaki ölçümler
onu kullanıyor. `kuklaSatirlar.Next` yöntemi, sürücünün gerçekte yaptığı şeyi taklit ediyor:
her satırı `driver.Value` diliminde döndürüyor ve `driver.Value` **kasıtlı olarak dar** bir
tip: yalnızca `int64`, `float64`, `bool`, `[]byte`, `string`, `time.Time` ve `nil` bu tipe
girebiliyor. Bu ders için önemli olan sonuç: bir sütunun kaynaktaki "gerçek" anlamı ne
olursa olsun (bir kullanıcı kimliği, bir para tutarı, bir zaman damgası), sürücü sınırını
geçerken bu yedi tipten birine **indirgeniyor**.

Bu darlık kasıtlı bir tasarım kararı. `database/sql` paketi genel bir sözleşme sunuyor —
her sürücü, hangi veritabanı ürününü konuşursa konuşsun, satırdan gelen değeri bu yedi tipten
birine dönüştürmek zorunda. Çağıran kod, hangi sürücüyle çalıştığını bilmeden aynı `Query` ve
`Scan` çağrılarını kullanabiliyor, çünkü sürücüler arasındaki fark tam olarak bu dar arayüzün
arkasında kalıyor. Bu ders, gerçek bir sürücünün yaptığı şeyi en aza indirgenmiş bir biçimde
tekrarlıyor: iki satırlık sabit bir veri, bağlantı açma girişiminde hiçbir gerçek işlem
yapmayan bir `Open` ve yalnızca elindeki diziyi sırayla döndüren bir `Next`.

#### Beş Sütun, Sürücünün Dar Tip Kümesi

**SS17.** — Beş sütunluk bir sorgu koşturuluyor; her sütun kendi Go tipine (`KullaniciID`,
`string`, `Tutar`, `bool`, `ZamanDamgasi`) taranıyor ve sonucun tipi `%T` ile okunuyor.

```go
// olcum.go — bes sutun kendi tipine taraniyor, indirgenen ve indirgenmeyen sutunlar ayriliyor
package main

import (
	"database/sql"
	"fmt"
)

func main() {
	db, _ := sql.Open("kukla", "bellek-ici")
	defer db.Close()

	satirlar, _ := db.Query("select id, ad, fiyat_kurus, aktif, eklenme from urunler")
	defer satirlar.Close()

	for satirlar.Next() {
		var id KullaniciID
		var ad string
		var fiyat Tutar
		var aktif bool
		var eklenme ZamanDamgasi
		if err := satirlar.Scan(&id, &ad, &fiyat, &aktif, &eklenme); err != nil {
			fmt.Println("tarama hatasi:", err)
			continue
		}
		fmt.Printf("id=%d(%T) ad=%s(%T) fiyat=%d(%T) aktif=%t(%T) eklenme=%s(%T)\n",
			id, id, ad, ad, fiyat, fiyat, aktif, aktif, eklenme.Format("2006-01-02"), eklenme)
	}
}
```

```
id=1(main.KullaniciID) ad=kalem(string) fiyat=1250(main.Tutar) aktif=true(bool) eklenme=2026-08-23(main.ZamanDamgasi)
id=2(main.KullaniciID) ad=defter(string) fiyat=3990(main.Tutar) aktif=false(bool) eklenme=2026-08-20(main.ZamanDamgasi)
```

Beş sütunun üçü — `id`, `fiyat_kurus`, `eklenme` — sürücüden geldiğinde sırasıyla `int64`,
`int64`, `string`; hiçbiri hedef tipin adını (`KullaniciID`, `Tutar`, `ZamanDamgasi`) taşımıyor.
Bu üç sütunun her biri, hedef değişkenin kendi `Scan` yöntemi tarafından **geri kuruluyor** —
`KullaniciID.Scan` ham `int64`'ü alıp kendi tipine dönüştürüyor, `ZamanDamgasi.Scan` ham dizgiyi
alıp `time.Parse` ile bir `time.Time`e çeviriyor. Öteki iki sütun — `ad`, `aktif` — sürücüden
zaten hedef tipiyle aynı biçimde geliyor (`string`, `bool`); onlar için özel bir `Scan`
yöntemine gerek yok, doğrudan atama yeterli. Sonuç: **beş sütunun üçü indirgeniyor ve taramayla
geri kuruluyor, ikisi zaten sürücünün doğal tipiyle örtüşüyor.**

Bu üç `Scan` yöntemi, konunun ölçü ekseninin en net "yeniden kuruluyor" örneği: sürücü hiçbir
yerde "bu bir kullanıcı kimliği" ya da "bu bir zaman" demiyor, yalnızca ham bir sayı ya da
dizgi veriyor. Geri kurmayı sağlayan tarif, sürücüde değil, çağıranın `Scan`e verdiği hedef
değişkenin **kendi tipinde ve kendi yönteminde** duruyor.

`ad` ve `aktif` sütunlarının hiçbir özel `Scan` yöntemine ihtiyaç duymaması bir rastlantı
değil: standart kitaplığın kendi dönüştürme mantığı, hedef değişkenin tipi sürücüden gelen
`driver.Value` tipiyle zaten örtüştüğünde, hiçbir ek adım gerektirmeden doğrudan atama
yapıyor. Özel bir `Scan` yöntemi yalnızca hedef tip sürücünün tanıdığı yedi tipin **dışında**
olduğunda devreye giriyor — `KullaniciID`, `Tutar` ve `ZamanDamgasi` bu yüzden kendi
yöntemlerini taşıyor, `string` ve `bool` taşımıyor. Bu, sürücünün dar tip kümesinin sınırının
tam olarak nerede çizildiğini gösteriyor: sınır sürücüde sabit, ama sınırın ötesine geçen her
hedef tip kendi geçiş kuralını kendisi yazmak zorunda.

#### Sınırlayıcı Ölçüm: Tarama Hedefin Tipine Bakar

Yukarıdaki `Scan` çağrısı, sütun sırasının hedef değişken sırasıyla eşleştiği varsayımına
dayanıyor. Bu eşleşme bozulduğunda — bir sütunun hedefi, o sütunun taşıdığı `driver.Value`
tipine uymadığında — sonuç bir derleme hatası değil, bir **çalışma zamanı** hatası.

**SS18.** — İkinci sütun (`ad`, bir `string`) yanlışlıkla `KullaniciID` tipinde bir hedefe
taranıyor; sorgunun kendisi hiç değişmiyor, yalnızca `Scan` çağrısındaki değişken sırası
karışıyor.

```go
// uyumsuz-hedef.go — sorgu ayni, yalniz Scan hedefi karisiyor; hata calisma zamaninda geliyor
package main

import (
	"database/sql"
	"fmt"
)

func main() {
	db, _ := sql.Open("kukla", "bellek-ici")
	defer db.Close()

	satirlar, _ := db.Query("select id, ad, fiyat_kurus, aktif, eklenme from urunler")
	defer satirlar.Close()

	satirlar.Next()
	var id KullaniciID
	var yanlisHedef KullaniciID // ikinci sutun (ad, bir dizgi) buraya taranmaya calisiliyor
	var fiyat Tutar
	var aktif bool
	var eklenme ZamanDamgasi
	hata := satirlar.Scan(&id, &yanlisHedef, &fiyat, &aktif, &eklenme)
	fmt.Println("uyumsuz hedefle tarama hata verdi mi:", hata != nil)
}
```

```
uyumsuz hedefle tarama hata verdi mi: true
```

Sorgu metni (`"select id, ad, ..."`) burada hiçbir şey söylemiyor — sütun adları doğru,
sorgu kukla sürücüden başarıyla dönüyor. Hata, yalnızca `Scan` çağrısı çalıştığında ortaya
çıkıyor: `yanlisHedef` bir `KullaniciID`, ama sürücüden gelen ikinci sütunun değeri bir
`string` (`"kalem"`) ve `KullaniciID.Scan` bunu bir `int64`e dönüştüremediği için hata
döndürüyor. Bu, konunun ölçü ekseninin bu derse özgü sınırı: tarama **hedefin** tipine
bakıyor, **kaynağın** tipine değil — sütunun kendisi hangi tipte olduğunu önceden ilan
etmiyor, yalnızca hedefe uyup uymadığı çalışma zamanında sınanıyor. Bir sorgu yanlış yazılmış
gibi görünmüyor; yalnızca `Scan` argümanlarının sırası kaymış ve bu kaymayı derleyici hiç
yakalayamıyor çünkü `Scan`in imzası `...any` — kaç ve hangi tipte argüman verildiği derleme
zamanında denetlenmiyor.

Bu, önceki dersteki günlük alanı okumasıyla aynı biçimde ortaya çıkan bir kusur. Orada da
yanlış bir tarifle (`basarili` alanını `int` diye okumak) hata çalışma zamanında geliyordu,
sorgu ya da günlük satırı yazılırken değil. Aradaki fark, sorgunun `select` listesi ile
`Scan`in argüman listesi arasındaki bağın **konuma** dayanması: iki liste arasında sütun
adına bakan hiçbir denetim yok, yalnızca sıra var — ve bu sıranın doğru olup olmadığı, ancak
program fiilen çalıştırıldığında sınanıyor.

#### Bağlantı Havuzu ve Kapatma Sorumluluğu

`sql.DB` bir tek bağlantı değil, bir **bağlantı havuzu**; havuzun büyüklüğü
`SetMaxOpenConns` ile sınırlanabiliyor. Bir sorgunun sonucu (`*sql.Rows`) kapatılana kadar,
o sorgunun kullandığı bağlantı havuza geri dönmüyor — kapatmayı unutmak, havuzu tıkıyor.

**SS19.** — Havuz tek bağlantıyla sınırlanıyor. İlk sorgunun sonucu **kasıtlı olarak**
kapatılmadan, aynı havuzdan ikinci bir sorgu (kısa bir zaman aşımıyla) deneniyor; sonra ilk
sonuç kapatılıp üçüncü bir sorgu deneniyor.

```go
// havuz.go — kapatilmayan bir satir kumesi tek baglantili havuzu tikiyor
package main

import (
	"context"
	"database/sql"
	"fmt"
)

func main() {
	db, _ := sql.Open("kukla", "bellek-ici")
	defer db.Close()
	db.SetMaxOpenConns(1)

	birinci, _ := db.Query("select id, ad, fiyat_kurus, aktif, eklenme from urunler")
	birinci.Next() // baglantiyi acik tutuyor, henuz kapatilmadi

	fmt.Println("birinci sorgudan sonra acik baglanti sayisi:", db.Stats().OpenConnections)

	baglamKisa, iptal := context.WithTimeout(context.Background(), 0)
	defer iptal()
	_, hataIkinci := db.QueryContext(baglamKisa, "select id, ad, fiyat_kurus, aktif, eklenme from urunler")
	fmt.Println("havuz doluyken ikinci sorgu basarisiz mi:", hataIkinci != nil)

	birinci.Close() // baglantiyi havuza geri birakir

	ucuncu, hataUcuncu := db.Query("select id, ad, fiyat_kurus, aktif, eklenme from urunler")
	fmt.Println("kapatmadan sonra sorgu basarili mi:", hataUcuncu == nil)
	if hataUcuncu == nil {
		defer ucuncu.Close()
	}
}
```

```
birinci sorgudan sonra acik baglanti sayisi: 1
havuz doluyken ikinci sorgu basarisiz mi: true
kapatmadan sonra sorgu basarili mi: true
```

Havuz tek bağlantıya sınırlandığında, ilk sorgunun sonucu kapatılmadan durduğu sürece o tek
bağlantı **meşgul** kalıyor. İkinci sorgu, süresi anında dolan bir bağlamla (`WithTimeout`
sıfır süreyle) deneniyor — gerçek bir süre ölçülmüyor, yalnızca "havuzda boş bağlantı yok"
durumunun hemen bir hata olarak dönmesi sağlanıyor. `birinci.Close()` çağrıldığında bağlantı
havuza geri dönüyor ve üçüncü sorgu sorunsuz çalışıyor. Bu, "kapatma sorumluluğu" ifadesinin
somut karşılığı: bir `*sql.Rows` değeri kapatılmadığı sürece, altındaki bağlantıyı **elinde
tutmaya devam ediyor** — havuzun büyüklüğü sınırlıysa, bu unutma diğer sorguları
tıkayabiliyor. Sürücünün kendisi (kukla ya da gerçek) burada rol oynamıyor; havuzun yönetimi
tamamen `database/sql` paketinin kendi sorumluluğunda.

Bu ölçüm havuzu bilerek tek bağlantıya indirdi ki tıkanma bir tek sorguyla görünür olsun;
gerçek boyuttaki bir havuzda aynı unutma birden fazla sorgu birikene kadar fark edilmeyebiliyor.
Kod içinde `defer satirlar.Close()` yazmak — `Query` çağrısından hemen sonra, satırların
kullanılmasından önce — bu unutmayı önlemenin standart yolu; bu ders boyunca kullanılan kalıp
da bu. Kapatmanın kendisi ölçülebilir bir sonuç üretmiyor, yalnızca bir sonraki sorgunun
başarılı olup olmamasını belirliyor — tam olarak bu ölçümün gösterdiği şey.

#### Özet

- Sürücünün taşıdığı tip kümesi (`driver.Value`) yalnızca yedi tiple sınırlı; bir sütunun
  "gerçek" anlamı ne olursa olsun, sürücü sınırını geçerken bu kümeye indirgeniyor.
- Beş sütunun üçü (`id`, `fiyat_kurus`, `eklenme`) hedef tipin kendi `Scan` yöntemiyle geri
  kuruluyor; ikisi (`ad`, `aktif`) zaten sürücünün doğal tipiyle örtüştüğü için doğrudan
  atanıyor.
- Sınırlayıcı ölçüm: tarama hedefin tipine bakıyor, kaynağın tipine değil — uyumsuz bir hedef
  sorgu yazılırken değil, `Scan` çağrıldığında, çalışma zamanında hata veriyor.
- `sql.DB` bir bağlantı havuzu yönetiyor; bir sorgu sonucu kapatılmadığı sürece altındaki
  bağlantıyı elinde tutmaya devam ediyor.
- Kapatılmayan bir satır kümesi, sınırlı büyüklükteki bir havuzda sonraki sorguları
  tıkayabiliyor; kapatmak bağlantıyı havuza geri bırakıyor.

#### Sonraki Adım

Bu derste tarama, hedefin tipine bakarak sürücünün dar tip kümesinden bir Go değerini geri
kurdu — tarif kodun kendisindeydi, `Scan`e verilen değişkenin tipinde. Konunun beş dersi
boyunca aynı soru beş ayrı yerde soruldu: dosya ve arabellekte, ikili dosyaya gömülen bir
kaynakta, metne dönen bir zaman değerinde, alan adlarını taşıyan bir günlük kaydında ve
şimdi bir veritabanı satırında. Beşinde de sınırı geçen değer kendi başına yeterli değildi;
geri kurmayı sağlayan şey her seferinde karşı tarafın elindeki bir tarifti.

Tarifin nerede durduğu her derste değişti. Gömülü kaynakta tarif derleme zamanındaydı — hedef
değişkenin kendi tipi. Zaman değerinde tarif çağıranın bildiği bir düzendi. Günlük kaydında
tarif okuyan kodun elle yazdığı bir dönüşümdü. Bu derste tarif, hedef tipin kendi `Scan`
yöntemiydi — bir sözleşme olarak, standart kitaplığın kendisine bağlanmış. Ortak nokta hep
aynı: sınırın kendisi hiçbir zaman tip bilgisini taşımadı, taşıyan şey her defasında sınırın
bu tarafında duran bir bilgiydi.

Sıradaki ders bu konunun kapanışı ve aynı soruyu programın **girişine** taşıyor: bir komut
satırı argümanı da, tıpkı bir günlük satırı ya da bir veritabanı sütunu gibi, dizgi olarak
geliyor. Programın kendi ayrıştırıcısı bu dizgiden hangi tipleri, ne kadarını geri kurabiliyor?

### Komut Satırı Uygulamaları

Source: https://academia.sh/tr/kurslar/go-uygulamalar/komut-satiri-uygulamalari

Kursun ve konunun son dersi. Bir bayrak ayrıştırıcısına elle verilen dizgi dizisinden tam sayı, mantıksal, süre ve dizgi tiplerinin geri kurulması; tanınmayan bir bayrağın ayrıştırmayı durdurduğu; alt komut yapısında her alt komutun kendi bayrak kümesini taşıdığı koşturularak ölçülüyor. Sınırlayıcı ölçüm: ayrıştırıcı beklenen tipi geri kurur, değerin anlamını değil — negatif bir adet hiçbir hata üretmeden geçiyor. Kurs kapanışında on iki dersin ölçtüğü sınır geçişleri tek bir tabloda toparlanıyor.

Önceki ders bir veritabanı satırının sürücünün dar tip kümesinden hedef değişkenin kendi `Scan`
yöntemiyle geri kurulduğunu gösterdi ve konunun beş dersini kapattı: dosya ve arabellek, ikili
dosyaya gömülen bir kaynak, metne dönen bir zaman değeri, alan adlarını taşıyan bir günlük kaydı,
bir veritabanı satırı — beşinde de sınırı geçen değer kendi başına yeterli değildi; geri kurmayı
sağlayan şey her seferinde karşı tarafın elindeki bir tarifti. Bu ders aynı soruyu programın kendi
**girişine** taşıyor: bir komut satırı bayrağı da, tıpkı bir günlük satırı ya da bir veritabanı
sütunu gibi, programa dizgi olarak giriyor. Kabuk Programlama kursu (M03/K02) bu argümanların
kabuk tarafında nasıl kurulduğunu ve metnin nasıl işlendiğini zaten kurdu; bu ders onu tekrar
etmiyor, yalnız argümanın programın eline geçtiği noktadan devam ediyor — dizgi elde tutulduğunda,
tipi kim, hangi tarifle geri kuruyor?

Yanıt bu kursun standart kitaplığındaki **komut satırı bayrağı** ayrıştırıcısında duruyor.
Bu ders boyunca yalnız kısaca "bayrak" deniyor, ama kastedilen hep bu: bir çağrının önüne
`-` ile eklenen, dizgi olarak gelen bir komut satırı seçeneği. Ayrıştırıcı bir
dizgi dizisini alıp, kaynakta önceden bildirilmiş bayrak adlarına göre, her bayrağın beklediği
tipte bir değer üretiyor — tarif burada da karşı tarafın elinde, ama bu kez karşı taraf ağın
ötesi ya da bir sürücü değil, kaynağın kendisinde `kume.Int`, `kume.Bool`, `kume.String` gibi
çağrılarla yazılmış bildirimler. Bu ders üç şeyi ölçüyor: ayrıştırıcının dört tipi (tam sayı,
mantıksal, süre, dizgi) dizgiden nasıl geri kurduğunu, tanınmayan bir bayrağın ayrıştırmayı
nerede durdurduğunu ve alt komut yapısının her alt komuta kendi bayrak kümesini nasıl
verdiğini. **Gerçek argüman listesi (`os.Args`) hiçbir yerde okunmuyor** — bayrak kümesine
her seferinde elle kurulmuş bir dizgi dizisi veriliyor, tıpkı önceki derste kukla sürücüye
elle kurulmuş bir satır verildiği gibi. Konunun son ve kursun kapanış dersi olarak bu ders
ayrıca on iki dersin ölçtüğü sınır geçişlerini tek bir tabloda topluyor.

#### Bayrak Ayrıştırıcının Dizgiden Geri Kurduğu Dört Tip

Standart kitaplığın bayrak paketi, tek bir küresel bayrak kümesi (`flag.CommandLine`) yanında,
kendi bayrak kümesini kurmayı da destekliyor: `flag.NewFlagSet` bağımsız bir küme döndürüyor
ve bu küme `os.Args`e hiç dokunmadan, kendisine verilen herhangi bir dizgi dilimini
ayrıştırabiliyor. Bu ders boyunca yalnız bu bağımsız kümeler kullanılıyor — bir kısıt değil,
gerçek kaynak yasağının doğal bir sonucu: `os.Args` koşumun kendi ortamına bağlı bir kaynak,
bu yüzden ölçüm ona hiç dokunmuyor.

**SS20.** — Dört bayrak (tam sayı, mantıksal, süre, dizgi) bir bayrak kümesine bildiriliyor;
elle kurulmuş bir dizgi dizisi ayrıştırılıp her bayrağın geri kurduğu değerin tipi `%T` ile
okunuyor.

```go
// tipler.go — bayrak ayristiricinin dizgiden geri kurdugu dort tip
package main

import (
	"flag"
	"fmt"
	"io"
)

func main() {
	kume := flag.NewFlagSet("olcum", flag.ContinueOnError)
	kume.SetOutput(io.Discard)

	adet := kume.Int("adet", 0, "adet")
	kalici := kume.Bool("kalici", false, "kalici mi")
	bekleme := kume.Duration("bekleme", 0, "bekleme suresi")
	ad := kume.String("ad", "", "ad")

	girdi := []string{"-adet", "12", "-kalici", "-bekleme", "1h30m", "-ad", "vida"}
	kume.Parse(girdi)

	fmt.Println("-- dizgiden geri kurulan dort tip --")
	fmt.Printf("%-8s kaynak=%-7q deger=%-8v tip=%T\n", "adet", "12", *adet, *adet)
	fmt.Printf("%-8s kaynak=%-7q deger=%-8v tip=%T\n", "kalici", "(var)", *kalici, *kalici)
	fmt.Printf("%-8s kaynak=%-7q deger=%-8v tip=%T\n", "bekleme", "1h30m", *bekleme, *bekleme)
	fmt.Printf("%-8s kaynak=%-7q deger=%-8v tip=%T\n", "ad", "vida", *ad, *ad)
}
```

```
-- dizgiden geri kurulan dort tip --
adet     kaynak="12"    deger=12       tip=int
kalici   kaynak="(var)" deger=true     tip=bool
bekleme  kaynak="1h30m" deger=1h30m0s  tip=time.Duration
ad       kaynak="vida"  deger=vida     tip=string
```

Dört bayrağın dördü de kaynakta **dizgi olarak** duruyor — `girdi` dilimindeki her eleman bir
`string`, başka bir şey değil. `kume.Int`in döndürdüğü `*int` işaretçisinin arkasındaki değer
`"12"` dizgisinden ayrıştırılan bir tam sayı; `kume.Bool` bir bayrağın **varlığını** (değer
yazılmadan yalnız adının geçmesini) `true` olarak okuyor; `kume.Duration` `"1h30m"` dizgisini
`time.Duration` tipinde, saat ve dakikaya ayrıştırılmış bir değere çeviriyor; `kume.String` ise
hiçbir dönüşüm yapmadan aynı dizgiyi geri veriyor. Dördü de kendi tipini `%T` ile doğruluyor —
ayrıştırıcı burada tam olarak Veritabanı Erişimi dersindeki `Scan` yönteminin yaptığı şeyi
yapıyor: kaynakta tip bilgisi taşımayan bir veriyi (orada sürücünün dar tip kümesi, burada çıplak
dizgi), hedefte önceden bildirilmiş bir tarifle bir Go tipine geri kuruyor.

Bu geri kurma otomatik değil, **bildirime bağlı**. `kume.Int("adet", 0, "adet")` çağrısı
ayrıştırıcıya "`adet` adlı bayrak bir tam sayı taşıyacak" diye önceden söylüyor; bu bildirim
olmasaydı ayrıştırıcının `"12"` dizgisini bir tam sayı mı, bir dizgi mi okuyacağını bilecek
hiçbir yolu yoktu. Tıpkı yapı etiketinin bir JSON alanının adını taşıması gibi, burada da
tarif kaynağın kendisinde değil, ayrıştırıcıyı kuran kodun kendisinde duruyor.

#### Tanınmayan Bayrağın Ne Yaptığı

Bir bayrak kümesi yalnızca kendisine önceden bildirilen adları tanıyor. Dizgi dizisinde
bildirilmemiş bir ad geçtiğinde ayrıştırıcı bunu sessizce yok saymıyor — ayrıştırmayı orada
**durduruyor** ve bir hata döndürüyor.

**SS21.** — Bilinen bir bayraktan sonra tanınmayan bir bayrak, ondan sonra da bilinen ikinci
bir bayrak geçiyor; ayrıştırma tanınmayan bayrakta durduğu için ikinci bilinen bayrağın hiç
okunmadığı ölçülüyor.

```go
// bilinmeyen.go — taninmayan bir bayrak ayristirmayi durduruyor, sonrasi hic okunmuyor
package main

import (
	"flag"
	"fmt"
	"io"
)

func main() {
	kume := flag.NewFlagSet("olcum", flag.ContinueOnError)
	kume.SetOutput(io.Discard)

	adet := kume.Int("adet", 0, "adet")
	ad := kume.String("ad", "baslangic", "ad")

	girdi := []string{"-adet", "5", "-bilinmeyen", "x", "-ad", "vida"}
	hata := kume.Parse(girdi)

	fmt.Println("-- taninmayan bayragin ne yaptigi --")
	fmt.Println("ayristirma hata dondurdu mu:", hata != nil)
	fmt.Println("adet (taninmayandan once okundu):", *adet)
	fmt.Println("ad (taninmayandan sonra, hic okunmadi):", *ad)
}
```

```
-- taninmayan bayragin ne yaptigi --
ayristirma hata dondurdu mu: true
adet (taninmayandan once okundu): 5
ad (taninmayandan sonra, hic okunmadi): baslangic
```

`adet` bayrağı `-bilinmeyen`den **önce** geçtiği için ayrıştırıcı onu sorunsuz okuyup `5`e atıyor.
`-bilinmeyen` bayrağına geldiğinde ise `kume`de bu adla bildirilmiş hiçbir bayrak olmadığını
görüyor ve `Parse` hemen bir hatayla dönüyor — dizgi dizisinin geri kalanı (`-ad vida`) hiç
işlenmiyor; `ad` kendi varsayılan değerinde (`"baslangic"`) kalıyor. Bu, Go'nun kendi dağıtımının
çoğu komutunda görülen davranış: ayrıştırma bir bütün olarak başarılı ya da baştan sona hiç
tamamlanmamış olarak sonuçlanmıyor, **kısmi** kalıyor — durma noktasından önceki bayraklar okunmuş
oluyor; sonrasındakiler hiç görülmüyor.

`kume.SetOutput(io.Discard)` çağrısı burada da aynı gerekçeyle duruyor: `flag.ContinueOnError`
modunda ayrıştırıcı normalde hata metnini kendi yazdığı bir akışa basıyor ve bu metin hangi
bayrağın tanınmadığını içeriyor — sürüme bağlı bir biçimde. Ölçülen tek şey `hata != nil`
sonucu; hata metninin kendisi hiçbir yere yazılmıyor.

#### Alt Komut Yapısı: Her Alt Komutun Kendi Bayrak Kümesi

Tek bir bayrak kümesi tek bir düz komut için yeterli, ama birden çok alt işlevi olan bir
araç genelde **alt komut** yapısını kullanıyor — ilk argüman bir işlevin adı, geri kalanı o
işleve özgü bayraklar. Bu yapının kendisi dilin bir özelliği değil, `flag.NewFlagSet`in
bağımsız kümeler döndürebilmesinin doğal bir sonucu: her alt komuda kendi kümesi veriliyor
ve bir kümede bildirilen bir bayrak ötekinde hiç var olmuyor.

**SS22.** — İki alt komut (`ekle`, `listele`) kendi bayrak kümesiyle çağrılıyor; sonra
`ekle`nin kendi bayrağı (`-adet`) `listele`nin kümesine verilip tanınmadığı ölçülüyor.

```go
// altkomut.go — her alt komut kendi bayrak kumesini tasiyor, biri otekinde taninmiyor
package main

import (
	"flag"
	"fmt"
	"io"
)

func main() {
	ekleKumesi := flag.NewFlagSet("ekle", flag.ContinueOnError)
	ekleKumesi.SetOutput(io.Discard)
	ekleAd := ekleKumesi.String("ad", "", "urun adi")
	ekleAdet := ekleKumesi.Int("adet", 1, "adet")

	listeleKumesi := flag.NewFlagSet("listele", flag.ContinueOnError)
	listeleKumesi.SetOutput(io.Discard)
	listeleFiltre := listeleKumesi.String("filtre", "", "filtre")

	komutlar := map[string]*flag.FlagSet{"ekle": ekleKumesi, "listele": listeleKumesi}

	girdi1 := []string{"ekle", "-ad", "vida", "-adet", "20"}
	komutlar[girdi1[0]].Parse(girdi1[1:])
	fmt.Println("-- alt komut: ekle, kendi bayrak kumesi --")
	fmt.Printf("ad=%s adet=%d\n", *ekleAd, *ekleAdet)

	girdi2 := []string{"listele", "-filtre", "aktif"}
	komutlar[girdi2[0]].Parse(girdi2[1:])
	fmt.Println("-- alt komut: listele, kendi bayrak kumesi --")
	fmt.Printf("filtre=%s\n", *listeleFiltre)

	girdi3 := []string{"listele", "-adet", "5"}
	hata3 := komutlar[girdi3[0]].Parse(girdi3[1:])
	fmt.Println("-- listele kendi kumesinde 'adet' bayragini hic tanimiyor --")
	fmt.Println("hata verdi mi:", hata3 != nil)
}
```

```
-- alt komut: ekle, kendi bayrak kumesi --
ad=vida adet=20
-- alt komut: listele, kendi bayrak kumesi --
filtre=aktif
-- listele kendi kumesinde 'adet' bayragini hic tanimiyor --
hata verdi mi: true
```

Alt komut adının kendisi (`"ekle"`, `"listele"`) hiçbir bayrak kümesinin parçası değil —
elle kurulmuş dizginin **ilk** elemanı, ayrıştırıcıya hiç verilmeden, çağrılacak kümeyi
seçmek için kullanılıyor; ayrıştırıcının gördüğü yalnızca dizginin geri kalanı
(`girdi1[1:]`). `ekle` kümesi `-ad` ve `-adet` bayraklarını tanıyor, `listele` kümesi yalnız
`-filtre`yi. Üçüncü çağrı bu ayrımı doğruluyor: `-adet` bayrağı `ekle`nin kümesinde geçerli
bir bayrak, ama `listele`nin kümesine verildiğinde ayrıştırıcı onu hiç tanımıyor ve
`bilinmeyen` bölümündeki aynı hatayı döndürüyor. İki küme aynı pakette, aynı `main`
fonksiyonunda tanımlanmış olsa da birbirinden tamamen bağımsız — biri ötekinin bildirdiği
hiçbir bayrağı miras almıyor.

#### Sınırlayıcı Ölçüm: Tip Geri Kurulur, Anlam Geri Kurulmaz

Önceki üç ölçüm ayrıştırıcının bir dizgiden **beklediği tipi** doğru geri kurduğunu gösterdi.
Ama "beklenen tip" ile "doğru değer" aynı şey değil: ayrıştırıcı bir dizginin sözdizimsel
olarak o tipe uyup uymadığına bakıyor, değerin anlamlı olup olmadığına hiç bakmıyor.

**SS23.** — `adet` bayrağına negatif bir dizgi (`"-3"`) veriliyor; ayrıştırıcı bunu geçerli
bir tam sayı olarak kabul edip hiçbir hata döndürmüyor.

```go
// anlam.go — ayristirici beklenen tipi geri kurur, degerin anlamini degil
package main

import (
	"flag"
	"fmt"
	"io"
)

func main() {
	kume := flag.NewFlagSet("olcum", flag.ContinueOnError)
	kume.SetOutput(io.Discard)
	adet := kume.Int("adet", 0, "siparis adedi")

	girdi := []string{"-adet", "-3"}
	hata := kume.Parse(girdi)

	fmt.Println("-- ayristirici beklenen tipi geri kurar, degerin anlamini degil --")
	fmt.Println("ayristirma hata verdi mi:", hata != nil)
	fmt.Printf("adet=%d tip=%T\n", *adet, *adet)
}
```

```
-- ayristirici beklenen tipi geri kurar, degerin anlamini degil --
ayristirma hata verdi mi: false
adet=-3 tip=int
```

`-3` dizgisi bir tam sayının sözdizimine tastamam uyuyor — işaret, ardından rakamlar —
bu yüzden `kume.Int`in ayrıştırma mantığı onu sorunsuz kabul ediyor ve `adet` değişkeni
`-3` değerini, `int` tipinde, hiçbir hata olmadan alıyor. Ama bir sipariş adedinin negatif
olması alanın **anlamı** açısından geçersiz; bu geçersizliği ayrıştırıcı hiç bilmiyor, çünkü
ayrıştırıcının tek işi dizgiyi bildirilen tipe **çevirmek**, o tipin değer aralığını
sınırlı tutan bir kural uygulamak değil. Aynı durum önceki derslerde de görüldü: yapı etiketi
bir alanın adını taşıyordu ama değerin geçerliliğini denetlemiyordu, sürücünün taraması bir
sütunun tipini geri kuruyordu ama içindeki sayının anlamlı olup olmadığını sormuyordu. Bu
ders o örüntüyü kapatıyor — geçerli bir aralık denetimi isteniyorsa, bu denetim ayrıştırıcının
işi değil, ayrıştırılan değeri kullanan kodun kendi işi.

#### Kurs Kapanışı

On iki ders boyunca bu kursun tek sorusu şuydu: bir değer sınırı geçerken tip bilgisine ne
oluyor? Ortak tanımın üç iddiası bu soruya üç yanıt verdi. Birincisi: sınırı geçen tip
bilgisi kural olarak kayboluyor — ortak ölçümde on üç geçişin yedisinde karşı tarafa yalnız
bayt ya da metin ulaştı, programın içindeki bir tip sınırın ötesinde bir sayı dizisine ya da
bir satıra indirgendi. İkincisi: kaybolan bilgi geri kurulabiliyor, ama bunu yapan şey her
seferinde tarifi tutan taraf oldu — yapı etiketi, hedef tipin kendisi, ayrıştırıcının
beklediği tip, taramanın indirgediği tip; ortak ölçümde beş geçiş bu yolla yeniden kuruldu.
Üçüncüsü, bu kursa özgü olanı: tip bilgisinin korunduğu tek geçiş, iki tarafın **aynı
tarifi paylaştığı** geçişti — Sözleşme Temelli Servis İletişimi dersi bunu on üç geçişin
yalnız birinde gösterdi.

On iki dersin kendi tablosu, ortak ölçümün on üç geçişiyle birebir aynı çözünürlükte değil:
JSON Dizileştirme dersi tek başına dört alt geçiş ölçtü, bu ders de dört tipi tek derste
ölçtü. Yön yine de aynı — on iki satırın beşi `kayboluyor`, altısı `yeniden kuruluyor`, biri
`korunuyor` sınıfına düştü; çoğunluk hâlâ kayıp ya da tarifle geri kurma tarafında, koruma
yine tek bir satırda kaldı. Sınırlayıcı okuma her derste bir biçimde tekrarlandı: "geri
kuruldu" doğru kuruldu, ya da anlamlı bir değer geldi, demek değil. Bir sürücünün taşıdığı
tip, bir yapı etiketinin taşıdığı alan adı, bir bayrak ayrıştırıcının geri kurduğu tam sayı —
hepsi doğru **tipi** verdi, hiçbiri kaynağın niyet ettiği **anlamı** garanti etmedi.

Kurs aşırı bir genelleme yapmadan kapanıyor. Tip sistemi yalnız sınırın **bu tarafında**
çalışıyor; sınırın öbür tarafında — ağda, diskte, bir sürücüde, bir dizgide — tip diye bir
şey yok, yalnız bayt ve metin var. Geri kurma tarifi her zaman **tutan tarafın** işi ve o
tarif yanlış ya da eksik olduğunda hiçbir uyarı gelmiyor: alıcıda karşılığı olmayan bir alan
sessizce düşüyor, yanlış bir hedefe tarama çalışma zamanında hata veriyor, negatif bir adet
hiç sorgulanmadan geçiyor. Koruma, en güçlü sonuç, yalnız iki tarafın **aynı** tarifi
paylaşmasına bağlı — ve bu paylaşımın kendisi kursun hiçbir dersinde otomatik değildi, hep
ayrıca kurulması gereken bir şeydi.

| Ders | Ölçülen geçiş | Sonucu | Sınırlayıcı ölçüm |
|---|---|---|---|
| HTTP Sunucusu | İsteğin yolu, üstbilgisi ve gövdesinin tip bilgisi taşıyıp taşımadığı | kayboluyor | İçerik tipi üstbilgisi bir ipucu, tip değil — yanlış ipucu sunucuda hiçbir hata üretmiyor |
| HTTP İstemcisi | Yanıt gövdesinin istemci tarafında okunması ve kapatılması | kayboluyor | Gövde bir okuyucu, bir değer değil — ikinci okuma hatasız boş dönüyor |
| JSON Dizileştirme | Yapı etiketiyle işaretli alanların JSON gidiş-dönüşü | yeniden kuruluyor | "Geri kuruldu" doğru kuruldu demek değil — karşılıksız alan sessizce düşüyor |
| Şablonlar | Aynı yapının iki şablondan geçirilmiş çıktısı | kayboluyor | Alan adı çalışma zamanında aranıyor — olmayan alan hatası çıktının ortasında doğuyor |
| Sözleşme Temelli Servis İletişimi | İki sürecin paylaştığı tarifle üretilen kaynağın alanları | korunuyor | Koruma tarifin dağıtımına bağlı — tarif ayrışırsa geçiş sessizce kayboluyora düşüyor |
| Gerçek Zamanlı İletişim | Uzun süreli bağlantıda itilen mesajın gövdesi | kayboluyor | Çerçeveleme mesajın nerede bittiğini veriyor, içindeki değerin tipini vermiyor |
| Dosya ve Girdi/Çıktı Arayüzleri | Dört kaynağın (dosya, arabellek, dizgi, ağ gövdesi) aynı okuyucudan geçmesi | kayboluyor | Kısmi okuma ile dosya sonu ayrı adımlar — ayrımı yönetmek çağıranın işi |
| Gömülü Kaynaklar | Kaynak dosyanın üç Go tipine derleme zamanında bağlanması | yeniden kuruluyor | İkili, kaynak dosya silindikten sonra da aynı içeriği üretiyor — bağ derleme zamanında kuruldu |
| Zaman İşlemleri | Bir anın metne dönüp geri gelmesi | yeniden kuruluyor | Ofset yeniden kuruluyor ama bölge adı taşınmıyor — `Equal` eşit, `==` ve `String()` farklı |
| Yapılandırılmış Günlükleme | Aynı olayın düz metin ve anahtar-değer biçiminde yazılması | yeniden kuruluyor | Alan adı korunuyor, tip korunmuyor — geri kurma okuyanın kendi tarifine bağlı |
| Veritabanı Erişimi | Sürücüden gelen beş sütunun hedef tipe taranması | yeniden kuruluyor | Tarama hedefin tipine bakıyor, kaynağın tipine değil — uyumsuz hedef çalışma zamanında hata veriyor |
| Komut Satırı Uygulamaları | Bayrak kümesine verilen dizginin dört tipe ayrıştırılması | yeniden kuruluyor | Ayrıştırıcı beklenen tipi geri kuruyor, değerin anlamını değil — negatif adet hatasız geçiyor |

#### Özet

- Bayrak ayrıştırıcısı, elle kurulmuş bir dizgi dizisinden dört tipi (tam sayı, mantıksal,
  süre, dizgi) önceden bildirilen bayrak tanımlarına göre geri kurdu; `os.Args` hiçbir yerde
  okunmadı.
- Tanınmayan bir bayrak ayrıştırmayı olduğu noktada durdurdu: durma noktasından önceki
  bayraklar okundu, sonrasındakiler hiç işlenmedi.
- Alt komut yapısında her alt komut kendi bağımsız bayrak kümesini taşıdı; bir kümede
  bildirilen bir bayrak ötekinde hiç tanınmadı.
- Sınırlayıcı ölçüm: ayrıştırıcı bir dizginin beklenen tipe sözdizimsel olarak uyup
  uymadığına baktı, değerin anlamlı olup olmadığına bakmadı — negatif bir adet hatasız geçti.
- Kurs on iki ders boyunca aynı soruyu on iki ayrı sınırda sordu ve üç sonuçtan birine
  yazdı; çoğunluk kayboluyor ya da yeniden kuruluyor sınıfında kaldı, koruma yalnız iki
  tarafın aynı tarifi paylaştığı tek bir geçişte görüldü.
- Kurs on iki dersin ölçtüğü yanıtları tek bir tabloda topladı; her satır kendi kaynak
  dersinden okundu.

