---
title: 'Modül Yayımlama'
source: 'https://academia.sh/tr/kurslar/go-uretim/modul-yayimlama'
course: 'Üretim Go: Test, Araçlar ve Başarım'
language: tr
updated: '2026-09-13T05:08:46+00:00'
license: 'CC BY-SA 4.0'
objectives:
    - 'Bir sürüm değişikliğinin uyumlu mu uyumsuz mu olduğunu, dışa aktarılan işlevlerin imzalarını karşılaştırarak ölçer'
    - 'İmza karşılaştırmasının verdiği kararı, eski bir çağıranı yeni sürümle gerçekten derleterek doğrular'
    - 'Uyumluluk sözünün imzalar üzerine kurulu olduğunu, aynı imzayı taşıyan iki farklı davranışla sınırlar'
---

# Modül Yayımlama

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

import "fmt"

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

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

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

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

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

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

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

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

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

## Gerçek Bir Derlemeyle Doğrulama

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

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

import "fmt"

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

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

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

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

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

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

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

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

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

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

import "fmt"

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

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

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

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

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

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

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

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

## Özet

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

## Sonraki Adım

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