---
title: 'Ruby Ekosistemi ve Üretim'
source: 'https://academia.sh/tr/kurslar/ruby-uretim'
language: tr
updated: '2026-10-21T05:03:43+00:00'
license: 'CC BY-SA 4.0'
lessons: 9
outcomes:
    - 'Bir paketi tanımı, sürümü ve bağımlılıklarıyla kurar, sürüm kısıtlarının hangi sürümleri kabul ettiğini ölçer'
    - 'Kilit dosyasının kurulan sürümleri nasıl sabitlediğini ve kilitsiz çözümün neye bağlı kaldığını açıklar'
    - 'Testlerin sıraya, saate ve paylaşılan duruma bağımlılığını ölçer, yalıtım ve sahtelemeyle giderir'
    - 'Eşzamanlılık modelinin sınırlarını iş parçacığı, süreç ve Ractor üzerinden ayırt eder'
    - 'Üretimde bellek ve başarım sorunlarını nesne sayımı ve çağrı sayımıyla tanılar, bir yayını geri alınabilir biçimde dağıtır'
    - 'Bir sonucu kodun mu, bağımlılığın mı, sıranın mı, ortamın mı belirlediğini ölçerek ayırt eder'
---

# Ruby Ekosistemi ve Üretim

Geliştiricinin makinesinde doğru çalışan bir program üretimde başka bir sonuç verebilir: kod aynıdır, değişen kurulan paket sürümü, testlerin ya da iş parçacıklarının koştuğu sıra, sunucunun saati ya da bir ortam değişkenidir. Kurs her düzeneği tek bir soruyla ölçer — bu sonucu ne belirliyor? Paket yapısı, bağımlılık yönetimi, test çerçeveleri, sahteleme, eşzamanlılık modeli, süreç tabanlı ölçekleme, bellek yönetimi, profil çıkarma ve dağıtım aynı çerçeveden geçirilir.

## Paketler ve Test

Bir paketin tanımı, sürümü ve dosya adı, sürüm kısıtları ve kilit dosyası, tohumla karıştırılan test sırası ve paylaşılan durum, saati ve iş birlikçileri dışarıdan alan testler, sahte nesneler ve tekrarlanabilir test verisi.

### Gem Yapısı ve Yayımlama

Source: https://academia.sh/tr/kurslar/ruby-uretim/gem-yapisi-ve-yayimlama

Bu kurs bir sonucu neyin belirlediğini sorar. Ders dokuz gözlemlik ölçüm çekirdeğini tanıtır, sonra paket tanımını, dosya adının kaynağını, bağımlılık türlerini ve sürüm karşılaştırmasını ölçer.

Önceki kurs bir değerin ne kadar kaldığını ölçtü ve kapanışında testlerin yalıtımı göründü: iki test aynı uygulamayı paylaşınca kalıcı depo aralarında sızıyordu. Uygulama geliştiricinin makinesinden çıkıp üretime gidince yeni bir soru doğar: kod aynıyken sonuç neden değişiyor? Bu kursun sorusu **bu sonucu ne belirliyor?** sorusudur. Kural, sonucun davranışını okur; kodun nasıl yazıldığını okumaz.

M08/K06'da da bir sonucun sebebi arandı, ama orada sebep kaynaktaki bir çağrı, bir açıklama ya da bir varsayılandı; hepsi kodun içindeydi. Burada kodun **dışındaki** girdi sorulur. Paket tanımı kaynakta yazılıdır ve koddur; kurulan sürüm kaynakta yazılı değildir.

Kural bir sonucu dört sınıftan birine koyar. **Kod**, hesabın yalnız kendi girdisine baktığı durumdur. **Bağımlılık**, kilit dosyasındaki sürümlerin sabitlediği şeydir. **Sıra**, testlerin ya da iş parçacıklarının hangi sırayla koştuğudur. **Ortam**, makinenin söylediğidir: saat, çekirdek sayısı, ortam değişkeni. Bu dört sınıfa **belirleyen** denir. Ölçüm modeli üç girdiden kurulur: `kilit`, `tohum` ve `ortam`. Bu üçünün dışında kalan her şey koddur.

#### Ölçüm Çekirdeği

Çekirdek iki dosyadır ve kurs boyunca yeniden yazılmaz, kesiti alınır. `belirleyen.rb` koşum modelini ve sınıflandıran yardımcıyı taşır; blok kaynaktır, çıktısı yoktur.

```ruby
# belirleyen.rb — bir kosum modeli ve bir sonucu neyin belirledigini siniflandiran yardimci

# Kosum: sonucun bagli olabilecegi uc girdi. Bu uc girdinin disinda kalan her sey koddur.
Kosum = Data.define(:kilit, :tohum, :ortam)

TABAN = Kosum.new(
  kilit: { "bicimleyici" => "1.4.2", "ayristirici" => "2.0.1" },
  tohum: 1,
  ortam: { saat: Time.utc(2026, 10, 16, 9, 0), cekirdek: 4, degiskenler: {} }
)

# Her degisken tek bir girdiyi degistirir; otekiler tabandaki gibi kalir.
DEGISKENLER = {
  bagimlilik: TABAN.with(kilit: { "bicimleyici" => "1.5.0", "ayristirici" => "2.1.0" }),
  sira: TABAN.with(tohum: 2),
  ortam: TABAN.with(ortam: { saat: Time.utc(2027, 3, 1, 9, 0), cekirdek: 8,
                             degiskenler: { "ISCI_SAYISI" => "3" } })
}

# Kural: hesap once tabanda, sonra her degiskende bir kez kosar. Sonucu degistiren ilk
# degisken (bagimlilik, sira, ortam sirasiyla) belirleyendir; hicbiri degistirmiyorsa kod.
def belirleyen(hesap)
  taban = hesap.call(TABAN)
  degisti = DEGISKENLER.to_h { |ad, kosum| [ad, hesap.call(kosum) != taban] }
  tur = degisti.key(true)&.to_s || "kod"
  kanit = degisti.map { |ad, d| "#{ad[0].upcase}#{d ? '+' : '-'}" }.join(" ")
  [tur, kanit, taban]
end
```

`Kosum` üç girdiyi tutar. `TABAN` bir başlangıç koşumudur; `DEGISKENLER` her seferinde yalnız bir girdiyi değiştirir. `belirleyen(hesap)` hesabı tabanda ve üç değişkende koşturur ve sonucu değiştiren ilk değişkenin adını verir; hiçbiri değiştirmiyorsa sonuç `"kod"`dur. Kanıt sütunu `B± S± O±` biçimindedir: bağımlılık, sıra ve ortam değişkeninde sonucun değişip değişmediği. İkinci dosya, ekosistemin küçük modellerini ve dokuz gözlemi taşır.

```ruby
# gozlemler.rb — dokuz gozlem: bu sonucu ne belirliyor
require "digest"
require_relative "belirleyen"

# Paket tanimi: ad ve surum paketin dosya adini verir.
def paket_tanimi(dosyalar = ["lib/olcer.rb", "lib/olcer/sayac.rb"])
  Gem::Specification.new do |t|
    t.name = "olcer"
    t.version = "0.3.0"
    t.summary = "kucuk bir olcum paketi"
    t.authors = ["ornek yazar"]
    t.files = dosyalar
    t.add_dependency "bicimleyici", "~> 1.4"
  end
end

# Bagimlilik cozumu: kilitteki surum kisiti karsiliyorsa o surum kurulur.
def kurulan_surum(kosum, ad, kisit)
  surum = Gem::Version.new(kosum.kilit.fetch(ad))
  raise ArgumentError, "kilit kisiti karsilamiyor" unless Gem::Requirement.new(kisit).satisfied_by?(surum)

  surum.to_s
end

# Test kosucusu: testler tohumla karistirilan sirada kosar ve ayni ayarlari paylasir.
TESTLER = {
  "fiyat_bicimi" => ->(ayar) { "12 #{ayar[:para_birimi]}" == "12 TRY" },
  "doviz_cevirimi" => ->(ayar) { ayar[:para_birimi] = "USD"; true },
  "fatura_toplami" => ->(ayar) { ayar[:para_birimi] == "TRY" }
}

def test_kos(kosum, testler = TESTLER)
  ayar = { para_birimi: "TRY" }
  sira = testler.keys.shuffle(random: Random.new(kosum.tohum))
  sira.reject { |ad| testler[ad].call(ayar) }
end

# Sahteleme: deneme suresi 30 gundur; saat disaridan verilir.
def deneme_suresinde?(baslangic, saat) = saat - baslangic < 30 * 86_400

# Zamanlayici modeli: iki is parcacigi sayaci okur ve bir fazlasini yazar; okuma ile yazma
# arasinda gecis olabilir. Siradaki adimi hangi is parcacigin atacagini tohum secer.
def kilitsiz_sayac(kosum, artis: 3)
  rastgele = Random.new(kosum.tohum)
  sayac = 0
  isler = Array.new(2) { { kalan: artis, okunan: nil } }
  loop do
    bekleyen = isler.select { |i| i[:kalan].positive? }
    break if bekleyen.empty?

    is = bekleyen.sample(random: rastgele)
    if is[:okunan].nil?
      is[:okunan] = sayac
    else
      sayac = is[:okunan] + 1
      is[:okunan] = nil
      is[:kalan] -= 1
    end
  end
  sayac
end

# Surec tabanli olcekleme: isci sayisi ortam degiskeninden, yoksa cekirdek sayisindan gelir.
def isci_sayisi(ortam) = Integer(ortam[:degiskenler].fetch("ISCI_SAYISI", ortam[:cekirdek]))

# Bellek: blok uc kez kosar; ilk kosumlar isinmadir, son kosumda ayrilan nesneler sayilir.
def ayrilan_nesne
  sonuc = nil
  3.times do
    once = GC.stat(:total_allocated_objects)
    yield
    sonuc = GC.stat(:total_allocated_objects) - once
  end
  sonuc
end

# Profil: programin tanimladigi yontemlerin cagrilari sayilir; en cok cagrilan sicak yoldur.
def sicak_yol
  sayim = Hash.new(0)
  iz = TracePoint.new(:call) { |t| sayim[t.method_id] += 1 if t.defined_class == Object }
  iz.enable { yield }
  sayim.max_by { |_, n| n }
end

def para_bicimle(kurus) = format("%d.%02d", kurus / 100, kurus % 100)
def satir_bicimle(satir) = "#{satir[:ad]} #{para_bicimle(satir[:birim])} #{para_bicimle(satir[:toplam])}"
def rapor(satirlar) = satirlar.map { |s| satir_bicimle(s) }.join("\n")

# Dagitim: varlik adi icerigin ozetini tasir; yayina kilitteki surumler kurulur.
def varlik_adi(ad, icerik) = ad.sub(/(\.\w+)\z/) { "-#{Digest::SHA256.hexdigest(icerik)[0, 8]}#{Regexp.last_match(1)}" }

def yayin_bildirimi(kosum)
  [varlik_adi("uygulama.css", "body { margin: 0 }"), *kosum.kilit.map { |ad, s| "#{ad}-#{s}" }]
end

satirlar = Array.new(100) { |i| { ad: "urun#{i}", birim: 1250, toplam: 1250 * (i + 1) } }
gozlemler = {
  "paket dosya adi" => ->(_k) { paket_tanimi.file_name },
  "kilitli surum" => ->(k) { kurulan_surum(k, "bicimleyici", "~> 1.4") },
  "test sonucu" => ->(k) { test_kos(k) },
  "saati okuyan test" => ->(k) { deneme_suresinde?(Time.utc(2026, 10, 1), k.ortam[:saat]) },
  "kilitsiz sayac" => ->(k) { kilitsiz_sayac(k) },
  "isci sayisi" => ->(k) { isci_sayisi(k.ortam) },
  "ayrilan nesne" => ->(_k) { ayrilan_nesne { Array.new(1000) { |i| i.to_s } } },
  "sicak yol" => ->(_k) { sicak_yol { rapor(satirlar) } },
  "yayin bildirimi" => ->(k) { yayin_bildirimi(k) }
}

adet = { "kod" => 0, "bagimlilik" => 0, "sira" => 0, "ortam" => 0 }

printf("%-24s%-13s%-10s%s\n", "gozlem", "belirleyen", "kanit", "taban")
gozlemler.each do |ad, hesap|
  tur, kanit, taban = belirleyen(hesap)
  adet[tur] += 1
  printf("%-24s%-13s%-10s%s\n", ad, tur, kanit, taban.inspect)
end

puts
adet.each { |tur, n| printf("%-13s%d\n", tur, n) }
puts "toplam gozlem: #{adet.values.sum}"
```

```
gozlem                  belirleyen   kanit     taban
paket dosya adi         kod          B- S- O-  "olcer-0.3.0.gem"
kilitli surum           bagimlilik   B+ S- O-  "1.4.2"
test sonucu             sira         B- S+ O-  []
saati okuyan test       ortam        B- S- O+  true
kilitsiz sayac          sira         B- S+ O-  6
isci sayisi             ortam        B- S- O+  4
ayrilan nesne           kod          B- S- O-  1001
sicak yol               kod          B- S- O-  [:para_bicimle, 200]
yayin bildirimi         bagimlilik   B+ S- O-  ["uygulama-997facec.css", "bicimleyici-1.4.2", "ayristirici-2.0.1"]

kod          3
bagimlilik   2
sira         2
ortam        2
toplam gozlem: 9
```

Çıktının 3/2/2/2 dağılımı bu kursun haritasıdır. Üç gözlem koddur, ikisi bağımlılık, ikisi sıra, ikisi ortamdır. Her ders bir gözlemi ayrıntılı ölçer; kurs bitince dokuzu da açılmış olur. `kurulan_surum` bir bağımlılık çözücü değil, kilit dosyasındaki sürümü okuyan bir modeldir. Dokuz satırın içinden bu dersin gözlemi ilk satırdır: `paket dosya adi`, kod. **RK1.** Çekirdek gerçek bir paket ekosistemi değil, onun modelidir; bu kursta ölçülen her blok saf Ruby ve standart kütüphanedir.

#### Paket Tanımı ve Dosya Adı

Bir **paket**, kodu, adını, sürümünü ve bağımlılıklarını tek birim olarak taşır. Bunları tanımlayan şey **paket tanımı**dır; Ruby'de bu `Gem::Specification` sınıfıdır. Çekirdekteki `paket_tanimi` yardımcısı küçük bir tanım kurar. Bir sonraki dosya aynı tanımı birebir alır; `belirleyen`'e gerek olmadığı için yalnız tanımı çağırır.

```ruby
# paket.rb — paket tanimi: ad ve surum paketin dosya adini verir
# Paket tanimi: ad ve surum paketin dosya adini verir.
def paket_tanimi(dosyalar = ["lib/olcer.rb", "lib/olcer/sayac.rb"])
  Gem::Specification.new do |t|
    t.name = "olcer"
    t.version = "0.3.0"
    t.summary = "kucuk bir olcum paketi"
    t.authors = ["ornek yazar"]
    t.files = dosyalar
    t.add_dependency "bicimleyici", "~> 1.4"
  end
end
```

Şimdi aynı tanımın dosya adına ve tam adına bakılır. Sürümü `0.3.0.pre1` yapmak için `version` alanı değiştirilir; ön sürüm, kararlı sürümden önce gelen sürümdür.

```ruby
# dosya_adi.rb — paketin dosya adi ad ve surumden gelir
require_relative "paket"

t = paket_tanimi
p t.file_name
p t.full_name

t.version = "0.3.0.pre1"
p t.file_name
p t.full_name
```
```
"olcer-0.3.0.gem"
"olcer-0.3.0"
"olcer-0.3.0.pre1.gem"
"olcer-0.3.0.pre1"
```

Dosya adı `ad-surum.gem` biçimindedir. `file_name` uzantıyı taşır, `full_name` taşımaz. Ön sürümde sürüm dizesinin bir parçası olur ve dosya adına girer. Bu, kuralın neden kodu görmesini açıklar: ad ve sürüm kaynakta yazılıdır, kilit dosyasından, tohumdan ya da saatten gelmez. **RK2.** Burada tek bir paket tanımı ölçüldü; çok sayıda alanı olan gerçek bir tanımın aynı kuralı izlediği sınanmadı.

#### Çalışma ve Geliştirme Bağımlılığı

Bir paket iki türde bağımlılık taşır. **Çalışma bağımlılığı**, paket kurulurken birlikte gelen şeydir. **Geliştirme bağımlılığı**, yalnız paketi geliştirenin ihtiyaç duyduğu şeydir; testler için kullanılan bir denetim aracı gibi. Paket tanımında ikisi ayrı yöntemlerle yazılır.

```ruby
# bagimliliklar.rb — calisma ve gelistirme bagimliligi
require_relative "paket"

t = paket_tanimi
t.add_development_dependency "denetci"
p t.dependencies.map { |b| [b.name, b.requirement.to_s, b.type] }
p t.runtime_dependencies.map(&:name)
```
```
[["bicimleyici", "~> 1.4", :runtime], ["denetci", ">= 0", :development]]
["bicimleyici"]
```

Birinci satır iki bağımlılığı sürüm kısıtlarıyla ve türleriyle listeler. Geliştirme bağımlılığının kısıtı `>= 0`'dır: hiçbir sürüm sınırı yazılmadı. İkinci satır paketle kurulacak olanları verir ve yalnız `bicimleyici` kalır. Paketi kuran kişi `denetci`'yi almaz. **RK3.** Bağımlılığın sürümünü kilit dosyası sabitler, kısıt yalnız sınırlar; bunun ölçümü sonraki dersin konusudur.

#### Sürüm Dizesi Değildir

Bir sürüm numarası noktayla ayrılmış sayılardan oluşur, ama karşılaştırması dize karşılaştırması gibi çalışmaz. Sürüm `0.10.0`, `0.3.0`'dan sonra gelir; dize olarak karşılaştırılırsa ilk farklı karakterde `1` ile `3` karşılaşır ve sıra tersine döner.

```ruby
# surum.rb — surum dize gibi karsilastirilmaz
p "0.10.0" > "0.3.0"
p Gem::Version.new("0.10.0") > Gem::Version.new("0.3.0")

onsurum = Gem::Version.new("1.0.0.pre1")
p onsurum < Gem::Version.new("1.0.0")
p onsurum.prerelease?

begin
  Gem::Version.new("bir.iki")
rescue => e
  p e.class
end
```
```
false
true
true
true
ArgumentError
```

Dize karşılaştırması `false`, sürüm karşılaştırması `true` verir. Ön sürüm, aynı numaralı kararlı sürümden küçüktür ve `prerelease?` ön sürüm olduğunu söyler. Sayıyla başlamayan bir dize (`"bir.iki"`) sürüm sayılmaz ve `ArgumentError` oluşur; `pre1` gibi harfli bir parça ise sayılardan sonra geldiğinde hata vermez, ön sürüm etiketi olarak okunur. **Anlamlı sürümleme**, sürümü ana, alt ve yama olarak üç sayıya böler: ana sürüm uyumsuz değişiklikte, alt sürüm uyumlu yeni özellikte, yama sürüm hata düzeltmesinde artar. Bu bir yazılı sözleşmedir; Ruby onu doğrulamaz. **RK4.** Ölçüm, sürüm kuralının karşılaştırmayı nasıl yaptığını gösterir; bir paketin gerçekten anlamlı sürümleme izlediğini göstermez.

#### Yayımlama

Paketi üretmek ve paket deposuna göndermek iki ayrı iştir. İlki paket tanımından `.gem` dosyasını yapar, ikincisi dosyayı herkese açık depoya yükler. Sandbox'ta ne paket komutu ne de ağ vardır, bu yüzden aşağıdaki iki komut koşturulmaz.

```text
gem build olcer.gemspec
gem push olcer-0.3.0.gem
```

`gem build` dosya adını paket tanımından alır; ölçülen `file_name` ile aynı addır. `gem push` ile yayımlanan bir sürümün içeriği sonradan değiştirilemez. Hatalı sürüm `gem yank` ile depo dizininden kaldırılabilir, ama o sürümü indirmiş olanların elindeki kopya yerinde kalır; düzeltilmiş içerik yeni bir sürüm numarasıyla çıkar. Pakete yanlışlıkla bir parola ya da anahtar girdiyse kaldırmak yetmez, o bilgi hemen değiştirilir. Bu yüzden ön sürümler vardır: yayımlamadan önce `0.3.0.pre1` ile denemek, kararlı sürümü korur.

#### Sınırlayıcı Ölçüm

Kural yalnız üç girdiyi görür: kilit, tohum, ortam. Çalışma dizini bunlardan biri değildir. Aşağıdaki ölçüm bu sınırı gösterir. Paketin dosya listesi `Dir.glob` ile dizinden okunur; hesap önce dizin boşken, sonra bir dosya yazıldıktan sonra sınıflandırılır.

```ruby
# sinir.rb — dosya listesi calisma dizininden gelince
require_relative "belirleyen"
require_relative "paket"

hesap = ->(_k) { paket_tanimi(Dir.glob("lib/**/*.rb")).files }
p belirleyen(hesap)

Dir.mkdir("lib")
File.write("lib/olcer.rb", "module Olcer; end\n")
p belirleyen(hesap)
```
```
["kod", "B- S- O-", []]
["kod", "B- S- O-", ["lib/olcer.rb"]]
```

Sonuç iki kez `kod` çıktı, ama dosya listesi değişti: önce boş, sonra `lib/olcer.rb`. Kural üç değişkenin hiçbirinde farkı görmedi, çünkü üçü de dizine dokunmaz. Sonuç gerçekte çalışma dizinine bağlıdır. Dizin modelin girdisi olmadığı için kural bu bağımlılığı **kod** diye adlandırır ve bu adlandırma yanıltıcıdır: değişen şey kodun kendisi değil, kodun okuduğu dosya sistemidir. **RK5.** Bu sınır, ölçümün altı çizilmeden bırakılırsa "bu sonuç koda bağlıdır" cümlesini olduğundan güçlü gösterir.

#### Özet

- Bu kursun sorusu "bu sonucu ne belirliyor?"dur; kural sonucu kod, bağımlılık, sıra ya da ortam olarak sınıflandırır ve dağılım 3/2/2/2'dir.
- Bu dersin belirleyeni **kod**dur: paketin dosya adı `olcer-0.3.0.gem` ad ve sürümden gelir, `paket dosya adi` satırında `B- S- O-` görünür.
- Çalışma bağımlılığı paketle kurulur, geliştirme bağımlılığı kurulmaz; ön sürüm dosya adına girer.
- Sürüm dize olarak karşılaştırılmaz; `Gem::Version` kuralı `0.10.0 > 0.3.0` der.
- Yayımlanan bir sürümün içeriği değiştirilemez; dosya listesini dizinden okuyan bir paket tanımı kuralın görmediği bir girdiye bağlanır.

#### Sonraki Adım

Paketin dosya adını ne belirlediği artık görünüyor. Ama bir paket kurulurken içindeki bağımlılığın hangi sürümünün geleceği paket tanımında yazılı değil. Bir sonraki ders kilit dosyası aynıyken kurulan sürümün neden değişmediğini ölçer.

### Bağımlılık Yönetimi

Source: https://academia.sh/tr/kurslar/ruby-uretim/bagimlilik-yonetimi

Kilit dosyası aynıyken kurulan sürüm neden değişmez? Ders sürüm kısıtlarının hangi sürümleri kabul ettiğini, kilitsiz çözümün depoya göre değiştiğini ve bağımlılığın belirleyen olduğunu ölçer.

Önceki ders paketin dosya adının ad ve sürümden geldiğini gösterdi: o sonuç koddu. Paket tanımında ayrıca bir bağımlılık yazılıydı, ama hangi sürümünün kurulacağı yazılı değildi. Bu dersin sorusu bu yüzden bir adım ileridedir: kilit dosyası aynı kaldığı sürece kurulan sürüm neden değişmiyor, ve kilit değişince ne oluyor?

Kursun sorusu aynıdır: **bu sonucu ne belirliyor?** Bu dersin gözlemi `kilitli surum` satırıdır. M12/K02'de "bu değişiklik nereye ulaşıyor" sorulmuştu; orada bir değişikliğin hangi katmanlara yayıldığı ölçüldü. Burada soru tersine döner: kurulan sürüm hangi girdiye bağlı? Çekirdeğin `belirleyen.rb` dosyası birebir yeniden verilir; blok kaynaktır ve `require_relative` ile çekilir.

```ruby
# belirleyen.rb — bir kosum modeli ve bir sonucu neyin belirledigini siniflandiran yardimci

# Kosum: sonucun bagli olabilecegi uc girdi. Bu uc girdinin disinda kalan her sey koddur.
Kosum = Data.define(:kilit, :tohum, :ortam)

TABAN = Kosum.new(
  kilit: { "bicimleyici" => "1.4.2", "ayristirici" => "2.0.1" },
  tohum: 1,
  ortam: { saat: Time.utc(2026, 10, 16, 9, 0), cekirdek: 4, degiskenler: {} }
)

# Her degisken tek bir girdiyi degistirir; otekiler tabandaki gibi kalir.
DEGISKENLER = {
  bagimlilik: TABAN.with(kilit: { "bicimleyici" => "1.5.0", "ayristirici" => "2.1.0" }),
  sira: TABAN.with(tohum: 2),
  ortam: TABAN.with(ortam: { saat: Time.utc(2027, 3, 1, 9, 0), cekirdek: 8,
                             degiskenler: { "ISCI_SAYISI" => "3" } })
}

# Kural: hesap once tabanda, sonra her degiskende bir kez kosar. Sonucu degistiren ilk
# degisken (bagimlilik, sira, ortam sirasiyla) belirleyendir; hicbiri degistirmiyorsa kod.
def belirleyen(hesap)
  taban = hesap.call(TABAN)
  degisti = DEGISKENLER.to_h { |ad, kosum| [ad, hesap.call(kosum) != taban] }
  tur = degisti.key(true)&.to_s || "kod"
  kanit = degisti.map { |ad, d| "#{ad[0].upcase}#{d ? '+' : '-'}" }.join(" ")
  [tur, kanit, taban]
end
```

#### Sürüm Kısıtı

**Bağımlılık yöneticisi**, bir paketin hangi sürümlerle çalışacağını **sürüm kısıtı** üzerinden çözer. Kısıt bir sürüm değil, bir kabul aralığıdır. Üç biçim görülür. Tam sürüm kısıtı tek bir sürümü kabul eder. Aralık kısıtı iki sınır arasını kabul eder: `>= 1.4` ve `< 3` birlikte yazılır. Üçüncüsü **karamsar kısıttır** (pessimistic constraint): `~> 1.4` yazısı, "1.4'ten küçük olmayan, ama ana sürümü değişmeyen" anlamına gelir. Kısıt, yazılan son basamağın yükselmesine izin verir, ondan önceki basamağın yükselmesine izin vermez; `~> 1.4` için üst sınır `2`'dir.

Aşağıdaki dosya üç biçimi aynı sürüm listesine uygular. `Gem::Requirement` kısıtı kurar, `satisfied_by?` bir sürümün kabul edilip edilmediğini söyler.

```ruby
# kisitlar.rb — surum kisitlari hangi surumleri kabul eder
def kabul_edilen(kisitlar, surumler)
  gerek = Gem::Requirement.new(*kisitlar)
  surumler.select { |s| gerek.satisfied_by?(Gem::Version.new(s)) }
end

liste = %w[1.3.9 1.4.0 1.4.2 1.5.0 1.99 2.0.0]
p kabul_edilen(["~> 1.4"], liste)
p kabul_edilen(["~> 1.4.0"], liste)
p kabul_edilen([">= 1.4", "< 3"], %w[1.3.9 1.4.0 2.9 3.0])

bagimlilik = Gem::Dependency.new("bicimleyici", "~> 1.4")
p bagimlilik.match?("bicimleyici", "1.9.0")
p bagimlilik.match?("bicimleyici", "2.0.0")
```
```
["1.4.0", "1.4.2", "1.5.0", "1.99"]
["1.4.0", "1.4.2"]
["1.4.0", "2.9"]
true
false
```

`~> 1.4` ana sürümü 1'de tutar ve `1.99`'a kadar her şeyi kabul eder: alt sürüm yükselebilir. `~> 1.4.0` bir basamak daha uzundur ve kabul aralığı daralır: yalnız `1.4.0` ile `1.4.2`. **Son basamak kısıtın genişliğini belirler.** Aralık kısıtı iki sınırın kesişimidir ve `3.0`'ı dışarıda bırakır. `Gem::Dependency` ise adı ve kısıtı birlikte taşır; `1.9.0`'ı kabul eder, `2.0.0`'ı reddeder. Anlamlı sürümleme sözleşmesi doğruysa, ana sürüm değişmediği sürece uyumsuz bir değişiklik gelmez; ama bu sözleşmeyi paketin yazarı verir, kısıt onu denetlemez. **RK6.** Bu ölçüm kısıtın hangi sürümleri kabul ettiğini gösterir; kabul edilen bir sürümün gerçekten uyumlu olduğunu göstermez.

#### Kilitsiz Çözüm

Kısıt birden çok sürümü kabul ettiğinde yöneticinin bir tanesini seçmesi gerekir. En basit kural en yüksek uygun sürümü almaktır. Aşağıdaki saf Ruby yardımcı bu çözümün küçük bir modelidir.

```ruby
# cozum.rb — kilit yokken depodan en yuksek uygun surum
def en_yuksek_uygun(depo, kisit)
  gerek = Gem::Requirement.new(kisit)
  depo.map { |s| Gem::Version.new(s) }.select { |s| gerek.satisfied_by?(s) }.max.to_s
end
```

Yardımcı kaynaktır. Aşağıdaki dosya onu önce üç sürümlük bir depoya, sonra bir sürüm eklenmiş depoya uygular.

```ruby
# depo.rb — ayni kisit, degisen depo
require_relative "cozum"

depo = %w[1.4.2 1.5.0 2.0.0]
p en_yuksek_uygun(depo, "~> 1.4")

depo << "1.6.0"
p en_yuksek_uygun(depo, "~> 1.4")
```
```
"1.5.0"
"1.6.0"
```

Kısıt aynı kaldı, depo değişti, sonuç `"1.5.0"`'dan `"1.6.0"`'a geçti. Kilitsiz çözüm **deponun o anki içeriğine** bağlıdır. Dün kurulan makine ile bugün kurulan makine aynı kısıtla farklı sürüm alabilir. Bu, "benim makinemde çalışıyordu" sorusunun kaynaklarından biridir.

#### Kilit Dosyası

Bunun çaresi, çözümün sonucunu bir **kilit dosyası**na yazmaktır. Kilit dosyası bildirim dosyasındaki kısıtlardan çözülmüş tam sürümleri tutar. Sonraki her kurulum çözüm yapmaz, kilitteki sürümü okur. Kilit dosyası depoya işlenir, çünkü bütün makinelerin aynı sürümleri alması ancak böyle sağlanır.

Çekirdekteki `kurulan_surum` bir bağımlılık çözücü değil, kilit dosyasındaki sürümü okuyan bir modeldir. Aşağıdaki dosya onun tanımını birebir alır.

```ruby
# bagimlilik.rb — kilitteki surumu okuyan model
# Bagimlilik cozumu: kilitteki surum kisiti karsiliyorsa o surum kurulur.
def kurulan_surum(kosum, ad, kisit)
  surum = Gem::Version.new(kosum.kilit.fetch(ad))
  raise ArgumentError, "kilit kisiti karsilamiyor" unless Gem::Requirement.new(kisit).satisfied_by?(surum)

  surum.to_s
end
```

Model üç yerde ölçülür: tam sürüm kısıtında, geniş kısıtta ve kilidi değişmiş koşumda. Son satır kuralın bu gözlemi nasıl sınıflandırdığını gösterir.

```ruby
# kilit.rb — kilit kurulan surumu sabitler
require_relative "belirleyen"
require_relative "bagimlilik"

p kurulan_surum(TABAN, "bicimleyici", "~> 1.4.0")
p kurulan_surum(TABAN, "bicimleyici", "~> 1.4")
p kurulan_surum(DEGISKENLER[:bagimlilik], "bicimleyici", "~> 1.4")
p belirleyen(->(k) { kurulan_surum(k, "bicimleyici", "~> 1.4") })
```
```
"1.4.2"
"1.4.2"
"1.5.0"
["bagimlilik", "B+ S- O-", "1.4.2"]
```

Kısıt `~> 1.4` geniş olduğu hâlde, kilit `1.4.2` dediği için kurulan `"1.4.2"` kaldı; kısıt `1.5.0`'ı kabul eder, ama kilit onu seçmedi. Kilit `1.5.0` olunca kurulan sürüm de `"1.5.0"` oldu. Dördüncü satır sınıflandırmayı verir: `bagimlilik`, kanıt `B+ S- O-`. Bağımlılık değişkeninde sonuç değişti, sıra ve ortam değişkenlerinde değişmedi. **Kısıt sürümü sabitlemez, yalnız sınırlar; sabitleyen kilittir.** Bu dersin belirleyeni **bağımlılık**tır ve ders boyunca bu kelime kilit dosyasının içeriği demektir. **RK7.** Ölçüm, kilit dosyasındaki sürüm listesinin çözümden geldiğini varsayar; bu listenin gerçekten bir çözücü tarafından üretildiği modele dahil değildir.

#### Kilidi Güncellemek

Kilit dosyası kendiliğinden değişmez. Depoya yeni sürüm gelmesi, kilidi yenilemek için yeterli değildir; kilit yalnız yönetici yeniden çözüm yaptığında yazılır. Bu bir açık işlemdir ve çekirdekte `TABAN.with` ile temsil edilir: kilit dosyasının içeriği yeni bir değerle değiştirilir, eski değer olduğu gibi kalır.

```ruby
# guncelle.rb — kilit yalniz acikca guncellenince degisir
require_relative "belirleyen"
require_relative "bagimlilik"
require_relative "cozum"

depo = %w[1.4.2 1.5.0 1.6.0]
yeni = en_yuksek_uygun(depo, "~> 1.4")
guncel = TABAN.with(kilit: TABAN.kilit.merge("bicimleyici" => yeni))

p kurulan_surum(TABAN, "bicimleyici", "~> 1.4")
p kurulan_surum(guncel, "bicimleyici", "~> 1.4")
p TABAN.kilit
p guncel.kilit
```
```
"1.4.2"
"1.6.0"
{"bicimleyici" => "1.4.2", "ayristirici" => "2.0.1"}
{"bicimleyici" => "1.6.0", "ayristirici" => "2.0.1"}
```

Depoda `1.6.0` bulunmasına rağmen `TABAN`'ın kilidi `1.4.2` kaldı; `kurulan_surum` bu koşumda `"1.4.2"` verdi. Kilidi yenilenmiş koşum `"1.6.0"` verdi, ve yalnız `bicimleyici` değişti: `ayristirici` kilitte `2.0.1` olarak kaldı. Bir güncelleme yalnız istenen bağımlılığı oynatabilir, ötekilere dokunmaz. Bu, bağımlılığın neden bir belirleyen olduğunu da açıklar: sonucu değiştiren şey kod değildir, kilit dosyasına yapılan bir yazıdır. Kod ve kısıt aynı kalır; kilit değişince sonuç değişir. **RK8.** Güncelleme burada tek bir bağımlılık için ve saf bir depoyla modellendi; birbirine bağlı bağımlılıkların birlikte güncellenmesi ölçülmedi.

#### İki Paketin Kısıtı

Bir uygulama birden çok paket kurar ve her paket aynı bağımlılık için kendi kısıtını yazar. Yönetici bütün kısıtları birlikte karşılayan bir sürüm arar: kabul aralıklarının kesişimi. Kesişim boşsa çözüm yoktur. Aşağıdaki dosya `kabul_edilen`'i iki paketin kısıtıyla çağırır.

```ruby
# kesisim.rb — iki paketin kisitinin kesisimi
def kabul_edilen(kisitlar, surumler)
  gerek = Gem::Requirement.new(*kisitlar)
  surumler.select { |s| gerek.satisfied_by?(Gem::Version.new(s)) }
end

depo = %w[1.4.0 1.4.2 1.5.0 1.99 2.0.0]
p kabul_edilen(["~> 1.4", ">= 1.5"], depo)
p kabul_edilen(["~> 1.4.0", ">= 1.5"], depo)
p kabul_edilen(["~> 1.4", "~> 2.0"], depo)

onsurum = Gem::Dependency.new("bicimleyici", "~> 1.4")
p Gem::Requirement.new("~> 1.4").satisfied_by?(Gem::Version.new("1.5.0.pre1"))
p onsurum.match?("bicimleyici", "1.5.0.pre1")
```
```
["1.5.0", "1.99"]
[]
[]
true
false
```

Birinci paket `~> 1.4`, ikincisi `>= 1.5` yazınca depodaki `1.5.0` ile `1.99` kalır. İkincisi `~> 1.4.0` yazarsa kesişim boşalır, çünkü `1.4.x` aralığı ile `1.5` ve sonrası hiçbir sürümde buluşmaz. Üçüncü satırda iki karamsar kısıt ana sürümde ayrılır ve yine hiçbir sürüm kalmaz. Boş küme bir çözüm değildir: yönetici burada kurulum yapmaz, çakışmayı bildirir. Çözüm, bütün kısıtların kesişiminde kalan bir sürümü aramaktır. Bu, kilit dosyasının neyi saklamadığını da gösterir: kilit çözümün sonucunu saklar, ama çözümün var olması kısıtların kesişimine bağlıdır.

Son iki satır ön sürümü ayırır. Kısıt nesnesi `1.5.0.pre1`'i yalnız sayılara bakarak kabul eder. Bağımlılık nesnesi ise ön sürümü varsayılan olarak aday saymaz. Aynı kısıt iki nesnede iki sonuç verdi, bu yüzden bir ön sürümün kurulup kurulmayacağı kısıta değil, soruyu soran nesneye bağlıdır. **RK9.** Bu dersin modeli yalnız `Gem` modülünün nesnelerini ölçer; gerçek bir yöneticinin ön sürümü hangi kuralla aday saydığı sınanmadı.

#### İki Makine, Bir Kilit Dosyası

Kilit dosyasının depoya işlenme gerekçesi iki makineyle ölçülür. Birinci makine kurulumu bugün yapar, ikincisi deponun bir sürüm daha kazandığı ertesi gün. Kilitsiz kurulum çözümü her makinede yeniden yapar. Kilitli kurulum çözüm yapmaz, kilidi okur.

```ruby
# makineler.rb — kilitsiz ve kilitli kurulum iki makinede
require_relative "belirleyen"
require_relative "bagimlilik"
require_relative "cozum"

bugun = %w[1.4.2 1.5.0]
yarin = bugun + ["1.6.0"]

p [en_yuksek_uygun(bugun, "~> 1.4"), en_yuksek_uygun(yarin, "~> 1.4")]
p [kurulan_surum(TABAN, "bicimleyici", "~> 1.4"), kurulan_surum(TABAN, "bicimleyici", "~> 1.4")]
```
```
["1.5.0", "1.6.0"]
["1.4.2", "1.4.2"]
```

Kilitsiz çiftte iki makine iki farklı sürüm kurdu; kilitli çiftte ikisi de aynısını kurdu. İkinci çiftte depo hiç kullanılmadı: `kurulan_surum` depoya bakmaz, bu yüzden deponun ertesi günkü içeriği sonucu etkilemez. Dün ile bugün arasındaki fark deponun değişmesidir; kilit dosyası bu farkı kurulumdan ayırır. Tutarlılığı kilit dosyası sağlar, ve bu ancak herkes aynı kilit dosyasını okuyorsa doğrudur. Kilit dosyası yalnız bir makinede duruyorsa öteki makinenin kurduğu sürüm bu ölçüm hakkında hiçbir şey söylemez. **RK10.** Model iki makineyi aynı kilit nesnesiyle temsil eder; gerçek bir ekipte kilit dosyasının eşitlenmesi ayrı bir iştir ve burada ölçülmedi.

#### Bağımlılık Türü ve Kısıt

Çalışma ve geliştirme bağımlılığı ayrımı bir önceki derste paket tanımı üzerinden kuruldu. Burada eklenen, türün kısıtla ilişkisidir: tür kurulan sürümü değil, nerede kurulacağını belirler, ve iki tür de aynı biçimde bir kısıt taşır.

```ruby
# tur.rb — bagimlilik turu ve kisit
bicimleyici = Gem::Dependency.new("bicimleyici", "~> 1.4", :runtime)
sinayici = Gem::Dependency.new("sinayici", ">= 2", :development)

p [bicimleyici.type, bicimleyici.runtime?]
p [sinayici.type, sinayici.runtime?]
p bicimleyici.requirement.to_s
p sinayici.requirement.to_s
```
```
[:runtime, true]
[:development, false]
"~> 1.4"
">= 2"
```

Geliştirme bağımlılığının `type` değeri `:development` döndü ve `runtime?` yanlış oldu; çalışma bağımlılığında `:runtime` ve doğru. İki kısıt metni de verildiği gibi geri okundu: `"~> 1.4"` ve `">= 2"`. Her bağımlılığın türü ayrı okunur ve `runtime?` yalnız çalışma türünde doğrudur. Kısıtın metni türden bağımsızdır. Bu ayrım, kilit dosyasının neden her ikisini de içerebildiğini açıklar: kilit bütün çözümü tutar, tür ise o çözümün hangi bölümünün kimin makinesine ineceğini söyler. **RK11.** `type` ve `runtime?` yalnız `Gem::Dependency` nesnesinin ne dediğini gösterir; bir yöneticinin geliştirme bağımlılığını kurulum sırasında atlaması bu ölçümün dışındadır.

#### Sınırlayıcı Ölçüm

Kural bir hesabı üç değişkende koşturur. Hesap herhangi birinde hata verirse sınıflandırma yapamaz. Bu dersin kendi hatası bunu gösterir: kısıt `~> 1.4.0` iken tabandaki kilit (`1.4.2`) kısıtı karşılar, ama bağımlılık değişkeninin kilidi (`1.5.0`) karşılamaz.

```ruby
# sinir.rb — kilit kisiti karsilamayinca kural siniflandirmaz
require_relative "belirleyen"
require_relative "bagimlilik"

begin
  belirleyen(->(k) { kurulan_surum(k, "bicimleyici", "~> 1.4.0") })
rescue => e
  p e.class
  puts e.message
end
```
```
ArgumentError
kilit kisiti karsilamiyor
```

Kural bir sınıf vermedi, `ArgumentError` yükseldi. Bu hata kuralın değil, hesabın hatasıdır: kilit dosyası kısıtla çelişiyor. Kurulumda görülen sorun budur; kural onu "bağımlılık" diye adlandırmaz çünkü hesap sonuç üretemedi. **RK12.** Bu ölçüm yalnız kilit ile kısıt çeliştiğinde olan şeyi gösterir; gerçek bir yönetici çelişkiyi kilidi yeniden çözerek gidermeyi seçebilir, bu modelin dışındadır.

#### Özet

- Sürüm kısıtı bir sürüm değil bir kabul aralığıdır: `~> 1.4` ana sürümü sabit tutar, `~> 1.4.0` yalnız yama sürümünü serbest bırakır, aralık kısıtı iki sınırın kesişimidir.
- Kilitsiz çözüm deponun içeriğine bağlıdır: depoya `1.6.0` gelince aynı kısıt başka sürüm verir.
- Bu dersin belirleyeni **bağımlılık**tır: `kilitli surum` satırında `B+ S- O-` görünür ve kilit `1.5.0` olunca kurulan sürüm de değişir.
- Kısıt sınırlar, kilit dosyası sabitler; kilit dosyası bu yüzden depoya işlenir.
- Kilit kısıtı karşılamazsa kural sınıflandırma yapmaz, hata verir.

#### Sonraki Adım

Kurulan sürümü artık kilit dosyası belirliyor. Ama aynı kod ve aynı kilitle bile bir sonuç değişebilir. Bir sonraki ders, aynı testlerin neden bazen düştüğünü ve bunu sıranın nasıl belirlediğini ölçer.

### Test Çerçeveleri

Source: https://academia.sh/tr/kurslar/ruby-uretim/test-cerceveleri

Aynı testler neden bazen düşer? Ders bir test çerçevesinin üç parçasını saf Ruby ile kurar, tohumun sırayı seçtiğini ve paylaşılan durumun testi düşürdüğünü ölçer.

Bağımlılık kilitlendi ve kurulan sürüm artık sabit. Yine de aynı kod, aynı kilit dosyası ve aynı testler bir koşumda geçip ötekinde düşebilir. Bu dersin gözlemi `test sonucu` satırıdır: tabanda `[]`, yani hiçbir test düşmez, ama kanıt sütununda `B- S+ O-` görünür. Sonuç, sıra değişince değişir.

M10/K05'te bir güvencenin sınırı soruldu: bir test neyi yakalar, neyi kaçırır. Burada soru başkadır. Bir testin geçip geçmediğini ne belirliyor? Orada sınır güvencenin kapsamıydı; burada sınır, testin kendi dışındaki bir girdidir. Çekirdeğin `belirleyen.rb` dosyası birebir yeniden verilir; blok kaynaktır ve `require_relative` ile çekilir.

```ruby
# belirleyen.rb — bir kosum modeli ve bir sonucu neyin belirledigini siniflandiran yardimci

# Kosum: sonucun bagli olabilecegi uc girdi. Bu uc girdinin disinda kalan her sey koddur.
Kosum = Data.define(:kilit, :tohum, :ortam)

TABAN = Kosum.new(
  kilit: { "bicimleyici" => "1.4.2", "ayristirici" => "2.0.1" },
  tohum: 1,
  ortam: { saat: Time.utc(2026, 10, 16, 9, 0), cekirdek: 4, degiskenler: {} }
)

# Her degisken tek bir girdiyi degistirir; otekiler tabandaki gibi kalir.
DEGISKENLER = {
  bagimlilik: TABAN.with(kilit: { "bicimleyici" => "1.5.0", "ayristirici" => "2.1.0" }),
  sira: TABAN.with(tohum: 2),
  ortam: TABAN.with(ortam: { saat: Time.utc(2027, 3, 1, 9, 0), cekirdek: 8,
                             degiskenler: { "ISCI_SAYISI" => "3" } })
}

# Kural: hesap once tabanda, sonra her degiskende bir kez kosar. Sonucu degistiren ilk
# degisken (bagimlilik, sira, ortam sirasiyla) belirleyendir; hicbiri degistirmiyorsa kod.
def belirleyen(hesap)
  taban = hesap.call(TABAN)
  degisti = DEGISKENLER.to_h { |ad, kosum| [ad, hesap.call(kosum) != taban] }
  tur = degisti.key(true)&.to_s || "kod"
  kanit = degisti.map { |ad, d| "#{ad[0].upcase}#{d ? '+' : '-'}" }.join(" ")
  [tur, kanit, taban]
end
```

#### Çerçevenin Üç Parçası

Bir **test çerçevesi** üç parçadan oluşur. **Test tanımı**, çalıştırılabilir bir deneme olarak adlandırılmış bir bloktur. **İddia**, beklenen ile gerçeği karşılaştırır ve tutmuyorsa hata yükseltir. **Test koşucusu**, tanımları çalıştırır, iddia hatasını yakalar ve sonucu bir tabloya yazar. Yaygın test çerçeveleri de bu üç parça üzerine kuruludur. Burada üçünü de saf Ruby ile kuruyoruz, çünkü gözlem koşucunun içindedir ve koşucu görünür olmalıdır.

```ruby
# cerceve.rb — test cercevesinin uc parcasi: tanim, iddia, kosucu
class IddiaHatasi < StandardError; end

# Iddia: beklenen ile gercek ayniysa sessiz kalir, degilse hata yukseltir.
def iddia(beklenen, gercek)
  raise IddiaHatasi, "iddia tutmadi" unless beklenen == gercek
end

# Kosucu: her testi calistirir, iddia hatasini yakalir, sonucu tabloya yazar.
def testleri_kos(testler)
  testler.to_h do |ad, test|
    test.call
    [ad, :gecti]
  rescue IddiaHatasi
    [ad, :dustu]
  end
end
```

Dosya kaynaktır. Aşağıdaki dosya üç testi koşturur. İkincisi tam sayı bölmesini yanlış bekler.

```ruby
# kucuk.rb — uc parca bir araya gelir
require_relative "cerceve"

testler = {
  "toplama" => -> { iddia(5, 2 + 3) },
  "bolme" => -> { iddia(3.5, 7 / 2) },
  "ondalik" => -> { iddia(3.5, 7 / 2.0) }
}
p testleri_kos(testler)
```
```
{"toplama" => :gecti, "bolme" => :dustu, "ondalik" => :gecti}
```

`7 / 2` tam sayı bölmesidir ve `3` verir; ikinci iddia tutmadı, koşucu hatayı yakaladı ve `bolme` testini `:dustu` olarak yazdı. Öteki ikisi geçti. Koşucunun yalnız `IddiaHatasi`'nı yakaladığına dikkat edin. Başka bir hata, örneğin sıfıra bölme, bir iddia sonucu değildir ve tabloya girmez.

```ruby
# hata.rb — iddia hatasi olmayan hata tabloya girmez
require_relative "cerceve"

begin
  testleri_kos("sifira bolme" => -> { 1 / 0 })
rescue => e
  p e.class
end
```
```
ZeroDivisionError
```

Koşucu `ZeroDivisionError`'u yakalamadı, hata dışarı çıktı. Bu ayrım önemlidir: bir testin **düşmesi** ile bir testin **patlaması** farklı olaylardır, ve bu derste yalnız ilkini ölçüyoruz. **RK13.** Burada kurulan çerçeve üç parçayı gösterir; gerçek bir çerçevenin beklenti kütüphanesi, ön hazırlık ve temizlik adımları bu modelin dışındadır.

#### İki Yazım Biçimi

Yaygın çerçeveler aynı üç parçayı iki ayrı yazım biçimiyle sunar. Birincisinde testler bir sınıfın `test_` ile başlayan yöntemleridir ve iddia bir yöntem çağrısıdır. İkincisinde testler bir tanım bloğunun içinde adlandırılan örneklerdir ve iddia gerçek değerden başlayan bir beklenti zinciridir. Aşağıdaki dosya aynı iki testi iki biçimde yazar ve ikisini de aynı koşucuya verir.

```ruby
# bicimler.rb — ayni iki test, iki yazim bicimi, tek kosucu
require_relative "cerceve"

# Birinci bicim: test sinifi; test_ ile baslayan her yontem bir testtir.
class BolmeTestleri
  def test_tam
    iddia(3, 7 / 2)
  end

  def test_ondalik
    iddia(3.5, 7 / 2)
  end
end

sinif_testleri = BolmeTestleri.public_instance_methods(false).grep(/\Atest_/).sort.to_h do |ad|
  [ad.to_s, -> { BolmeTestleri.new.public_send(ad) }]
end

# Ikinci bicim: tanim blogu ve beklenti zinciri.
Beklenti = Struct.new(:gercek) do
  def olsun(beklenen)
    iddia(beklenen, gercek)
  end
end

def bekle(gercek)
  Beklenti.new(gercek)
end

class Tanim
  attr_reader :testler

  def initialize(konu)
    @konu = konu
    @testler = {}
  end

  def ornek(ad, &blok)
    @testler["#{@konu} #{ad}"] = blok
  end
end

def tanimla(konu, &govde)
  tanim = Tanim.new(konu)
  tanim.instance_eval(&govde)
  tanim.testler
end

blok_testleri = tanimla("bolme") do
  ornek("tam") { bekle(7 / 2).olsun(3) }
  ornek("ondalik") { bekle(7 / 2).olsun(3.5) }
end

p testleri_kos(sinif_testleri)
p testleri_kos(blok_testleri)
```
```
{"test_ondalik" => :dustu, "test_tam" => :gecti}
{"bolme tam" => :gecti, "bolme ondalik" => :dustu}
```

İki tablo aynı sonucu verir: tam sayı bölmesini `3.5` bekleyen test iki biçimde de düştü. Fark yazımdadır. Birinci biçimde test adı yöntem adıdır ve testler bir sınıfta toplanır. İkinci biçimde test adı, konu ile örnek adından kurulan bir dizedir. Koşucu iki durumda da aynı ad → blok tablosunu görür; bu yüzden sonraki bölümlerde ölçülecek sıra ve tohum etkisi koşucuya aittir. **RK13a.** İki biçim burada tek bir örnekte, iki testle karşılaştırıldı; gerçek çerçevelerin ön hazırlık, iç içe gruplama ve raporlama farkları bu karşılaştırmanın dışındadır.

#### Gözlemin Koşucusu

Gözlem satırındaki koşucu daha küçüktür ama bir şey daha yapar: testleri tohumla karıştırır ve hepsine aynı ayar tablosunu verir. Aşağıdaki dosya `gozlemler.rb`'den alınan `TESTLER` ve `test_kos` tanımlarını birebir taşır.

```ruby
# test.rb — gozlem satirinin testleri ve kosucusu
# Test kosucusu: testler tohumla karistirilan sirada kosar ve ayni ayarlari paylasir.
TESTLER = {
  "fiyat_bicimi" => ->(ayar) { "12 #{ayar[:para_birimi]}" == "12 TRY" },
  "doviz_cevirimi" => ->(ayar) { ayar[:para_birimi] = "USD"; true },
  "fatura_toplami" => ->(ayar) { ayar[:para_birimi] == "TRY" }
}

def test_kos(kosum, testler = TESTLER)
  ayar = { para_birimi: "TRY" }
  sira = testler.keys.shuffle(random: Random.new(kosum.tohum))
  sira.reject { |ad| testler[ad].call(ayar) }
end
```

Üç test var. `fiyat_bicimi` ayardaki para birimine bakar ve `TRY` bekler. `doviz_cevirimi` ayardaki para birimini `USD` yapar ve doğru döner. `fatura_toplami` yine `TRY` bekler. Koşucu düşen testlerin adlarını döndürür. Aşağıdaki dosya altı tohum için karışık sırayı ve düşen testleri yan yana basar.

```ruby
# tohumlar.rb — alti tohum, alti sira
require_relative "belirleyen"
require_relative "test"

(1..6).each do |tohum|
  sira = TESTLER.keys.shuffle(random: Random.new(tohum))
  puts "#{tohum} #{sira.inspect} #{test_kos(TABAN.with(tohum: tohum)).inspect}"
end
```
```
1 ["fiyat_bicimi", "fatura_toplami", "doviz_cevirimi"] []
2 ["fatura_toplami", "doviz_cevirimi", "fiyat_bicimi"] ["fiyat_bicimi"]
3 ["doviz_cevirimi", "fiyat_bicimi", "fatura_toplami"] ["fiyat_bicimi", "fatura_toplami"]
4 ["doviz_cevirimi", "fiyat_bicimi", "fatura_toplami"] ["fiyat_bicimi", "fatura_toplami"]
5 ["fiyat_bicimi", "doviz_cevirimi", "fatura_toplami"] ["fatura_toplami"]
6 ["fiyat_bicimi", "doviz_cevirimi", "fatura_toplami"] ["fatura_toplami"]
```

Tohum 1 sıralaması `doviz_cevirimi`'ni sona koyar; ondan sonra koşan test olmadığı için hiçbir test düşmez. Tohum 2'de `doviz_cevirimi` ortadadır ve ondan sonra gelen `fiyat_bicimi` düşer. Tohum 3 ve 4 aynı sırayı verdi: `doviz_cevirimi` başta, ondan sonra gelen iki test de düştü. Tohum 5 ve 6 de aynı sırayı paylaşır; `doviz_cevirimi` ortadadır ve yalnız ondan sonra gelen `fatura_toplami` düşer. Altı tohumdan dört farklı sıra çıktı, ve her sıranın sonucu tohum sabit kaldıkça aynıdır.

#### Paylaşılan Durum

Kusur sırada değil, sırayı kusur olarak görünür kılan şeydedir. `doviz_cevirimi` ortak ayar tablosunu değiştirir. Bu değişiklik, ondan sonra koşan her testin girdisidir. Tablonun testlerin arasında paylaşıldığını doğrudan göstermek mümkündür.

```ruby
# paylasilan.rb — bir testin yazdigi ayar, ondan sonra gelen testin girdisi
require_relative "test"

ayar = { para_birimi: "TRY" }
TESTLER["doviz_cevirimi"].call(ayar)
p ayar
p TESTLER["fiyat_bicimi"].call(ayar)
p TESTLER["fiyat_bicimi"].call({ para_birimi: "TRY" })
```
```
{para_birimi: "USD"}
false
true
```

`doviz_cevirimi`'nden sonra ayar `USD` oldu. Aynı `fiyat_bicimi` testi bu ayarla `false` döndü, taze bir ayarla `true`. Test kodu aynıdır; değişen, testin başlangıç durumudur. Bu **paylaşılan durum**dur ve **kararsız test**in en yaygın kaynağıdır: bir test iyidir, ama kendisinden önce kimin koştuğuna bağlıdır. **RK14.** Ölçüm yalnız ayar tablosunu paylaşan bir model için geçerlidir; dosya sistemi, ağ ya da küresel değişken üzerinden paylaşılan durum aynı biçimde davranır, ama burada ölçülmedi.

#### Hangi Test, Kaç Tohumda Düşüyor

Tek bir tohumun sonucu bir tanıdır; altı tohumun sayımı ise bir profil verir. Aşağıdaki dosya her tohumun düşürdüğü test sayısını, her testin kaç tohumda düştüğünü ve en az bir testin düştüğü tohum sayısını sayar.

```ruby
# sayim.rb — dusenlerin tohuma gore dagilimi
require_relative "belirleyen"
require_relative "test"

sonuclar = (1..6).map { |t| test_kos(TABAN.with(tohum: t)) }
p sonuclar.map(&:size)
p sonuclar.flatten.tally
p sonuclar.count { |d| !d.empty? }
```
```
[0, 1, 2, 2, 1, 1]
{"fiyat_bicimi" => 3, "fatura_toplami" => 4}
5
```

Altı koşumun beşinde en az bir test düştü; yalnız tohum 1 temiz geçti. `doviz_cevirimi` hiç düşmedi, ama o, öteki ikisini düşüren testtir. `fatura_toplami` daha çok tohumda düştü çünkü `doviz_cevirimi` onun önüne daha sık geçti; `fiyat_bicimi` daha az düştü. Bu tablo suçluyu göstermez, mağduru gösterir. Düşen test, kusurlu testin kendisi değildir; kusurlu test sessizce geçiyor ve başkasını düşürüyor. **Bir testin düşme sıklığı, onun kendi sağlamlığını değil, önüne kimin geçtiğini ölçer.** Bu yüzden düşen testin kodu okunurken ilk soru, düşen testin neyi paylaştığı olmalıdır. **RK15.** Sayım tek bir takımın altı tohumu içindir; düşme sıklığının gerçek bir takımda aynı oranlarda olacağı bu ölçümden çıkmaz.

Sırayı sabitlemek bu sorunu çözmez, saklar. Tohum 1 her koşumda kullanılsaydı bu takım hiç düşmezdi ve paylaşılan durum fark edilmeden kalırdı. Karıştırma bir hata üretmez; var olan bağımlılığı görünür kılar. Ve görünür kılabildiği ölçüde, düşen testin tohumunu basmak, karıştırmayı bir kuşkudan bir tanıya çevirir.

#### Tohumla Yeniden Üretmek

Kararsız test, düştüğü anda yeniden üretilebilirse bir hata raporudur; üretilemiyorsa bir söylentidir. Koşucu tohumu sabitleyince düşen test her seferinde aynı sırada düşer. Bu yüzden iyi bir koşucu tohumu **basar**: bir koşum düştüğünde tohum, yeniden üretimin anahtarıdır.

```ruby
# tekrar.rb — dusen testi tohumla yeniden uret
require_relative "belirleyen"
require_relative "test"

sonuclar = (1..6).map { |t| test_kos(TABAN.with(tohum: t)) }
p sonuclar.count([])
p test_kos(DEGISKENLER[:sira])
p test_kos(DEGISKENLER[:sira]) == test_kos(TABAN.with(tohum: 2))
p belirleyen(->(k) { test_kos(k) })
```
```
1
["fiyat_bicimi"]
true
["sira", "B- S+ O-", []]
```

Altı tohumdan yalnız birinde hiçbir test düşmedi: tohum 1 geçerken tohum 2 düştü. `DEGISKENLER[:sira]` tohum 2 olduğu için `["fiyat_bicimi"]` verdi ve aynı tohumla ikinci çağrı birincinin aynısını verdi. Son satır sınıflandırmayı gösterir: sonuç `sira`dan geliyor, kanıt `B- S+ O-`, taban `[]`. Kural tabandaki sonucu `[]` olarak yazdı; düşen test taban koşumunda görünmez, yalnız `sira` değişkeninde görünür. Bu dersin belirleyeni **sıra**dır. **RK16.** Tohum 1'in "geçti" göstermesi testin sağlam olduğunu göstermez; yalnız o sırada ayar tablosunu bozan test sona düştüğünü gösterir.

#### Sınırlayıcı Ölçüm

Sınıf iki yönden sınanır. Birinci yön: her test kendi ayar kopyasıyla koşarsa ne olur? İkinci yön: kural yalnız iki tohumu dener, bu iki tohum her kararsızlığı yakalar mı?

```ruby
# yalitim.rb — her test kendi ayar kopyasini alir
require_relative "belirleyen"
require_relative "test"

def yalitilmis_test_kos(kosum, testler = TESTLER)
  ayar = { para_birimi: "TRY" }
  sira = testler.keys.shuffle(random: Random.new(kosum.tohum))
  sira.reject { |ad| testler[ad].call(ayar.dup) }
end

p (1..6).map { |t| yalitilmis_test_kos(TABAN.with(tohum: t)) }
p belirleyen(->(k) { yalitilmis_test_kos(k) })
```
```
[[], [], [], [], [], []]
["kod", "B- S- O-", []]
```

Altı tohumun hepsi `[]` verdi ve kural bu kez `kod` dedi, kanıt `B- S- O-`. Aynı üç test, aynı altı tohum; tek fark her testin kendi kopyasını alması. Sınıf **sıra**dan **kod**a geçti: **yalıtım sıranın etkisini ortadan kaldırır**, çünkü testler artık birbirinin girdisi değil. Yalıtımın sınırı kopyanın derinliğidir.

```ruby
# yuzeysel.rb — dup yuzeyseldir
ayar = { liste: [] }
kopya = ayar.dup
kopya[:liste] << 1
p ayar
p ayar[:liste].equal?(kopya[:liste])
```
```
{liste: [1]}
true
```

`dup` tabloyu kopyaladı, içindeki diziyi kopyalamadı: iki tablo aynı diziyi tutuyor ve kopyadaki değişiklik aslına geçti. Ayar tablosu yalnız basit değerler taşıdığı için bu dersin yalıtımı çalıştı. İç içe bir ayar için `dup` yetmez. **RK17.** Yalıtım ölçümü yalnız düz bir tablo için geçerlidir.

İkinci yön, kuralın kendi kör noktasıdır. Kural tabanı ve `sira` değişkenini, yani tohum 1 ve tohum 2'yi dener. Tohum 2'nin geçtiği ama tohum 5'in düştüğü bir test takımı kurulabilir. `fiyat_bicimi` testini ayara hiç bakmayan bir testle değiştirmek yeter.

```ruby
# kor.rb — kuralin yalniz tohum 1 ve 2'yi denedigi yer
require_relative "belirleyen"
require_relative "test"

TESTLER_KOR = TESTLER.merge("fiyat_bicimi" => ->(_ayar) { true })

p belirleyen(->(k) { test_kos(k, TESTLER_KOR) })
sonuclar = (1..6).map { |t| test_kos(TABAN.with(tohum: t), TESTLER_KOR) }
p sonuclar
```
```
["kod", "B- S- O-", []]
[[], [], ["fatura_toplami"], ["fatura_toplami"], ["fatura_toplami"], ["fatura_toplami"]]
```

Kural bu takımı `kod` olarak sınıflandırdı, çünkü tohum 1 ve 2 ikisi de `[]` verdi. Ama altı tohumdan dördünde `fatura_toplami` düştü. **"Sıra değil" sonucu yalnız denenen iki tohum için söylenmiştir.** Takım hâlâ paylaşılan duruma bağlı; kural onu görmedi çünkü bakmadığı sıralarda düşüyor. Bunun çaresi kuralı değiştirmek değil, ölçümü yorumlamaktır: kuralın `kod` dediği bir test takımı, tohum aralığı genişletilmeden yalıtılmış sayılmaz. Yukarıdaki iki ölçüm birlikte okunmalıdır. Yalıtılmış koşucu altı tohumun hepsinde `[]` verdi; sınanan takım ise yalnız tohum 1 ve 2'de `[]` verdi. Birincide `[]` altı tohumun tümünde görüldü, ikincisinde yalnız ikisinde. Aynı çıktı, farklı sayıda tohumla desteklenir, ve ölçümün değeri o sayıdan gelir. **RK18.** Altı tohum altı olası sıranın yalnız dördünü verdi; testlerin sayısı arttıkça olası sıra sayısı hızla büyür ve birkaç tohum onları tüketemez.

#### Özet

- Bir test çerçevesi üç parçadır: test tanımı, iddia ve koşucu. Düşen test iddia hatasıdır, patlayan test başka bir hatadır.
- İki yazım biçimi, yani `test_` yöntemli test sınıfı ve beklenti zincirli tanım bloğu, koşucuya aynı ad → blok tablosunu verir; aynı iki test iki biçimde de aynı sonucu verdi.
- Bu dersin belirleyeni **sıra**dır: `test sonucu` satırında kanıt `B- S+ O-`, tabanda `[]`, tohum 2'de `["fiyat_bicimi"]`.
- Kararsız testin kaynağı paylaşılan durumdur: `doviz_cevirimi` ortak ayarı değiştirir ve kendisinden sonra koşan testi düşürür. Tohum basılırsa düşen test yeniden üretilir.
- Her test kendi ayar kopyasını alınca altı tohumun hepsi `[]` verir ve kural `kod` der; `dup` yüzeyseldir.
- Kural yalnız tohum 1 ve 2'yi dener; tohum 2'nin geçtiği ama tohum 5'in düştüğü bir takımı `kod` diye sınıflandırır.

#### Sonraki Adım

Sırayı tohum seçiyor ve yalıtım onu kaldırıyor. Ama bir test başka bir girdiye de bağlı olabilir: bugünün saati. Bir sonraki ders saati okuyan bir testin neden yarın düşeceğini, ve sahteleme ile test verisinin bu bağımlılığı nasıl kaldırdığını ölçer.

### Sahteleme ve Test Verisi

Source: https://academia.sh/tr/kurslar/ruby-uretim/sahteleme-ve-test-verisi

Saati okuyan test neden yarın düşer? Ders saatin dışarıdan verilmesini, sahte nesneleri ve tohumlu test verisini ölçer; sahtelemenin hangi belirleyeni kaldırdığını ve hangisini bıraktığını ayırır.

Önceki ders bir test takımının sırayla düştüğünü gösterdi ve yalıtımla düşmeyi ortadan kaldırdı. Şimdi başka bir girdi var: bugünün tarihi. Bir test bugün geçiyor, otuz gün sonra düşüyor ve kodunda tek satır değişmedi. Bu dersin gözlemi `saati okuyan test` satırıdır: tabanda `true`, kanıt sütununda `B- S- O+`. Sonuç, saat değişince değişir; bağımlılık ve sıra değişince değişmez.

M13/K03'te bir değerin hangi isteğe kadar kaldığı soruldu: oturumda tutulan bir bilgi bir sonraki istekte hâlâ oradaydı. Burada aynı soru testlere uygulanır: bir test verisi hangi testten sonra hâlâ oradadır? Cevap, verinin kimin elinde olduğuna bağlıdır, ve bu ders onu ölçer. Çekirdeğin `belirleyen.rb` dosyası birebir yeniden verilir; blok kaynaktır ve `require_relative` ile çekilir.

```ruby
# belirleyen.rb — bir kosum modeli ve bir sonucu neyin belirledigini siniflandiran yardimci

# Kosum: sonucun bagli olabilecegi uc girdi. Bu uc girdinin disinda kalan her sey koddur.
Kosum = Data.define(:kilit, :tohum, :ortam)

TABAN = Kosum.new(
  kilit: { "bicimleyici" => "1.4.2", "ayristirici" => "2.0.1" },
  tohum: 1,
  ortam: { saat: Time.utc(2026, 10, 16, 9, 0), cekirdek: 4, degiskenler: {} }
)

# Her degisken tek bir girdiyi degistirir; otekiler tabandaki gibi kalir.
DEGISKENLER = {
  bagimlilik: TABAN.with(kilit: { "bicimleyici" => "1.5.0", "ayristirici" => "2.1.0" }),
  sira: TABAN.with(tohum: 2),
  ortam: TABAN.with(ortam: { saat: Time.utc(2027, 3, 1, 9, 0), cekirdek: 8,
                             degiskenler: { "ISCI_SAYISI" => "3" } })
}

# Kural: hesap once tabanda, sonra her degiskende bir kez kosar. Sonucu degistiren ilk
# degisken (bagimlilik, sira, ortam sirasiyla) belirleyendir; hicbiri degistirmiyorsa kod.
def belirleyen(hesap)
  taban = hesap.call(TABAN)
  degisti = DEGISKENLER.to_h { |ad, kosum| [ad, hesap.call(kosum) != taban] }
  tur = degisti.key(true)&.to_s || "kod"
  kanit = degisti.map { |ad, d| "#{ad[0].upcase}#{d ? '+' : '-'}" }.join(" ")
  [tur, kanit, taban]
end
```

#### Saati Okuyan Test

Gözlemin hesabı küçük bir kuraldır: deneme süresi 30 gündür ve süre başlangıçtan beri geçen saniye sayısıyla ölçülür. Hesap saati parametre olarak alır; iki sınır değeri ölçelim.

```ruby
# saat.rb — deneme suresi 30 gundur
def deneme_suresinde?(baslangic, saat) = saat - baslangic < 30 * 86_400

baslangic = Time.utc(2026, 10, 1)
p deneme_suresinde?(baslangic, Time.utc(2026, 10, 30, 23, 59))
p deneme_suresinde?(baslangic, Time.utc(2026, 10, 31, 0, 0))
```
```
true
false
```

Sınır `30 * 86_400` saniyedir. 30 Ekim 23:59 süre içindedir; 31 Ekim 00:00 tam otuz gün geçmiştir ve `false` verir. Bu hesap saf bir hesaptır: aynı iki girdi her zaman aynı sonucu verir. Şu ana kadar hiçbir şey ortama bağlı değil. Bağlılık, saatin **nereden** geldiği sorulduğunda başlar.

Aşağıdaki dosya iki test yazar. Birincisi saati koşum modelinin ortamından okur, yani saati makinenin söylediği gibi alır. İkincisi aynı hesabı, başında verilmiş sabit bir saat kaynağıyla koşturur.

```ruby
# ortam.rb — saati kimin verdigi sonucu belirler
require_relative "belirleyen"

def deneme_suresinde?(baslangic, saat) = saat - baslangic < 30 * 86_400

baslangic = Time.utc(2026, 10, 1)
p deneme_suresinde?(baslangic, TABAN.ortam[:saat])
p deneme_suresinde?(baslangic, DEGISKENLER[:ortam].ortam[:saat])
p belirleyen(->(k) { deneme_suresinde?(baslangic, k.ortam[:saat]) })
```
```
true
false
["ortam", "B- S- O+", true]
```

Tabandaki saatle test geçti, Mart 2027'deki saatle düştü. Kural `ortam` dedi, kanıt `B- S- O+`: bağımlılık ve sıra değişkenlerinde sonuç aynı kaldı, ortam değişkeninde değişti. Kodun hiçbir satırı değişmeden aynı test iki sonuç verdi. Bu, saati okuyan bir testin yarın düşecek olan testtir: `Time.now` ile okunan saat, bugünkü koşumda tabandaki gibi, otuz gün sonra Mart gibi davranır. **RK19.** Bu ölçüm saati ortam modelinden okur; gerçek saatin değeri `Time.now` ile okunur ve sandbox'tan sandbox'a değiştiği için basılmaz.

#### Saati Parametre Yapmak

Düzeltme, saati hesabın içinden çıkarıp dışarıdan vermektir. Hesabın saat kaynağını bir parametre olarak alması, saatin kim tarafından seçildiğini çağıran tarafa taşır. Üretimde kaynak gerçek saattir; testte kaynak sabit bir saattir. Bu sabit saat bir **sahte nesne**dir: gerçeğinin yerine geçen ve davranışı testin kontrol ettiği bir nesne.

```ruby
# sahte_saat.rb — saati disaridan veren test
require_relative "belirleyen"

def deneme_suresinde?(baslangic, saat) = saat - baslangic < 30 * 86_400
def kalan_deneme?(baslangic, saat_kaynagi) = deneme_suresinde?(baslangic, saat_kaynagi.call)

baslangic = Time.utc(2026, 10, 1)
sahte_saat = -> { Time.utc(2026, 10, 16, 9, 0) }

p belirleyen(->(k) { kalan_deneme?(baslangic, -> { k.ortam[:saat] }) })
p belirleyen(->(_k) { kalan_deneme?(baslangic, sahte_saat) })
```
```
["ortam", "B- S- O+", true]
["kod", "B- S- O-", true]
```

İlk satır saat kaynağı koşumun ortamından geldiğinde sonucu `ortam` olarak sınıflandırır. İkinci satır kaynağı sabit bir saatle değiştirir ve sonuç `kod` olur, kanıt `B- S- O-`. Hesap hâlâ aynı, test hâlâ aynı iddiada; değişen, saatin test tarafından seçilmesidir. Saat artık ortamın söylediği bir şey değil, testin kendi girdisidir. **RK20.** Sahte saat bir lambda ile kuruldu; gerçek bir çerçevenin zamanı dondurma yolu, örneğin `Time.now` yöntemini geçici olarak değiştirmesi, farklı bir kapsam taşır ve burada ölçülmedi.

Parametre yapmanın bir bedeli vardır ve ölçümün dışında kalmaz. Hesap artık bir saat kaynağı bekler; çağıran her yerin bunu geçmesi gerekir, ve üretimde geçilen kaynak gerçek saat olduğu için gerçek saatle çalışan yol testte hiç koşmaz. Sahte saatle yazılan test hesabı doğrular, saatin gerçek kaynaktan doğru okunduğunu doğrulamaz. Bu yüzden saati sahtelemek, saati okuyan testi yarın düşmekten korur ama okuma satırını denetimsiz bırakır. İyi bir ayrım, okumayı tek bir yerde toplamak ve o yeri ayrıca denemektir.

Sabit saatin ikinci bir yararı vardır: sınır değerleri denenebilir olur. Gerçek saatle 30. günün son dakikasını yakalamak için o dakikayı beklemek gerekir; sahte saatle iki çağrı yeter, ve ilk ölçümdeki `23:59` ile `00:00` ayrımı her koşumda aynı çıkar.

#### Sahte Nesneler

Saat, sahtelenen en basit bağımlılıktır. Testlerde üç tür sahte nesne görülür ve her birinin işi farklıdır. **Kayıt tutan sahte nesne** gerçeğinin yaptığı işi yapmaz, yalnız kendisine ne söylendiğini kaydeder. **Taklit yanıt** verilen bir çağrıya hazır bir cevap döner. **Beklenti nesnesi** ise bir çağrının yapılıp yapılmadığını sonradan denetler.

```ruby
# bildirici.rb — kayit tutan sahte nesne
class SahteBildirici
  attr_reader :gonderilenler

  def initialize
    @gonderilenler = []
  end

  def gonder(alici, metin)
    @gonderilenler << [alici, metin]
  end
end

def fatura_bildir(bildirici, alici) = bildirici.gonder(alici, "fatura hazir")

bildirici = SahteBildirici.new
fatura_bildir(bildirici, "a@ornek.test")
p bildirici.gonderilenler
```
```
[["a@ornek.test", "fatura hazir"]]
```

Fatura bildirimi gerçek bir ileti göndermedi; sahte bildirici ne gönderildiğini bir diziye yazdı. Test iddiasını bu diziye kurabilir: `[["a@ornek.test", "fatura hazir"]]`. Kayıt tutan sahte nesne, bir çağrının **ne yaptığını** değil **kime ne söylediğini** ölçer. Üretimdeki bildirici ağa çıkardı; sahte bildirici ağa çıkmaz, bu yüzden test hem hızlıdır hem ağın durumundan bağımsızdır.

```ruby
# taklit.rb — taklit yanit
doviz = Object.new
doviz.define_singleton_method(:kur) { |_birim| 42.5 }

p doviz.kur(:usd)
p doviz.singleton_methods
```
```
42.5
[:kur]
```

Taklit yanıt bir hesap yapmadı: ne sorulursa sorulsun `42.5` döndü. Nesnenin yalnız bir çağrısı var ve o da testin yazdığı çağrı. Bu, bir testin dış bir kaynağa, örneğin bir kur servisine bağlılığını koparır; ama aynı zamanda sahte nesnenin gerçeği ne kadar temsil ettiği sorusunu doğurur. Sahte bildirici iki bağımsız değişken alıyordu; gerçek bildirici üçüncü bir değişken, kanal, isteyebilir.

```ruby
# ayrisma.rb — sahte nesne gercekten ayrisabilir
class SahteBildirici
  attr_reader :gonderilenler

  def initialize = @gonderilenler = []

  def gonder(alici, metin) = @gonderilenler << [alici, metin]
end

class GercekBildirici
  def gonder(alici, metin, kanal) = [alici, metin, kanal]
end

sahte = SahteBildirici.new
sahte.gonder("a@ornek.test", "fatura hazir")
p sahte.gonderilenler

begin
  GercekBildirici.new.gonder("a@ornek.test", "fatura hazir")
rescue ArgumentError => e
  p e.class
end
```
```
[["a@ornek.test", "fatura hazir"]]
ArgumentError
```

Sahte nesneyle yazılan çağrı geçti, aynı çağrı gerçek nesnede `ArgumentError` verdi. Test yeşildi, üretim kırıldı. **RK21.** Sahte nesne gerçeğin davranışını taklit eder, doğrulamaz; sahte ile gerçek arasındaki ayrışmayı yakalamak için ya gerçek nesneyle yapılan ayrı bir test ya da sahtenin gerçeğin imzasını izlemesini denetleyen bir iddia gerekir. Bu ölçüm yalnız ayrışmanın var olabileceğini gösterir; ayrışmayı otomatik yakalayan bir aracı ölçmez.

Beklenti nesnesi kayıt tutan nesneye bir karar ekler. Neyin çağrıldığını tutar, ama testin sonunda bunu bir iddiaya çevirir: "bu alıcıya tam bir kez bildirim gitmeliydi".

```ruby
# beklenti.rb — beklenti nesnesi
class BildirimBeklentisi
  def initialize(alici, beklenen_sayi)
    @alici = alici
    @beklenen_sayi = beklenen_sayi
    @cagrilar = []
  end

  def gonder(alici, _metin) = @cagrilar << alici

  def dogrula = @cagrilar.count(@alici) == @beklenen_sayi
end

beklenti = BildirimBeklentisi.new("a@ornek.test", 1)
beklenti.gonder("a@ornek.test", "fatura hazir")
p beklenti.dogrula
beklenti.gonder("a@ornek.test", "fatura hazir")
p beklenti.dogrula
```
```
true
false
```

Birinci çağrıdan sonra beklenti tutar, ikinci çağrıdan sonra tutmaz: aynı alıcıya iki bildirim gitmiştir. Ayrım şudur: kayıt tutan nesne bilgiyi toplar, beklenti nesnesi o bilgiye bir karar bağlar. Karar yanlış kurulursa test yanlış şeyi doğrular; beklenti sayısı testin kendi varsayımıdır.

#### Test Verisi

Test verisi, bir testin girdisi olarak üretilen kayıtlardır: müşteri, fatura, kullanıcı. Veriyi elle yazmak her testte aynı satırları tekrarlar; bir **test verisi üreticisi** kayıtları bir kuralla üretir. En basit üretici bir sayaç tutar.

```ruby
# uretici.rb — sayacli test verisi ureticisi
def musteri_uretici
  sayac = 0
  -> { sayac += 1; "musteri#{sayac}" }
end

uret = musteri_uretici
p uret.call
p uret.call
p musteri_uretici.call
```
```
"musteri1"
"musteri2"
"musteri1"
```

Aynı üreticiden iki çağrı `"musteri1"` ve `"musteri2"` verdi: sayaç her çağrıda bir artar. Yeni bir üretici ise yeniden `"musteri1"` verdi. Bu iki gözlem yalıtımın cevabıdır. Bir üretici bütün testler arasında paylaşılırsa, bir testin ürettiği kayıt ötekinin sayacını ilerletir ve ikinci test `"musteri1"` yerine `"musteri2"` görür. Her testin kendi üreticisini kurması, paylaşılan durumu böler. Bu, önceki dersin ayar tablosundaki kopyalamanın aynısıdır, bir sayaç için. **RK22.** Ölçüm sayaçlı bir üreticiyi gösterir; veritabanında kalıcı bir kayıt üreten bir üreticinin aynı yalıtımı sağlaması için ayrıca temizlik adımı gerekir ve burada ölçülmedi.

#### Sınırlayıcı Ölçüm

Sahtelemenin sonucu her şeyi çözmüş gibi görünür: saat sabit, bildirici sahte, sayaç yalıtılmış. Ama bir belirleyeni kaldırmak, ötekini kaldırmaz. Test verisi tohumla üretilirse sonucu tohum belirler.

```ruby
# veri.rb — tohumlu test verisi
require_relative "belirleyen"

p Array.new(3) { Random.new(1).rand(100) }.uniq.size
p (r = Random.new(1); Array.new(3) { r.rand(100) })
p (r = Random.new(2); Array.new(3) { r.rand(100) })

veri = lambda do |k|
  r = Random.new(k.tohum)
  Array.new(3) { r.rand(100) }
end
p belirleyen(veri)

sabit = lambda do |_k|
  r = Random.new(7)
  Array.new(3) { r.rand(100) }
end
p belirleyen(sabit)
```
```
1
[37, 12, 72]
[40, 15, 72]
["sira", "B- S+ O-", [37, 12, 72]]
["kod", "B- S- O-", [47, 68, 25]]
```

Birinci satır, her çağrıda yeni bir `Random.new(1)` kurulursa üç değerin hep aynı olduğunu gösterir: tek bir değer. Tohum 1 ve tohum 2 iki farklı üçlü verdi. `belirleyen` tohumu koşumdan aldığında sonuç `sira`: kanıt `B- S+ O-`, taban tohum 1'in üç değeri. Tohum sabit bir sayı olduğunda, yani `Random.new(7)`, sonuç `kod`: hesap koşumun tohumuna bakmıyor. **Sahteleme bir belirleyeni kaldırır, ötekini kendiliğinden kaldırmaz.** Saati sabitlemek ortamı kaldırdı; tohumu koşumdan alan veri üreticisi sıra belirleyenini bıraktı. Hangi belirleyenin kaldığı, bir şeyin sahte olmasından değil, sonucun neye bakmaya devam ettiğinden anlaşılır. **RK23.** Tohumlu veri burada üç sayıdan ibarettir; gerçek bir veri üreticisinin kayıtları tohuma bağlı olduğunda testin neden bazen düştüğü, bir önceki dersin aynı nedenidir ve ayrıca ölçülmedi.

Bu ayrımın pratik sonucu şudur: bir testi kararlı yapmak için hangi belirleyenin sonucu oynattığı önce ölçülmelidir. Saati sahtelemek, sonucu sıraya bağlı bir testi kurtarmaz; sıralamayı sabitlemek, saate bağlı bir testi kurtarmaz. Önceki dersin tohum tablosu ile bu dersin saat ölçümü aynı soruyu iki ayrı değişkende sorar, ve cevap her seferinde değişkenin kendisine bağlıdır.

#### Özet

- Saati okuyan bir test ortamın söylediğine bağlıdır: `saati okuyan test` satırında kanıt `B- S- O+`, Mart 2027'de sonuç değişir.
- Bu dersin belirleyeni **ortam**dır. Saat parametre olarak verilince ve sabit bir saat kaynağı kullanılınca sonuç `kod` olur.
- Kayıt tutan sahte nesne ne söylendiğini toplar, taklit yanıt hazır bir cevap döner, beklenti nesnesi çağrıya bir karar bağlar.
- Sayaçlı test verisi üreticisi her çağrıda farklı değer verir; her testin kendi üreticisi paylaşılan durumu böler.
- Sahteleme bir belirleyeni kaldırır, ötekini kendiliğinden kaldırmaz: koşumun tohumundan üretilen test verisi `sira` olarak kalır.

#### Sonraki Adım

Bu kursun ilk yarısı paketlerin ve testlerin sonucu neyin belirlediğini ölçtü: dosya adını paket tanımı, kurulan sürümü kilit dosyası, düşen testi sıra, saati okuyan testi ortam. İkinci yarı üretimde çalışan koda geçer. Eşzamanlılık dersi, aynı kodun kilitsiz bir sayaçla koşarken tohuma göre farklı sonuç verdiğini ölçer ve dışlama kilidinin bu farkı neden kaldırdığını gösterir.

## Çalışma Zamanı ve Başarım

Küresel yorumlayıcı kilidi, iş parçacıkları ve Ractor'lar, çatallanan işçi süreçler ve ortam değişkenleriyle ölçekleme, nesne ayırma ve çöp toplayıcı, çağrı sayımıyla sıcak yolu bulma, içerik özetli varlıklar ve geri alınabilir sürüm geçişi.

### Eşzamanlılık Modeli

Source: https://academia.sh/tr/kurslar/ruby-uretim/eszamanlilik-modeli

Aynı kodun aynı girdilerle farklı sayı vermesi bir sıra sorunudur. Ders okuma ile yazma arasına giren geçişin bir sayacı kaybettirdiğini, dışlama kilidinin sonucu koddan çıkardığını ve Ractor'ın paylaşılan durumu kaldırdığını ölçer.

Önceki ders saati okuyan bir testin yarın düşebileceğini gösterdi: o sonucu ortam belirliyordu. Bu ders sıraya döner. Dört iş parçacığı bir sayacı bin kez artırdığında dört bin beklenir; sayaç bazen daha azını verir ve kod hiç değişmemiştir. Kursun ölçü ekseni sürer: **bu sonucu ne belirliyor?** Yanıt dört addan biridir, çıktıda ASCII yazılır: `kod`, `bagimlilik`, `sira`, `ortam`. Bu dersin gözlemi `kilitsiz sayac` satırıdır ve belirleyeni **sıra**dır.

M08/K04'te bir adımın hangi iş parçacığına düştüğü sorulmuştu. Burada iş parçacığının kimliği değil, sıranın sonucu değiştirip değiştirmediği sorulur. Çekirdeğin `belirleyen.rb` dosyası aşağıda olduğu gibi verilir; blok kaynaktır, çıktısı yoktur ve `require_relative` ile çekilir. Çekirdekteki `kilit` kilit dosyasının içeriğidir, yani kurulan sürümlerdir. Dışlama kilidi bu adla yalnız adı paylaşır; bu yüzden dersin dışlama kilidi değişkeni `dislama` olarak adlandırılır.

```ruby
# belirleyen.rb — bir kosum modeli ve bir sonucu neyin belirledigini siniflandiran yardimci

# Kosum: sonucun bagli olabilecegi uc girdi. Bu uc girdinin disinda kalan her sey koddur.
Kosum = Data.define(:kilit, :tohum, :ortam)

TABAN = Kosum.new(
  kilit: { "bicimleyici" => "1.4.2", "ayristirici" => "2.0.1" },
  tohum: 1,
  ortam: { saat: Time.utc(2026, 10, 16, 9, 0), cekirdek: 4, degiskenler: {} }
)

# Her degisken tek bir girdiyi degistirir; otekiler tabandaki gibi kalir.
DEGISKENLER = {
  bagimlilik: TABAN.with(kilit: { "bicimleyici" => "1.5.0", "ayristirici" => "2.1.0" }),
  sira: TABAN.with(tohum: 2),
  ortam: TABAN.with(ortam: { saat: Time.utc(2027, 3, 1, 9, 0), cekirdek: 8,
                             degiskenler: { "ISCI_SAYISI" => "3" } })
}

# Kural: hesap once tabanda, sonra her degiskende bir kez kosar. Sonucu degistiren ilk
# degisken (bagimlilik, sira, ortam sirasiyla) belirleyendir; hicbiri degistirmiyorsa kod.
def belirleyen(hesap)
  taban = hesap.call(TABAN)
  degisti = DEGISKENLER.to_h { |ad, kosum| [ad, hesap.call(kosum) != taban] }
  tur = degisti.key(true)&.to_s || "kod"
  kanit = degisti.map { |ad, d| "#{ad[0].upcase}#{d ? '+' : '-'}" }.join(" ")
  [tur, kanit, taban]
end
```

#### Okuma ile Yazma Arasındaki Boşluk

Çekirdeğin sayacı gerçek iş parçacığı değil, bir **zamanlayıcı modelidir**. İki iş parçacığı sayacı okur, bir fazlasını yazar ve her biri üç artış yapar. Okuma ile yazma arasında geçiş olabilir; sıradaki adımı hangi iş parçacığının atacağını `tohum` seçer. Doğru sonuç altıdır. Model gerçek iş parçacıklarının yerine konmuştur, çünkü gerçek iş parçacıklarının sırası koşumdan koşuma değişebilir ve basılan sayı bir daha aynı çıkmaz. Tohum sırayı sabitler; böylece bozuk sonuç her koşumda aynı çıkar ve ölçülebilir olur.

```ruby
# sayac.rb — zamanlayici modeli: kilitsiz sayac
# Zamanlayici modeli: iki is parcacigi sayaci okur ve bir fazlasini yazar; okuma ile yazma
# arasinda gecis olabilir. Siradaki adimi hangi is parcacigin atacagini tohum secer.
def kilitsiz_sayac(kosum, artis: 3)
  rastgele = Random.new(kosum.tohum)
  sayac = 0
  isler = Array.new(2) { { kalan: artis, okunan: nil } }
  loop do
    bekleyen = isler.select { |i| i[:kalan].positive? }
    break if bekleyen.empty?

    is = bekleyen.sample(random: rastgele)
    if is[:okunan].nil?
      is[:okunan] = sayac
    else
      sayac = is[:okunan] + 1
      is[:okunan] = nil
      is[:kalan] -= 1
    end
  end
  sayac
end
```

```ruby
# siralar.rb — kilitsiz sayac: belirleyen ve alti tohum
require_relative "belirleyen"
require_relative "sayac"

p belirleyen(->(k) { kilitsiz_sayac(k) })
sonuclar = (1..6).map { |tohum| kilitsiz_sayac(TABAN.with(tohum: tohum)) }
p sonuclar
```

```
["sira", "B- S+ O-", 6]
[6, 3, 5, 4, 4, 4]
```

İlk satır çekirdeğin sonucudur: tabanda sayaç altıdır ve kanıtta yalnız `S+` vardır. Kilit dosyasını ya da ortamı değiştirmek sonucu oynatmaz; tohumu değiştirmek oynatır. İkinci satır altı tohumun sonuçlarını gösterir: dört ayrı değer çıkar ve yalnız birinde sayaç doğru olan altıyı verir. Kayıp artış, iki iş parçacığının aynı değeri okuyup aynı değeri yazmasından gelir. **RB1.** Bu sonuçlar yalnız iki iş parçacığı ve üçer artış içindir; daha çok iş parçacığında kayıp oranı bu ölçümden çıkmaz.

#### Küresel Yorumlayıcı Kilidi Neyi Korumaz

Ruby'nin **küresel yorumlayıcı kilidi (global interpreter lock)**, aynı anda yalnız bir iş parçacığının Ruby kodu koşturmasını sağlar. Bu, iki iş parçacığının aynı bellek konumuna aynı anda yazmasını engeller; ama bir iş parçacığının okumasından yazmasına kadar geçen aralığı korumaz. `sayac += 1` tek bir adım gibi görünür, oysa okuma, toplama ve yazmadır. Zamanlayıcı bu üç adımın arasında başka bir iş parçacığına geçebilir ve modelin yaptığı tam olarak budur. Bu yüzden gerçek iş parçacıklarıyla yazılmış kilitsiz bir sayaç bu derste basılmaz: sonucu koşumdan koşuma değişebilir.

Sıranın sonucu değiştirmediği tasarım, aradaki boşluğu kapatır. **Dışlama kilidi (mutex)**, bir bölgeye aynı anda yalnız bir iş parçacığını alır. Aşağıdaki blok dört gerçek iş parçacığı kurar, her biri sayacı bin kez artırır ve her artış `dislama` kilidinin içinde yapılır.

```ruby
# dislama.rb — dort gercek is parcacigi, dislama kilidiyle
require_relative "belirleyen"

def dislamali_sayac(is_parcacigi: 4, artis: 1000)
  sayac = 0
  dislama = Mutex.new
  isler = Array.new(is_parcacigi) do
    Thread.new do
      artis.times { dislama.synchronize { sayac += 1 } }
    end
  end
  isler.each(&:join)
  sayac
end

p dislamali_sayac
p belirleyen(->(_k) { dislamali_sayac })
```

```
4000
["kod", "B- S- O-", 4000]
```

Sonuç dört bindir. Kural bu hesabı üç değişkende de koşturdu ve sonuç hiçbirinde değişmedi; kanıt `B- S- O-`, belirleyen **kod**dur. Kilitsiz model sıraya bağlıydı, dışlama kilidiyle yazılmış gerçek sayaç ise kendi koduna bağlıdır. **RB2.** Bu kuralın üç değişkeni iş parçacığı sırasını doğrudan değiştirmez: `tohum` yalnız modeldeki zamanlayıcıyı sürer. Dört bin, dışlama kilidinin sırayı değiştirdiğinde bile sonucu koruduğunu kanıtlamaz; yalnız bu koşumda tutarlı olduğunu söyler.

#### Kuyruk ve İş Parçacığındaki İstisna

Paylaşılan durumu kilitlemek tek yol değildir. Üretici ile tüketici bir **kuyruk (queue)** üzerinden konuşursa değer bir elden ötekine geçer ve iki taraf aynı değişkene yazmaz. Aşağıdaki blokta üretici sıfırdan dörde kadar sayıları kuyruğa bırakır, tüketici toplar.

```ruby
# kuyruk.rb — uretici ve tuketici bir kuyruktan konusur
kuyruk = Thread::Queue.new
uretici = Thread.new do
  5.times { |i| kuyruk << i }
  kuyruk << :bitti
end
toplam = 0
tuketici = Thread.new do
  while (oge = kuyruk.pop) != :bitti
    toplam += oge
  end
end
[uretici, tuketici].each(&:join)
p toplam
```

```
10
```

Toplam on çıktı. `toplam` değişkenini yalnız tüketici yazar, bu yüzden okuma ile yazma arasına giren başka bir yazan yoktur. Kuyruk sırayı korur ama sonuç sıradan da bağımsızdır: toplama sırası sonucu değiştirmez.

Bir iş parçacığında patlayan istisna sessizce kaybolmaz; ana iş parçacığında `join` çağrısında yeniden yükselir. Standart hataya iz düşmesin diye blok önce raporlamayı kapatır. İstisna iletisi dersin kendi `raise` çağrısındadır.

```ruby
# istisna.rb — is parcacigindaki istisna join'de ana is parcacigina doner
Thread.report_on_exception = false
is = Thread.new { raise "patladi" }
begin
  is.join
rescue => e
  puts "yakalandi: #{e.class} #{e.message}"
end
p is.status
```

```
yakalandi: RuntimeError patladi
nil
```

İstisna `RuntimeError` olarak `join`'de yakalandı. İş parçacığının durumu `nil`'dir: iş parçacığı bir istisnayla bitti. `join` çağrılmasaydı istisna ana iş parçacığına hiç ulaşmazdı. **RB3.** Bu ölçüm tek bir iş parçacığı ve tek bir istisna içindir; birden çok iş parçacığının aynı anda patlaması burada ölçülmedi.

#### Sınırlayıcı Ölçüm

Dışlama kilidi paylaşılan durumu sıraya karşı korur. **Ractor** başka bir yol açar: bir Ractor, yalıtılmış bellekle koşan yürütme birimidir ve dışarıdaki yerel değişkenlere erişemez. Paylaşılan durum baştan yoktur, bu yüzden korunacak bir şey de kalmaz. İlk blok bir Ractor'ın değer döndürdüğünü, ikincisi dışarıdaki bir değişkene dokunan bloğun reddedildiğini ölçer.

```ruby
# ractor.rb — yalitilmis bellekle kosan yurutme birimi
p Ractor.new { (1..10).sum }.value

dis = [1, 2, 3]
begin
  Ractor.new { dis.size }
rescue ArgumentError => e
  puts e.class
end
```

```
55
ArgumentError
```

İlk Ractor elli beş verdi. İkincisi `ArgumentError` ile reddedildi: blok dışarıdaki `dis` değişkenini okuyordu. Yalıtım sırayı değil, paylaşılan durumu kaldırır. Sıra hâlâ belirsizdir; ama iki Ractor aynı değişkene yazamayacağı için sıranın sonucu bozacağı yer kalmaz. Bir nesnenin Ractor'lar arasında paylaşılabilmesi için **paylaşılabilir nesne** olması gerekir. Dondurmak bunun için yetmez.

```ruby
# paylasilabilir.rb — dondurmak yuzeyseldir
sade = [1, 2].freeze
ic_degisken = [+"a"].freeze
p Ractor.shareable?(sade)
p Ractor.shareable?(ic_degisken)
p Ractor.shareable?(Ractor.make_shareable([+"a"]))
```

```
true
false
true
```

Donmuş ve içi sayılardan oluşan dizi paylaşılabilirdir. İçinde donmamış bir dize taşıyan donmuş dizi değildir: dondurma yalnız dizinin kendisini dondurur, içindekileri değil. `make_shareable` içindekileri de dondurur ve sonuç paylaşılabilir olur. **RB4.** Bu üç sonuç Ruby 4.0.7'de ölçüldü; Ractor'ın kuralları sürümler arasında değişebilir.

Sınırlayıcı sonuç şudur: Ractor, kuralın `sira` sınıfını yok etmez. Kilitsiz model sıraya bağlıydı, çünkü iki iş parçacığı bir değişkeni paylaşıyordu. Paylaşım kalkarsa sıra sonucu belirlemez; paylaşım kalkmadan sıra belirleyendir.

#### Özet

- Kilitsiz sayacı **sıra** belirler: aynı kod, aynı ortam ve aynı kilit dosyasıyla altı tohum dört farklı sonuç verdi, yalnız biri doğruydu.
- Küresel yorumlayıcı kilidi okuma ile yazma arasını korumaz; `sayac += 1` üç adımdır.
- Dışlama kilidi sayacı dört bine sabitledi ve kural sınıfı `kod` verdi; sıra sonucu artık belirlemiyor.
- Kuyruk değeri bir elden ötekine geçirir; iş parçacığındaki istisna `join` çağrısında yeniden yükselir.
- Ractor paylaşılan durumu kaldırır; dondurma yüzeyseldir ve içi donmamış bir dizi paylaşılabilir sayılmaz.

#### Sonraki Adım

Kilitsiz sayacın neden kimi zaman altıdan az saydığı sorusu yanıtını buldu: sayacı sıra belirliyordu. Bir sonraki soru işin kaç parçaya bölüneceğidir. Çatallanan işçi süreçlerin sayısı bir ortam değişkeninden ya da çekirdek sayısından gelir; sonraki ders bu sayının nereden okunduğunu ve çatallanan sürecin belleğinin ebeveyne neden yansımadığını ölçer.

### Süreç Tabanlı Ölçekleme

Source: https://academia.sh/tr/kurslar/ruby-uretim/surec-tabanli-olcekleme

İşçi sayısı koddan değil, ortamdan gelir. Ders çatallanan sürecin belleğinin ebeveyne yansımadığını, işin dilimlenip borudan toplandığını ve işçi sayısının ortam değişkeni ile çekirdek sayısından okunduğunu ölçer.

Önceki ders sayacın sonucunu sıranın belirlediğini gösterdi ve paylaşılan durumu kaldıran bir yol olarak Ractor'ı ölçtü. Süreç tabanlı ölçekleme aynı sonuca daha eski ve daha yaygın bir yoldan gider: her işçi kendi belleğiyle çalışan ayrı bir sürectir, bu yüzden paylaşılan değişken baştan yoktur. Geriye bir soru kalır: kaç işçi? Kursun ölçü ekseni burada da geçerlidir: **bu sonucu ne belirliyor?** Bu dersin gözlemi `isci sayisi` satırıdır ve belirleyeni **ortam**dır.

Bu kursta `ortam` kelimesi iki yerde geçer. M12/K03'te ortam, uygulamanın yapılandırma katmanıydı: geliştirme, test ve üretim. Burada ise ortam, kuralın üç girdisinden biridir: saat, çekirdek sayısı ve ortam değişkenleri. İkisi aynı ad altında farklı şeylerdir. M11/K03'te süreçlerin nasıl doğduğu ve borularla nasıl konuştuğu görülmüştü; bu ders onların sayısını kimin belirlediğini sorar. Çekirdeğin `belirleyen.rb` dosyası ve bu dersin kesiti `isci.rb` aşağıda olduğu gibi verilir. İkisi de kaynaktır, çıktısı yoktur. Çekirdek bir modeldir: `ortam` içindeki saat ve çekirdek sayısı gerçek makineden değil, tanımdan gelir.

```ruby
# belirleyen.rb — bir kosum modeli ve bir sonucu neyin belirledigini siniflandiran yardimci

# Kosum: sonucun bagli olabilecegi uc girdi. Bu uc girdinin disinda kalan her sey koddur.
Kosum = Data.define(:kilit, :tohum, :ortam)

TABAN = Kosum.new(
  kilit: { "bicimleyici" => "1.4.2", "ayristirici" => "2.0.1" },
  tohum: 1,
  ortam: { saat: Time.utc(2026, 10, 16, 9, 0), cekirdek: 4, degiskenler: {} }
)

# Her degisken tek bir girdiyi degistirir; otekiler tabandaki gibi kalir.
DEGISKENLER = {
  bagimlilik: TABAN.with(kilit: { "bicimleyici" => "1.5.0", "ayristirici" => "2.1.0" }),
  sira: TABAN.with(tohum: 2),
  ortam: TABAN.with(ortam: { saat: Time.utc(2027, 3, 1, 9, 0), cekirdek: 8,
                             degiskenler: { "ISCI_SAYISI" => "3" } })
}

# Kural: hesap once tabanda, sonra her degiskende bir kez kosar. Sonucu degistiren ilk
# degisken (bagimlilik, sira, ortam sirasiyla) belirleyendir; hicbiri degistirmiyorsa kod.
def belirleyen(hesap)
  taban = hesap.call(TABAN)
  degisti = DEGISKENLER.to_h { |ad, kosum| [ad, hesap.call(kosum) != taban] }
  tur = degisti.key(true)&.to_s || "kod"
  kanit = degisti.map { |ad, d| "#{ad[0].upcase}#{d ? '+' : '-'}" }.join(" ")
  [tur, kanit, taban]
end
```

```ruby
# isci.rb — isci sayisi ortam degiskeninden, yoksa cekirdek sayisindan gelir
require_relative "belirleyen"

# Surec tabanli olcekleme: isci sayisi ortam degiskeninden, yoksa cekirdek sayisindan gelir.
def isci_sayisi(ortam) = Integer(ortam[:degiskenler].fetch("ISCI_SAYISI", ortam[:cekirdek]))
```

#### Çatallanan Süreç Kendi Belleğine Yazar

**Çatallama (fork)**, çalışan sürecin bir kopyasını başlatır. Kopya, ebeveynin belleğini o ana kadarki hâliyle devralır, ama o andan sonra iki bellek ayrıdır. Çocuk bir değişkeni değiştirirse ebeveyn bunu görmez. Aşağıdaki blok bu ayrımı ölçer: çocuk `deger` değişkenine doksan dokuz yazar ve üç koduyla çıkar; ebeveyn değişkenin değerini ve çocuğun çıkış kodunu okur.

```ruby
# cocuk.rb — cocuk kendi bellegine yazar, cikis kodu ebeveyne doner
deger = 1
pid = fork do
  deger = 99
  exit!(3)
end
Process.wait(pid)
p deger
p $?.exitstatus
```

```
1
3
```

Ebeveyn `deger` için bir görür; çocuğun yazdığı doksan dokuz ebeveyne ulaşmadı. Ebeveyne dönen tek şey çıkış koduydu. Bu, Ractor'ın zorla kurduğu yalıtımın süreçlerde işletim sisteminden bedavaya gelmesidir. **RB5.** Bu ölçüm çatallamanın belleği ayırdığını gösterir; ayrılan belleğin ne kadar yer tuttuğunu göstermez. O soru bir sonraki derstedir.

#### İşi Dilimleyip Boruyla Toplamak

Bellek ayrı olunca sonucu ebeveyne geri taşımak gerekir. Bunun yolu **boru (pipe)**dir. Aşağıdaki blok bir ile otuz arasındaki sayıları onarlık üç dilime böler, her dilimi bir çocuğa verir ve çocuklar dilim numarası ile dilimin toplamını aynı boruya yazar. Çocukların bitiş sırası belli olmadığı için ebeveyn sonuçları numaraya göre sıralayarak basar.

```ruby
# dilim.rb — is uc iscide dilimlenir, sonuclar borudan toplanir
okuyucu, yazici = IO.pipe
pidler = (0..2).map do |no|
  fork do
    okuyucu.close
    dilim = ((no * 10) + 1)..((no * 10) + 10)
    yazici.puts "#{no} #{dilim.sum}"
    yazici.close
    exit!(0)
  end
end
yazici.close
pidler.each { |pid| Process.wait(pid) }
sonuclar = okuyucu.each_line.map { |satir| satir.split.map(&:to_i) }.sort
p sonuclar
p sonuclar.sum { |_, toplam| toplam }
```

```
[[0, 55], [1, 155], [2, 255]]
465
```

Üç dilim elli beş, yüz elli beş ve iki yüz elli beş verdi; toplam dört yüz altmış beştir ve bir ile otuz arasındaki sayıların toplamıyla aynıdır. Sıralama ölçümün parçasıdır: sıralanmasaydı satırlar çocukların bitiş sırasıyla gelirdi ve o sıra koşumdan koşuma değişebilirdi. **RB6.** Üç çocuk ve ufak bir iş için ölçüldü; çocuk başlatmanın bedeli iş küçükken kazancı aşabilir ve bu bedel burada ölçülmedi.

#### İşçi Sayısı Nereden Gelir

İşçi sayısı koda gömülmez. Bir dağıtımda dört çekirdekli makine de olur, sekiz çekirdekli de; sayıyı o makinenin söylemesi beklenir. `isci_sayisi` önce `ISCI_SAYISI` ortam değişkenine bakar, yoksa çekirdek sayısını alır. Aşağıdaki blok bu tanımı çekirdeğin tabanında ve ortam değişkeni verilmiş bir ortamda koşturur, sonra sayı olmayan bir değeri dener.

```ruby
# isci_olc.rb — isci sayisi ortamdan okunur
require_relative "isci"

p isci_sayisi(TABAN.ortam)
p isci_sayisi(TABAN.ortam.merge(degiskenler: { "ISCI_SAYISI" => "3" }))
begin
  isci_sayisi(TABAN.ortam.merge(degiskenler: { "ISCI_SAYISI" => "uc" }))
rescue ArgumentError => e
  puts e.class
end
p belirleyen(->(k) { isci_sayisi(k.ortam) })
p belirleyen(->(k) { k.ortam[:degiskenler].fetch("ISCI_SAYISI", "yok") })
```

```
4
3
ArgumentError
["ortam", "B- S- O+", 4]
["ortam", "B- S- O+", "yok"]
```

Tabanda dört çıktı: ortam değişkeni yok, çekirdek sayısı dört. Değişken üç olunca sayı üç oldu. `"uc"` yazısı bir tamsayıya çevrilemediği için `ArgumentError` doğdu; yanlış yazılmış bir değişken sessizce varsayılana düşmedi, patladı. Dördüncü satırda kural bu hesabı üç değişkende koşturdu. Yalnız ortam sonucu oynattı; kanıt `B- S- O+`, belirleyen **ortam**dır. Beşinci satır aynı soruyu çekirdek sayısına düşmeden sorar: ortam değişkeni yoksa `"yok"`, varsa `"3"` döner ve belirleyen yine **ortam**dır.

Bu çıktı bir yapılandırma kararını da gösterir: işçi sayısının ortamdan gelmesi bir kusur değildir, bilerek yapılmış bir tercihtir. Kusur, ortamın söylediğini kodun bilmemesidir. **RB7.** Çekirdek sayısı modelde dörttür; gerçek bir makinede bu sayı `Etc.nprocessors` ile okunur ve her makinede farklıdır.

```ruby
# cekirdek.rb — gercek cekirdek sayisi basilmaz, yalnizca var oldugu dogrulanir
require "etc"
p Etc.nprocessors.positive?
```

```
true
```

Gerçek sayı basılmadı çünkü her makinede farklıdır; ölçülen şey, sayının sıfırdan büyük olmasıdır.

#### Sınırlayıcı Ölçüm

Çatallanan süreçte de kuralın çalışıp çalışmadığı sınanmalıdır. Aşağıdaki blokta ikinci dilimin toplamı çocuk süreçte, `belirleyen` çağrısının içinde hesaplanır ve sonuç borudan ebeveyne `Marshal` ile geçer. Soru şudur: dilimin toplamını ne belirliyor?

```ruby
# sinir.rb — cocuk surecin dilim toplamini kural siniflandirir
require_relative "belirleyen"

okuyucu, yazici = IO.pipe
pid = fork do
  okuyucu.close
  yazici.write Marshal.dump(belirleyen(->(_k) { (11..20).sum }))
  yazici.close
  exit!(0)
end
yazici.close
Process.wait(pid)
p Marshal.load(okuyucu.read)
```

```
["kod", "B- S- O-", 155]
```

Sonuç yüz elli beştir ve kanıt `B- S- O-`dur; belirleyen **kod**dur. Dilimin toplamı onbir ile yirmi arasındaki sayılardan gelir; çocuğun hangi sırada bittiği, kaç çocuk olduğu ya da makinenin kaç çekirdeği olduğu toplamı değiştirmez. Süreç tabanlı ölçeklemede iki şey birbirinden ayrılır: her işçinin hesapladığı değer koda aittir, kaç işçinin çalıştığı ise ortama. Bu ayrım dağıtımda önem kazanır: aynı kod dört işçiyle de sekiz işçiyle de aynı toplamı verir, ama aynı sürede değil.

Bir sunucu çalışırken çatallama maliyetini bir kez ödemek için işçileri başlangıçta, kod yüklendikten sonra çatallar. Buna **ön çatallama (preforking)** denir ve çocuklar ebeveynin belleğini ilk anda paylaşıp yalnız yazdıkları sayfaları kopyalar. **RB8.** Ön çatallamanın bellek kazancı burada ölçülmedi; çocuğun yazdığı sayfaların kopyalandığı varsayımı bir sonraki dersteki bellek ölçümüne bağlıdır.

#### Özet

- Çatallanan süreç ebeveynin belleğini devralır ama kendi kopyasına yazar; ebeveyne dönen tek şey çıkış kodu ve boruya yazılandır.
- İş dilimlenip boruyla toplanabilir; çocukların bitiş sırası belirsiz olduğundan sonuçlar numaraya göre sıralanır.
- İşçi sayısının belirleyeni **ortam**dır: ortam değişkeni varsa ondan, yoksa çekirdek sayısından gelir ve kanıtı `B- S- O+` biçimindedir.
- Sayı olmayan bir ortam değişkeni `ArgumentError` doğurur; varsayılana sessizce düşmez.
- Çocuğun dilim toplamı koda aittir; kaç işçinin çalıştığı ortama.

#### Sonraki Adım

İşçiler ayrı bellekle çalışınca her birinin ne kadar nesne ayırdığı sorusu doğar. Bir sonraki ders aynı bloğun neden bin bir nesne ayırdığını ve ilk koşumun neden sonrakilerden pahalı olduğunu ölçer.

### Bellek Yönetimi

Source: https://academia.sh/tr/kurslar/ruby-uretim/bellek-yonetimi

Aynı iş aynı kodla çalışırken bir bloğun kaç nesne ayırdığı da bir ölçüm sorusudur. Ders nesne ayırma sayılarını, çöp toplayıcının görünür sayaçlarını ve ilk koşumun neden sonrakilerden farklı olduğunu ölçer.

Önceki ders işçi sayısını ortamın belirlediğini gösterdi. Her işçinin kendi belleği olduğuna göre bir işçinin belleğinin neyle dolduğu sorusu sırada. Bu soru da kursun ölçü eksenine bağlanır: **bu sonucu ne belirliyor?** Bir bloğun ayırdığı nesne sayısı, makinenin ya da zamanın değil, bloğun kendisinin işidir. Bu dersin gözlemi `ayrilan nesne` satırıdır ve belirleyeni **kod**dur.

M11/K06'da bir bedelin hangi kaynağa (derleme · koşum · bellek · bekleme) **taşındığı** sorulmuştu. Burada ayrılan nesne sayısının **ne belirlediği** sorulur; bedel ölçülür ama sınıf belirleyendir. Çöp toplayıcının nesneleri nasıl yaşlandırıp topladığı ise M08/K04'te görülmüştü. Bu ders o mekanizmanın içine inmez; yalnız dışarıdan okunabilen sayıları, yani **nesne ayırma (allocation)** sayılarını ölçer. Çekirdeğin `belirleyen.rb` dosyası ve bu dersin kesiti `bellek.rb` aşağıda olduğu gibi verilir; ikisi de kaynaktır, çıktısı yoktur. Çekirdek bir modeldir ve bu dersin ölçümünde modelin yalnız kuralı kullanılır: ayrılan nesne sayısı çalışma ortamının değil bloğun işidir.

```ruby
# belirleyen.rb — bir kosum modeli ve bir sonucu neyin belirledigini siniflandiran yardimci

# Kosum: sonucun bagli olabilecegi uc girdi. Bu uc girdinin disinda kalan her sey koddur.
Kosum = Data.define(:kilit, :tohum, :ortam)

TABAN = Kosum.new(
  kilit: { "bicimleyici" => "1.4.2", "ayristirici" => "2.0.1" },
  tohum: 1,
  ortam: { saat: Time.utc(2026, 10, 16, 9, 0), cekirdek: 4, degiskenler: {} }
)

# Her degisken tek bir girdiyi degistirir; otekiler tabandaki gibi kalir.
DEGISKENLER = {
  bagimlilik: TABAN.with(kilit: { "bicimleyici" => "1.5.0", "ayristirici" => "2.1.0" }),
  sira: TABAN.with(tohum: 2),
  ortam: TABAN.with(ortam: { saat: Time.utc(2027, 3, 1, 9, 0), cekirdek: 8,
                             degiskenler: { "ISCI_SAYISI" => "3" } })
}

# Kural: hesap once tabanda, sonra her degiskende bir kez kosar. Sonucu degistiren ilk
# degisken (bagimlilik, sira, ortam sirasiyla) belirleyendir; hicbiri degistirmiyorsa kod.
def belirleyen(hesap)
  taban = hesap.call(TABAN)
  degisti = DEGISKENLER.to_h { |ad, kosum| [ad, hesap.call(kosum) != taban] }
  tur = degisti.key(true)&.to_s || "kod"
  kanit = degisti.map { |ad, d| "#{ad[0].upcase}#{d ? '+' : '-'}" }.join(" ")
  [tur, kanit, taban]
end
```

```ruby
# bellek.rb — blok uc kez kosar; son kosumda ayrilan nesneler sayilir
# Bellek: blok uc kez kosar; ilk kosumlar isinmadir, son kosumda ayrilan nesneler sayilir.
def ayrilan_nesne
  sonuc = nil
  3.times do
    once = GC.stat(:total_allocated_objects)
    yield
    sonuc = GC.stat(:total_allocated_objects) - once
  end
  sonuc
end
```

#### Aynı Binlik Dizi, Farklı Nesne Sayısı

`ayrilan_nesne` bir bloğu üç kez koşturur ve sonuncusunda `GC.stat(:total_allocated_objects)` sayacının farkını alır. Sayaç, o ana kadar ayrılan nesnelerin toplamıdır ve çöp toplayıcı bir nesneyi topladığında azalmaz; bu yüzden fark, bloğun yarattığı nesne sayısına eşittir. Aşağıdaki blok aynı bin elemanlı diziyi farklı yollarla kurar.

```ruby
# olcum.rb — ayni dizi, farkli nesne sayisi
require_relative "bellek"

olcumler = {
  "i.to_s" => -> { Array.new(1000) { |i| i.to_s } },
  "i" => -> { Array.new(1000) { |i| i } },
  ":sabit" => -> { Array.new(1000) { :sabit } },
  "\"sabit\"" => -> { Array.new(1000) { "sabit" } },
  "\"sabit\".freeze" => -> { Array.new(1000) { "sabit".freeze } },
  "-\"sabit\"" => -> { Array.new(1000) { -"sabit" } },
  "map" => -> { (1..1000).map { _1 * 2 } },
  "each_slice" => -> { (1..1000).each_slice(10).to_a },
  "<<" => -> { s = +""; 1000.times { s << "a" }; s },
  "+=" => -> { s = ""; 1000.times { s += "a" }; s }
}
olcumler.each { |ad, blok| puts "#{ad}: #{ayrilan_nesne(&blok)}" }
```

```
i.to_s: 1001
i: 1
:sabit: 1
"sabit": 1001
"sabit".freeze: 1
-"sabit": 1
map: 2
each_slice: 107
<<: 1002
+=: 2001
```

Her satırın okuması aynı mantığa dayanır. Tamsayı ve sembol nesne ayırmaz; dizinin kendisi tek nesnedir, bu yüzden `i` ve `:sabit` bir verir. `i.to_s` ve `"sabit"` her yinelemede yeni bir dize yaratır, bu yüzden bin bir verir. Donmuş dize değişmez ve aynı nesne yeniden kullanılır: `"sabit".freeze` ve `-"sabit"` bir verir. `map` dizi ve sonuç dizisi için iki, `each_slice` ise yüz parçalık ara dizilerle yüz yedi nesne ayırır. Son iki satır dize birleştirmeyi gösterir: `<<` aynı dizeye ekler, `+=` her adımda yeni bir dize kurar ve sayı yaklaşık iki katına çıkar.

Bu satırların hiçbiri süre ölçmez. Süre makineye, ayrılan nesne sayısı bloğa bağlıdır; bu dersin ölçüsü ikincisidir. **RB9.** Bu sayılar Ruby 4.0.7'de ölçüldü; dizi ve dize uygulamaları sürümler arasında değişirse sayılar da değişir.

Aynı blokları belirleyen kuralına verelim. Seçilen blok, bin elemanlı bir tamsayı dizisidir.

```ruby
# kural.rb — ayrilan nesne sayisini ne belirliyor
require_relative "belirleyen"
require_relative "bellek"

p belirleyen(->(_k) { ayrilan_nesne { Array.new(1000) { |i| i } } })
```

```
["kod", "B- S- O-", 1]
```

Sonuç birdir ve kanıt `B- S- O-`dur: kilit dosyasındaki sürümler, tohum ve ortam değişince sayı değişmedi. Belirleyen **kod**dur. Bloğun içinde `i.to_s` yerine `i` yazmak sayıyı bin birden bire indirir; bunu başka hiçbir girdi yapmaz. **RB10.** Kural yalnız çekirdeğin üç girdisini oynatır; bellek ayırmayı etkileyebilecek başka bir değişken, örneğin çöp toplayıcının ayarları, burada modelde yoktur.

#### Çöp Toplayıcının Görünür Sayaçları

Ayrılan nesneler çöp toplayıcının işidir; bir nesne ulaşılamaz hâle gelince toplanır, ulaşılabilir kaldıkça yaşar. Aşağıdaki blok bin dizeyi bir diziye koyar, çöp toplayıcıyı zorla çalıştırır ve dizelerin yaşadığını sayar. Sonra çöp toplayıcının sayaçlarında beklenen anahtarların var olduğuna ve bir nesnenin boyutunun pozitif olduğuna bakar. Sayaçların değeri basılmaz: her koşumda ve her makinede farklıdır.

```ruby
# gc.rb — cop toplayici sayaclari ve nesne boyutu
require "objspace"

tutulan = Array.new(1000) { |i| i.to_s }
GC.start
p ObjectSpace.each_object(String).count >= 1000
p %i[major_gc_count minor_gc_count heap_live_slots].all? { |anahtar| GC.stat.key?(anahtar) }
p ObjectSpace.memsize_of("a" * 1000).positive?
p tutulan.size
```

```
true
true
true
1000
```

Üç mantıksal sonuç ve bir sayı basıldı. Dizeler `GC.start` çağrısından sonra da yaşıyor, çünkü `tutulan` dizisi onlara ulaşıyor. Sayaçların adları var; değerlerine bakmak yerine varlıklarını sınadık. `ObjectSpace.memsize_of` bir nesnenin bellekte kapladığı bayt sayısını verir ve bin karakterlik dize için pozitiftir. **RB11.** Değerlerin kendisi, bayt sayısı dahil, ölçümde basılmadı; çünkü değişir ve değişen değer bu kursun taşıdığı sayıların güvenini zayıflatır.

#### Sınırlayıcı Ölçüm

`ayrilan_nesne` bloğu neden üç kez koşturur? Çünkü ilk koşum sonrakilerden farklı olabilir. İlk çağrıda yorumlayıcının iç önbellekleri henüz dolmamış olabilir ve bu hazırlık için ayrılan nesneler sonraki koşumlarda tekrarlanmaz. Nedenini burada ölçmüyoruz; yalnız farkın yönünü ölçüyoruz. Aşağıdaki blok aynı bloğu üç kez koşturur ve ilk sayıyı sonuncuyla karşılaştırır.

```ruby
# isinma.rb — ilk kosum isinmadir, olcumun kendisi bir girdidir
sayilar = Array.new(3) do
  once = GC.stat(:total_allocated_objects)
  Array.new(1000) { |i| i.to_s }
  GC.stat(:total_allocated_objects) - once
end
p sayilar.first > sayilar.last
```

```
true
```

İlk koşum sonuncudan fazla nesne ayırdı. Aynı blok, aynı kod, aynı girdiler; fark ölçümün kendisinden geliyor. Bu yüzden `ayrilan_nesne` ilk iki koşumu ısınma sayar ve yalnız sonuncunun farkını döndürür. Tek koşumluk bir ölçüm bu ısınma payını da taşırdı ve sayı bloğun gerçek işinden büyük çıkardı. Sonuç: ölçümün nasıl yapıldığı, sonucu belirleyen girdilerden biri gibi davranır; bir ölçümü paylaşırken ısınmanın nasıl ele alındığı da paylaşılmalıdır.

Bu, kuralın sınırını da gösterir. `belirleyen` üç girdiyi oynatır; ölçüm yönteminin kendisini oynatmaz. Isınma payı kodun değil, ölçümün bir özelliğidir ve üç değişkenin hiçbirine bağlı değildir. Bu yüzden `ayrilan_nesne` içinde saklıdır, kuralın dışında. **RB12.** Isınmanın büyüklüğü burada yalnız yönüyle ölçüldü; bir ya da iki ek koşumun yeterli olup olmadığı bu ölçümden çıkmaz.

#### Özet

- Bir bloğun ayırdığı nesne sayısını **kod** belirler: kural üç değişkende de aynı sayıyı gördü.
- Tamsayı ve sembol nesne ayırmaz; donmuş dize aynı nesneyi yeniden kullanır; `i.to_s` ve `"sabit"` her yinelemede yeni bir dize yaratır.
- `+=` dize birleştirmede `<<`'in yaklaşık iki katı nesne ayırır.
- Çöp toplayıcının sayaçları `GC.stat` ile, nesne boyutu `ObjectSpace.memsize_of` ile okunur; değerler basılmaz, varlıkları ve işaretleri sınanır.
- İlk koşum ısınma payı taşır; ölçüm yönteminin kendisi sonucu etkileyen bir girdidir.

#### Sonraki Adım

Bir bloğun kaç nesne ayırdığı biliniyor; bir programın hangi yönteminin sıcak yol olduğu henüz bilinmiyor. Bir sonraki ders çağrı sayılarını sayar ve yavaş olanın çağrı sayısından çıkarılamayacağını gösterir.

### Profil Çıkarma

Source: https://academia.sh/tr/kurslar/ruby-uretim/profil-cikarma

Bir programın hangi yöntemi sıcak yoldur sorusu çağrı sayısıyla sorulur ve çağrı sayısı koda aittir. Ders TracePoint ile çağrıları sayar, düzeltmenin sayıyı nasıl değiştirdiğini ölçer ve süreye dayalı sıralamanın neden ortama bağlı olduğunu gösterir.

Önceki ders bir bloğun kaç nesne ayırdığını sordu. Bu ders bir programın hangi yöntemde ne kadar çalıştığını sorar; ancak süreyle değil, çağrı sayısıyla. Kursun ölçü ekseni değişmez: **bu sonucu ne belirliyor?** Bu dersin gözlemi `sicak yol` satırıdır ve belirleyeni **kod**dur.

**Profil çıkarma (profiling)**, bir programın zamanının ya da çağrılarının nerede toplandığını ölçme işidir. M10/K03'te bir kodun **kaç kez koştuğu** ölçülmüştü. Profil çıkarma da çağrı sayar, ama burada sınıf sayının kendisi değil, sayıyı **ne belirlediğidir**. Karşılaştırmalı ölçüm ve profil çıkarmanın ilkeleri M10/K05'te görülmüştü; bu ders yalnız Ruby'nin standart kütüphanesindeki araçlarla, ürün ya da profil aracı kullanmadan, iki şeyi ölçer: yöntem çağrılarının sayısı ve sürenin türü. **Sıcak yol (hot path)**, en çok çağrılan yöntemdir. Çekirdeğin `belirleyen.rb` dosyası ve bu dersin kesiti `profil.rb` aşağıda olduğu gibi verilir; ikisi de kaynaktır, çıktısı yoktur. Kesitteki `sicak_yol` çağrıları `TracePoint` ile sayar; yalnız programın kendi tanımladığı yöntemler sayılır.

```ruby
# belirleyen.rb — bir kosum modeli ve bir sonucu neyin belirledigini siniflandiran yardimci

# Kosum: sonucun bagli olabilecegi uc girdi. Bu uc girdinin disinda kalan her sey koddur.
Kosum = Data.define(:kilit, :tohum, :ortam)

TABAN = Kosum.new(
  kilit: { "bicimleyici" => "1.4.2", "ayristirici" => "2.0.1" },
  tohum: 1,
  ortam: { saat: Time.utc(2026, 10, 16, 9, 0), cekirdek: 4, degiskenler: {} }
)

# Her degisken tek bir girdiyi degistirir; otekiler tabandaki gibi kalir.
DEGISKENLER = {
  bagimlilik: TABAN.with(kilit: { "bicimleyici" => "1.5.0", "ayristirici" => "2.1.0" }),
  sira: TABAN.with(tohum: 2),
  ortam: TABAN.with(ortam: { saat: Time.utc(2027, 3, 1, 9, 0), cekirdek: 8,
                             degiskenler: { "ISCI_SAYISI" => "3" } })
}

# Kural: hesap once tabanda, sonra her degiskende bir kez kosar. Sonucu degistiren ilk
# degisken (bagimlilik, sira, ortam sirasiyla) belirleyendir; hicbiri degistirmiyorsa kod.
def belirleyen(hesap)
  taban = hesap.call(TABAN)
  degisti = DEGISKENLER.to_h { |ad, kosum| [ad, hesap.call(kosum) != taban] }
  tur = degisti.key(true)&.to_s || "kod"
  kanit = degisti.map { |ad, d| "#{ad[0].upcase}#{d ? '+' : '-'}" }.join(" ")
  [tur, kanit, taban]
end
```

```ruby
# profil.rb — programin tanimladigi yontemlerin cagrilari sayilir
# Profil: programin tanimladigi yontemlerin cagrilari sayilir; en cok cagrilan sicak yoldur.
def sicak_yol
  sayim = Hash.new(0)
  iz = TracePoint.new(:call) { |t| sayim[t.method_id] += 1 if t.defined_class == Object }
  iz.enable { yield }
  sayim.max_by { |_, n| n }
end

def para_bicimle(kurus) = format("%d.%02d", kurus / 100, kurus % 100)
def satir_bicimle(satir) = "#{satir[:ad]} #{para_bicimle(satir[:birim])} #{para_bicimle(satir[:toplam])}"
def rapor(satirlar) = satirlar.map { |s| satir_bicimle(s) }.join("\n")
```

#### Çağrıları Saymak

Bir yüz satırlık rapor kurulur: her satırın bir adı, birim fiyatı ve toplamı vardır. `rapor` her satırı `satir_bicimle`ye verir, o da hem birim fiyatı hem toplamı `para_bicimle` ile biçimler. Aşağıdaki blok bu raporu `TracePoint` altında koşturur ve her yöntemin çağrı sayısını çıkarır.

```ruby
# sayim.rb — her yontemin cagri sayisi
require_relative "belirleyen"
require_relative "profil"

def cagri_sayimi
  sayim = Hash.new(0)
  iz = TracePoint.new(:call) { |t| sayim[t.method_id] += 1 if t.defined_class == Object }
  iz.enable { yield }
  sayim.sort_by { |ad, n| [-n, ad] }
end

satirlar = Array.new(100) { |i| { ad: "urun#{i}", birim: 1250, toplam: 1250 * (i + 1) } }
p cagri_sayimi { rapor(satirlar) }
p sicak_yol { rapor(satirlar) }
p belirleyen(->(_k) { sicak_yol { rapor(satirlar) } })
```

```
[[:para_bicimle, 200], [:satir_bicimle, 100], [:rapor, 1]]
[:para_bicimle, 200]
["kod", "B- S- O-", [:para_bicimle, 200]]
```

`para_bicimle` iki yüz kez çağrıldı: yüz satırın her biri onu iki kez çağırdı. `satir_bicimle` yüz, `rapor` bir kez çağrıldı. İkinci satır `sicak_yol` sonucudur, yani en çok çağrılan yöntem ve sayısı. Üçüncü satır kuralı çalıştırır: kilit dosyasındaki sürümler, tohum ve ortam değişince sonuç değişmedi, belirleyen **kod**dur. Çağrı sayısı raporun satır sayısından ve yöntemlerin birbirini çağırma düzeninden gelir; ikisi de kodun işidir. **RB13.** `defined_class == Object` süzgeci yalnız ana düzeyde tanımlı yöntemleri sayar; başka sınıflardaki yöntemler bu sayıma girmez.

Süzgeç yalnız programın kendi yöntemlerini seçer. Ruby'nin kendi yerleşik yöntemleri `:call` olayını üretmez, `:c_call` olayını üretir. Aynı raporu `:c_call` ile saymak, hangi yerleşik yöntemlerin sıcak olduğunu gösterir.

```ruby
# yerlesik.rb — yerlesik yontemlerin cagri sayisi
require_relative "profil"

satirlar = Array.new(100) { |i| { ad: "urun#{i}", birim: 1250, toplam: 1250 * (i + 1) } }
sayim = Hash.new(0)
iz = TracePoint.new(:c_call) { |t| sayim[t.method_id] += 1 }
iz.enable { rapor(satirlar) }
p sayim.sort_by { |ad, n| [-n, ad] }.first(5)
```

```
[[:[], 300], [:%, 200], [:/, 200], [:format, 200], [:join, 1]]
```

En çok çağrılan yerleşik yöntem `[]` oldu: her satır adı, birim fiyatı ve toplamı üç kez indeksler. `%`, `/` ve `format` ikişer yüz kez geldi, çünkü `para_bicimle` iki yüz kez çağrıldı ve her çağrı bir bölme, bir kalan alma ve bir `format` yaptı. Sayılar kodun yapısını yansıtır; satır sayısını ikiye katlasak satıra bağlı sayıların hepsi ikiye katlanırdı. **RB14.** Altıncı sıra ve sonrası burada gösterilmedi; beşinci sıradaki `join` bir kez çağrıldı ve geri kalanlar bu sayımdan çıkmaz.

#### Düzeltme ve Yeni Sayım

Sıcak yol bulunduktan sonra ilk soru şudur: bu çağrıların hepsi gerekli mi? Raporda birim fiyat her satırda aynıdır ve yalnız bir kez basılması yeterlidir. Düzeltilmiş sürüm her satırda toplamı bir kez biçimler.

```ruby
# duzelt.rb — para_bicimle her satirda bir kez cagrilir
require_relative "profil"

def satir_bicimle_kisa(satir) = "#{satir[:ad]} #{para_bicimle(satir[:toplam])}"
def rapor_kisa(satirlar) = satirlar.map { |s| satir_bicimle_kisa(s) }.join("\n")

def cagri_sayimi
  sayim = Hash.new(0)
  iz = TracePoint.new(:call) { |t| sayim[t.method_id] += 1 if t.defined_class == Object }
  iz.enable { yield }
  sayim.sort_by { |ad, n| [-n, ad] }
end

satirlar = Array.new(100) { |i| { ad: "urun#{i}", birim: 1250, toplam: 1250 * (i + 1) } }
p cagri_sayimi { rapor_kisa(satirlar) }
```

```
[[:para_bicimle, 100], [:satir_bicimle_kisa, 100], [:rapor_kisa, 1]]
```

`para_bicimle` artık yüz kez çağrılıyor ve `satir_bicimle_kisa` ile aynı sayıya düştü. Sıcak yol tek bir yöntem olmaktan çıktı: iki yöntem eşit çağrılıyor. Düzeltme çağrıyı yarıya indirdi ama yavaşın hangisi olduğu sorusunu çözmedi. **RB15.** Bu ölçüm çağrı sayısının yarıya indiğini gösterir; sürenin de yarıya indiğini göstermez.

#### Sınırlayıcı Ölçüm

Çağrı sayısı, en çok çalışan yöntemi söyler; en yavaş yöntemi söylemez. Bir yöntem yüz kez çağrılıp her çağrıda uzun sürebilir, bir başkası iki yüz kez çağrılıp her seferinde kısa sürebilir. Süre ayrı bir ölçümdür ve ayrı bir belirleyeni vardır. İki araç süreyi ölçer: `Benchmark.realtime` ve `Process.clock_gettime(Process::CLOCK_MONOTONIC)`. İkisi de kayan noktalı bir sayı döndürür; sayının kendisi basılmaz çünkü her koşumda ve her makinede farklıdır.

```ruby
# sure.rb — sure olcen iki arac, ikisi de Float dondurur
require "benchmark"
require_relative "profil"

satirlar = Array.new(100) { |i| { ad: "urun#{i}", birim: 1250, toplam: 1250 * (i + 1) } }
p Benchmark.realtime { rapor(satirlar) }.class
p Process.clock_gettime(Process::CLOCK_MONOTONIC).class
```

```
Float
Float
```

İki araç da `Float` döndürdü; değerleri basılmadı. Bir ölçüm süreye dayanıyorsa ve sonuç "en yavaş hangisi" ise, belirleyen **ortam**dır: makine yüklüyse, çekirdek farklıysa ya da önbellek ısınmışsa sıra değişir. Çağrı sayısı ise kodun işidir ve bu yüzden her yerde aynı çıkar. İkisi aynı soruyu yanıtlamaz: hangisi daha çok çalışıyor sorusunun yanıtı koddadır; hangisi daha çok zaman alıyor sorusunun yanıtı makinededir.

Bu dersin sınırlayıcısı budur: sıcak yol çağrı sayısıyla belirlenir ve kod olarak sınıflandırılır, ama "en yavaş" bu sınıflamanın dışındadır. **RB16.** Süre ölçümü burada yalnız türüyle gösterildi; hangi yöntemin gerçekten en yavaş olduğu bu derste ölçülmedi ve çağrı sayısından çıkarılmamalıdır.

#### Özet

- Programın kendi yöntemlerinin çağrı sayısı `TracePoint` ile `:call` olayı üzerinden sayılır; yerleşik yöntemler `:c_call` ile ayrıca sayılır.
- Sıcak yolu çağrı sayısı belirler, çağrı sayısını ise **kod**: kural üç değişkende de aynı yöntemi buldu.
- Düzeltmeden sonra yeni sayım yapılır; sıcak yol tek yöntemden birkaçına yayılabilir.
- Süre `Benchmark.realtime` ya da `Process.clock_gettime` ile ölçülür ve ortama bağlıdır; çağrı sayısı ile süre aynı soruyu yanıtlamaz.

#### Sonraki Adım

Kod, çağrı sayıları ve ayrılan nesneler ölçüldü; elde bir yayın çıkarılacak bir uygulama var. Son ders yayının içindeki üç şeyi, varlık adını, sürüm dizinini ve bildirimi ayrı ayrı sorgular ve her birinin belirleyenini bulur.

### Dağıtım

Source: https://academia.sh/tr/kurslar/ruby-uretim/dagitim

Üretime çıkan yayının içinde ne olduğu üç ayrı soruyla sorulur. Ders varlık adını, sürüm dizinini ve yayın bildirimini ayrı ayrı ölçer, sembolik bağla atomik geçişi ve geri almayı sınar ve kursun dokuz ölçümünü tek tabloda toplar.

Önceki ders sıcak yolun çağrı sayısından, sürenin ise ortamdan geldiğini gösterdi. Elde ölçülmüş, test edilmiş, paketlenmiş bir uygulama var; geriye üretime çıkarmak kalıyor. Bu da kursun ölçü eksenine bağlanır: **bu sonucu ne belirliyor?** Üretime çıkan şey tek bir nesne değildir. Bir yayının üç parçası vardır ve her birinin belirleyeni farklıdır.

**Dağıtım (deployment)**, yayını hedef makineye taşıyıp yürürlüğe koyma işidir. M12/K05'te dağıtımın aşamaları ve araçları görülmüştü; bu ders aşamaları yinelemez, yayının içindeki üç değerin ne ürettiğini ölçer. Çekirdeğin `belirleyen.rb` dosyası ve bu dersin kesiti `dagitim.rb` aşağıda olduğu gibi verilir; ikisi de kaynaktır, çıktısı yoktur. Çekirdek bir modeldir: saat, çekirdek sayısı ve kilit dosyasının içeriği tanımdan gelir.

```ruby
# belirleyen.rb — bir kosum modeli ve bir sonucu neyin belirledigini siniflandiran yardimci

# Kosum: sonucun bagli olabilecegi uc girdi. Bu uc girdinin disinda kalan her sey koddur.
Kosum = Data.define(:kilit, :tohum, :ortam)

TABAN = Kosum.new(
  kilit: { "bicimleyici" => "1.4.2", "ayristirici" => "2.0.1" },
  tohum: 1,
  ortam: { saat: Time.utc(2026, 10, 16, 9, 0), cekirdek: 4, degiskenler: {} }
)

# Her degisken tek bir girdiyi degistirir; otekiler tabandaki gibi kalir.
DEGISKENLER = {
  bagimlilik: TABAN.with(kilit: { "bicimleyici" => "1.5.0", "ayristirici" => "2.1.0" }),
  sira: TABAN.with(tohum: 2),
  ortam: TABAN.with(ortam: { saat: Time.utc(2027, 3, 1, 9, 0), cekirdek: 8,
                             degiskenler: { "ISCI_SAYISI" => "3" } })
}

# Kural: hesap once tabanda, sonra her degiskende bir kez kosar. Sonucu degistiren ilk
# degisken (bagimlilik, sira, ortam sirasiyla) belirleyendir; hicbiri degistirmiyorsa kod.
def belirleyen(hesap)
  taban = hesap.call(TABAN)
  degisti = DEGISKENLER.to_h { |ad, kosum| [ad, hesap.call(kosum) != taban] }
  tur = degisti.key(true)&.to_s || "kod"
  kanit = degisti.map { |ad, d| "#{ad[0].upcase}#{d ? '+' : '-'}" }.join(" ")
  [tur, kanit, taban]
end
```

```ruby
# dagitim.rb — varlik adi icerigin ozetini tasir; yayina kilitteki surumler kurulur
require "digest"

# Dagitim: varlik adi icerigin ozetini tasir; yayina kilitteki surumler kurulur.
def varlik_adi(ad, icerik) = ad.sub(/(\.\w+)\z/) { "-#{Digest::SHA256.hexdigest(icerik)[0, 8]}#{Regexp.last_match(1)}" }

def yayin_bildirimi(kosum)
  [varlik_adi("uygulama.css", "body { margin: 0 }"), *kosum.kilit.map { |ad, s| "#{ad}-#{s}" }]
end
```

#### Varlık Adı İçeriğin Özetinden Gelir

Üretimde bir stil dosyası ya da betik, adında içeriğinin **içerik özeti (digest)** ile yayımlanır. İçerik değişince ad değişir ve tarayıcıdaki eski kopya kendiliğinden geçersiz olur. `varlik_adi` içeriğin SHA-256 özetinin ilk sekiz karakterini dosya adının uzantısından önce ekler. Aşağıdaki blok aynı dosya adını iki ayrı içerikle çağırır ve kuralı çalıştırır.

```ruby
# varlik.rb — varlik adi icerige baglidir
require_relative "belirleyen"
require_relative "dagitim"

puts varlik_adi("uygulama.css", "body { margin: 0 }")
puts varlik_adi("uygulama.css", "body { margin: 1px }")
p belirleyen(->(_k) { varlik_adi("uygulama.css", "body { margin: 0 }") })
```

```
uygulama-997facec.css
uygulama-42043fc8.css
["kod", "B- S- O-", "uygulama-997facec.css"]
```

İçerikteki `0` yerine `1px` yazılınca ad değişti. Kural üç değişkenin hiçbirinde farklı bir ad görmedi: kilit dosyasının sürümleri, tohum ve ortam adı etkilemedi. Belirleyen **kod**dur; ad, içeriğin özetinden gelir ve içerik kodun parçasıdır. **RB17.** Özetin ilk sekiz karakteri çakışma olasılığını sıfırlamaz; bu ölçüm iki içerik için ayrı ad gösterir ve herhangi iki içerik için ayrı ad garantisi vermez.

#### Sürüm Dizini Saatten Gelir

Her yayın hedef makinede kendi dizinine konur, ad olarak yayının saatini taşır. Böylece eski yayın silinmeden yanında durur. Dizin adı saatten kurulur ve saat modelde ortamın bir girdisidir.

```ruby
# surum.rb — surum dizini saatten kurulur
require_relative "belirleyen"

surum_dizini = ->(saat) { "surumler/#{saat.strftime('%Y%m%d%H%M%S')}" }
p belirleyen(->(k) { surum_dizini.call(k.ortam[:saat]) })
```

```
["ortam", "B- S- O+", "surumler/20261016090000"]
```

Tabanın saati 16 Ekim 2026, dokuz sıfır sıfırdır ve dizin adı bu zamanı taşır. Kural bu hesabı üç değişkende koşturdu: yalnız ortamın saati farklıydı, sonuç değişti. Kanıt `B- S- O+`, belirleyen **ortam**dır. Hesap deterministik bir işlemdir, ama girdisi saattir ve saat dışarıdan gelir. **RB18.** Gerçek saat bu ölçümde kullanılmadı; çekirdeğin sabit saati kullanıldı ve gerçek bir dağıtımda ad her yayında farklı olur.

#### Bildirim Kilitteki Sürümleri Taşır

Yayın bildirimi, yayının hangi varlık adını ve hangi paket sürümlerini içerdiğini söyler. Varlık adı koddan gelir; paket sürümleri kilit dosyasından.

```ruby
# bildirim.rb — yayin bildirimi kilitteki surumleri tasir
require_relative "belirleyen"
require_relative "dagitim"

p yayin_bildirimi(DEGISKENLER[:bagimlilik])
p belirleyen(->(k) { yayin_bildirimi(k) })
```

```
["uygulama-997facec.css", "bicimleyici-1.5.0", "ayristirici-2.1.0"]
["bagimlilik", "B+ S- O-", ["uygulama-997facec.css", "bicimleyici-1.4.2", "ayristirici-2.0.1"]]
```

Birinci satır, kilidi `1.5.0` ve `2.1.0` olan değişkenin bildirimidir: varlık adı aynı kalır, paket sürümleri değişir. İkinci satır kuralı çalıştırır. Kural kilit değişkeninde farkı görür ve belirleyen **bağımlılık**tır. Aynı bildirimde varlık adı koddan gelir, ama kural ilk değiştiren belirleyeni verir ve değiştiren bağımlılık olunca kod ikinci planda kalır. Kanıt `B+ S- O-`. **RB19.** Kural tek bir belirleyen verir; birden çok girdi aynı anda değişirse sıradaki ilk değiştiren seçilir ve diğerleri kanıt sütununda görünür.

#### Sembolik Bağ, Atomik Geçiş ve Geri Alma

Yeni yayın hazırlanınca yürürlükteki yayın bir **sembolik bağ (symlink)** ile gösterilir; geçiş, bağın bir adımda değiştirilmesidir. Bunu geçici bir dizinde sınayalım: iki sürüm dizini, bir bağ, atomik geçiş ve geri alma. Bağ önce geçici bir adla kurulur, sonra `File.rename` ile asıl ada taşınır; yeniden adlandırma tek adımdır, bu yüzden aradaki hiçbir anda bağ yok değildir.

```ruby
# gecis.rb — sembolik bagla atomik gecis ve geri alma
require "tmpdir"

def bagla(hedef)
  File.symlink(hedef, "gecerli.gecici")
  File.rename("gecerli.gecici", "gecerli")
end

Dir.mktmpdir do |dizin|
  Dir.chdir(dizin) do
    Dir.mkdir("surumler")
    Dir.mkdir("surumler/20261016090000")
    Dir.mkdir("surumler/20261016100000")
    bagla("surumler/20261016090000")
    puts File.readlink("gecerli").split("/").last
    bagla("surumler/20261016100000")
    puts File.readlink("gecerli").split("/").last
    bagla("surumler/20261016090000")
    puts File.readlink("gecerli").split("/").last
  end
end
```

```
20261016090000
20261016100000
20261016090000
```

Bağ önce dokuz sıfır sıfır sürümünü, sonra on sıfır sıfır sürümünü gösterdi; geri alma ise yeni bir bağ kurup dokuz sıfır sıfıra döndü. Geri almak eski dizini yeniden yaratmak değildir; eski dizin hiç silinmediği için yalnız bağ değiştirilir. Geçişten önce bir **sağlık denetimi (health check)** yeni yayının yanıt verdiğine bakar ve karar verir: iki istek de başarılıysa geç, biri başarısızsa geri al.

```ruby
# saglik.rb — saglik denetimi gecis ya da geri alma kararini verir
karar = ->(kodlar) { kodlar.all?(200) ? "gecis" : "geri al" }
puts karar.call([200, 200])
puts karar.call([200, 503])
```

```
gecis
geri al
```

İki istek de iki yüzlük dönünce karar geçiş, biri beş yüz üç dönünce geri almadır. **RB20.** Dizin ve bağ bir geçici dizinde ve tek süreçte sınandı; ağ üzerinden gelen istekler ve eşzamanlı okuyan süreçler bu ölçümde yoktur. **Kesintisiz dağıtım (zero-downtime deployment)** için gereken ek düzenek bu derste ölçülmedi.

#### Sınırlayıcı Ölçüm

Aynı yayının üç parçası, üç ayrı soruya üç ayrı yanıt verdi. Aşağıdaki blok bu üçünü aynı anda sorar.

```ruby
# yayin.rb — ayni yayinin uc parcasi, uc belirleyen
require_relative "belirleyen"
require_relative "dagitim"

adlar = {
  "varlik adi" => ->(_k) { varlik_adi("uygulama.css", "body { margin: 0 }") },
  "surum dizini" => ->(k) { "surumler/#{k.ortam[:saat].strftime('%Y%m%d%H%M%S')}" },
  "bildirim" => ->(k) { yayin_bildirimi(k) }
}
adlar.each { |ad, hesap| puts "#{ad}: #{belirleyen(hesap).first}" }
```

```
varlik adi: kod
surum dizini: ortam
bildirim: bagimlilik
```

Varlık adını kod belirler, sürüm dizinini ortam, bildirimi bağımlılık. Üçü aynı yayına aittir; bir yayını tek bir belirleyene bağlamak yanlıştır. Bildirimde ise kural kod değil bağımlılığı verdi: bildirim hem kodun hem kilidin ürünü olsa da `belirleyen` ilk değişen girdiyi seçer ve kilit, kuralda kodun önündedir. Bu dersin gözlemi `yayin bildirimi` satırıdır ve belirleyeni **bağımlılık**tır.

#### Özet

- Varlık adını içeriğin özeti belirler; içerik koddan geldiği için belirleyen **kod**dur.
- Sürüm dizininin adı saatten gelir; belirleyen **ortam**dır.
- Yayın bildirimi kilitteki sürümleri taşır; belirleyen **bağımlılık**tır ve kanıtı `B+ S- O-`dir.
- Sembolik bağ ve `File.rename` geçişi atomik yapar; geri alma eski dizini silmeden yeni bir bağ kurmaktır.
- Aynı yayının üç parçası üç ayrı belirleyene sahiptir.

#### Kurs Kapanışı

Kursun her dersi aynı soruyu sordu: bu sonucu ne belirliyor. Tablo dokuz dersin ölçtüğü değeri, o dersin belirleyenini ve sınırlayıcı ölçümünün söylediği şeyi yan yana koyar. Belirleyen sütunu her dersin `## Özet` bölümünde yazılı olandır.

| Ders | Ölçülen değer | Belirleyen | Sınırlayıcı ölçüm |
|---|---|---|---|
| Gem Yapısı | paket dosya adı `olcer-0.3.0.gem` | kod | dosya listesini çalışma dizininden toplayan tanımda `[]` ile `["lib/olcer.rb"]` ikisinde de kod |
| Bağımlılık Yönetimi | kilitli sürüm `1.4.2` | bağımlılık | `~> 1.4.0` ile 1.5.0 kilidi `ArgumentError` verir; kural hata veren hesabı sınıflandırmaz |
| Test Çerçeveleri | test sonucu `[]` | sıra | her test `ayar.dup` kullanınca sonuç `["kod", "B- S- O-", []]` |
| Sahteleme ve Test Verisi | saati okuyan test `true` | ortam | tohumlu veri `["sira", "B- S+ O-", [37, 12, 72]]` |
| Eşzamanlılık Modeli | kilitsiz sayaç `6` | sıra | Ractor paylaşılan durumu kaldırır; dondurma yüzeysel kalır |
| Süreç Tabanlı Ölçekleme | işçi sayısı `4` | ortam | çocuk süreçteki dilim toplamı `["kod", "B- S- O-", 155]` |
| Bellek Yönetimi | ayrılan nesne `1001` | kod | ilk koşum ısınma payı taşır |
| Profil Çıkarma | sıcak yol `[:para_bicimle, 200]` | kod | süre ortamdır, çağrı sayısı koddur |
| Dağıtım | yayın bildirimi | bağımlılık | aynı yayının üç parçası üç belirleyen |

Dokuz dersin belirleyen dağılımı: **3 kod · 2 bağımlılık · 2 sıra · 2 ortam**. Bu, çekirdeğin dokuz gözleminin dağılımıyla aynıdır, çünkü çekirdek her ders için bir gözlem taşır. İki sonuç tablodan okunur. Birincisi, aynı koda bakan iki ders bile farklı belirleyen verebilir: kilitsiz sayaç sıraya, işçi sayısı ortama bağlıdır. İkincisi, sınırlayıcı ölçümlerin çoğu dersin belirleyeninin yanında başka bir belirleyenin de çalıştığını gösterdi; bir yayın, bir test ya da bir ölçüm tek bir kaynağa bağlanmaz. **RB21.** Tablo bu kursun dokuz gözleminin dağılımını verir; Ruby uygulamalarının tamamının dağılımı hakkında bir iddia taşımaz.

Kurs burada kapanır ve M13 de onunla kapanır. Müfredat dört soru sordu. Birincisi, bu iletiyi kim yanıtlıyor. İkincisi, bu yöntemi kim yazdı. Üçüncüsü, bu değer hangi isteğe kadar kalıyor. Dördüncüsü, bu sonucu ne belirliyor. Her soru bir önceki sorunun bıraktığı boşluğa girdi: yanıtlayanı bulmak yazanı, yazanı bulmak kalışı, kalışı bulmak ise sonucun kaynağını sormayı gerektirdi. Bu kurs sonuncusunu yalnız ölçerek yanıtladı; bir sonucun neden belirli bir kaynağa bağlandığı sorusu ölçümün ötesinde kaldı.

