---
title: 'Yan Etkilerin Sınıra İtilmesi'
source: 'https://academia.sh/tr/kurslar/paradigmalar/yan-etkilerin-sinira-itilmesi'
course: 'Programlama Paradigmaları'
language: tr
updated: '2026-08-17T18:10:41+00:00'
license: 'CC BY-SA 4.0'
---

# Yan Etkilerin Sınıra İtilmesi

Saf çekirdek, kirli kabuk düzeni: dosya okuma, saat ve kayıt yazmanın hesabın içinden dış katmana taşınması, iki düzende sahte bağımlılık gerektiren deneme sayısının ve dış dünyaya dokunan satır sayısının karşılaştırılması.

Önceki dersin dört adımı da saftı: hiçbiri okumadığı bir şeye bakmadı, hiçbiri dışarı
yazmadı. Gerçek bir ücretlendirme koşumu ise saf değildir. Gönderiler bir dosyadan okunur,
gece tarifesinin geçerli olup olmadığı saate bakılarak belirlenir, sonuçlar bir dosyaya ve
bir kayıt akışına yazılır.

Bu üç bağımlılık ortadan kaldırılamaz; bir programın işe yaraması için dış dünyayla
konuşması gerekir. Kaldırılabilecek olan, bunların ücret kurallarıyla **aynı yerde**
durmasıdır. Bu ders o ayrımı kurar ve iki düzeni ölçer: çekirdeğin sınanması için kaç sahte
bağımlılık gerekiyor, dış dünyaya dokunan satırlar hangi dosyalarda toplanıyor.

## Serpiştirilmiş Düzen

İlk düzen doğal olanıdır: koşum baştan sona yazılır, her şey geldiği yerde yapılır. Tarife
verisi iki tasarımın da ortak girdisidir ve ayrı bir dosyada durur.

```js
// tarife.mjs — iki tasarimin da kullandigi tarife verisi
export const TARIFE = {
  asgariUcret: 4990,
  geceEki: 1500,
  kademeler: [
    { ustAgirlikGram: 1000, ucret: 4990 },
    { ustAgirlikGram: 5000, ucret: 6490 },
    { ustAgirlikGram: 15000, ucret: 10900 },
  ],
  bolgeKatsayisi: { 1: 1.0, 2: 1.15, 3: 1.35 },
};
```

```js
// icice.mjs — dosya okuma, saat ve kayit yazma hesabin icine serpistirilmis
import fs from "node:fs";
import { TARIFE } from "./tarife.mjs";

export function gunlukKosum(girdiYolu, ciktiYolu, kayitYolu) {
  const gonderiler = JSON.parse(fs.readFileSync(girdiYolu, "utf8"));
  const saat = new Date(Date.now()).getUTCHours();
  const satirlar = [];
  let toplam = 0;
  for (const g of gonderiler) {
    const kademe = TARIFE.kademeler.find((k) => g.agirlikGram <= k.ustAgirlikGram)
      ?? TARIFE.kademeler.at(-1);
    let ucret = Math.round(kademe.ucret * TARIFE.bolgeKatsayisi[g.bolge]);
    if (g.sozlesmeli) ucret = Math.round(ucret * 0.88);
    if (saat >= 20 || saat < 6) ucret += TARIFE.geceEki;
    ucret = Math.max(TARIFE.asgariUcret, ucret);
    satirlar.push(`${g.kod},${ucret}`);
    toplam += ucret;
    fs.appendFileSync(kayitYolu, `${g.kod} ucretlendirildi\n`);
  }
  fs.writeFileSync(ciktiYolu, `${satirlar.join("\n")}\n`);
  fs.appendFileSync(kayitYolu, `toplam ${toplam}\n`);
  return toplam;
}
```

Bu gövdede beş satır dış dünyaya dokunur: bir okuma, bir saat sorgusu, iki kayıt ekleme ve
bir yazma. Kalan satırlar ücret kurallarıdır. İkisi iç içe geçtiği için kurallara ulaşmanın
tek yolu koşumun tamamını çalıştırmaktır.

## Saf Çekirdek, Kirli Kabuk

İkinci düzen aynı işi iki parçaya böler. **Çekirdek**, kararı veren saf fonksiyondur:
gönderileri, tarifeyi ve saati parametre olarak alır; yazılacak satırları, düşülecek kayıt
satırlarını ve toplamı **döndürür**. Hiçbirini kendisi yapmaz.

```js
// cekirdek.mjs — saf cekirdek: gonderiler, tarife ve saat disaridan verilir
export function planla({ gonderiler, tarife, saat }) {
  const gece = saat >= 20 || saat < 6;
  const satirlar = [];
  const kayitlar = [];
  let toplam = 0;
  for (const g of gonderiler) {
    const kademe = tarife.kademeler.find((k) => g.agirlikGram <= k.ustAgirlikGram)
      ?? tarife.kademeler.at(-1);
    let ucret = Math.round(kademe.ucret * tarife.bolgeKatsayisi[g.bolge]);
    if (g.sozlesmeli) ucret = Math.round(ucret * 0.88);
    if (gece) ucret += tarife.geceEki;
    ucret = Math.max(tarife.asgariUcret, ucret);
    satirlar.push(`${g.kod},${ucret}`);
    kayitlar.push(`${g.kod} ucretlendirildi`);
    toplam += ucret;
  }
  return { satirlar, kayitlar: [...kayitlar, `toplam ${toplam}`], toplam };
}
```

Kritik ayrım döndürülen değerdedir. Çekirdek "şu satırlar yazılsın" diye bir **tanım**
üretir; yazma işini yapmaz. Kayıt satırları da metin olarak döner, akışa basılmaz. Saat bir
sayıdır, sorgulanan bir kaynak değildir.

**Kabuk** bu tanımı alır ve gerçekleştirir. Okuma, saat ve yazma yalnızca burada geçer.

```js
// kabuk.mjs — kirli kabuk: okuma, saat ve yazma yalnizca bu dosyada
import fs from "node:fs";
import { TARIFE } from "./tarife.mjs";
import { planla } from "./cekirdek.mjs";

export function gunlukKosum(girdiYolu, ciktiYolu, kayitYolu) {
  const gonderiler = JSON.parse(fs.readFileSync(girdiYolu, "utf8"));
  const saat = new Date(Date.now()).getUTCHours();
  const plan = planla({ gonderiler, tarife: TARIFE, saat });
  fs.writeFileSync(ciktiYolu, `${plan.satirlar.join("\n")}\n`);
  fs.appendFileSync(kayitYolu, `${plan.kayitlar.join("\n")}\n`);
  return plan.toplam;
}
```

Kabuğun içinde tek bir iş kuralı yoktur: okur, çağırır, yazar. Karar veren her satır
çekirdektedir, karar vermeyen her satır kabuktadır.

## Testin Ödediği Bedel

Fark testte sayılabilir hâle gelir. Serpiştirilmiş düzende ücret kurallarına ulaşmanın tek
yolu `gunlukKosum` çağrısıdır; o çağrı da dosya okur, saate bakar ve dosya yazar. Bunların
dördü de denemede yerine sahtesi konarak susturulmak zorundadır.

```js
// icice.test.mjs — her deneme dort sahte bagimlilik kurar
import test from "node:test";
import assert from "node:assert/strict";
import fs from "node:fs";
import { gunlukKosum } from "./icice.mjs";

const GONDERILER = [
  { kod: "TR-4471", agirlikGram: 800, bolge: 1, sozlesmeli: true },
  { kod: "TR-4472", agirlikGram: 3200, bolge: 2, sozlesmeli: true },
  { kod: "TR-4473", agirlikGram: 12400, bolge: 3, sozlesmeli: false },
];

function sahteOrtamda(gonderiler, saatUTC, is) {
  const asil = { oku: fs.readFileSync, yaz: fs.writeFileSync,
    ekle: fs.appendFileSync, saat: Date.now };
  const yazilan = { csv: "", kayit: [] };
  fs.readFileSync = () => JSON.stringify(gonderiler);
  fs.writeFileSync = (_, icerik) => { yazilan.csv = icerik; };
  fs.appendFileSync = (_, icerik) => { yazilan.kayit.push(icerik); };
  Date.now = () => Date.UTC(2024, 0, 15, saatUTC);
  try {
    return { sonuc: is(), yazilan };
  } finally {
    fs.readFileSync = asil.oku;
    fs.writeFileSync = asil.yaz;
    fs.appendFileSync = asil.ekle;
    Date.now = asil.saat;
  }
}

test("gunduz kosumunda gece eki uygulanmaz", () => {
  // SAHTE ORTAM
  const { sonuc, yazilan } = sahteOrtamda(GONDERILER, 10,
    () => gunlukKosum("g.json", "c.csv", "k.log"));
  assert.equal(sonuc, 26272);
  assert.equal(yazilan.csv, "TR-4471,4990\nTR-4472,6567\nTR-4473,14715\n");
});

test("gece kosumunda her gonderiye sabit ek biner", () => {
  // SAHTE ORTAM
  const { sonuc } = sahteOrtamda(GONDERILER, 22,
    () => gunlukKosum("g.json", "c.csv", "k.log"));
  assert.equal(sonuc, 30173);
});

test("indirim asgari ucret tabanini delemez", () => {
  // SAHTE ORTAM
  const tek = [{ kod: "TR-4477", agirlikGram: 500, bolge: 1, sozlesmeli: true }];
  const { sonuc } = sahteOrtamda(tek, 10, () => gunlukKosum("g.json", "c.csv", "k.log"));
  assert.equal(sonuc, 4990);
});
```

Ayrılmış düzende aynı üç kural doğrudan `planla` çağrısıyla sınanır. Sahte ortam yalnızca
kabuğun kendi denemesinde kurulur: kabuğun sınanacak tek davranışı, okuduğunu çekirdeğe
verip döndüğünü yazmasıdır.

```js
// kabuk-duzen.test.mjs — ucret kurallari sahtesiz sinanir, sahte yalnizca kablolamada
import test from "node:test";
import assert from "node:assert/strict";
import fs from "node:fs";
import { TARIFE } from "./tarife.mjs";
import { planla } from "./cekirdek.mjs";
import { gunlukKosum } from "./kabuk.mjs";

const GONDERILER = [
  { kod: "TR-4471", agirlikGram: 800, bolge: 1, sozlesmeli: true },
  { kod: "TR-4472", agirlikGram: 3200, bolge: 2, sozlesmeli: true },
  { kod: "TR-4473", agirlikGram: 12400, bolge: 3, sozlesmeli: false },
];

test("gunduz kosumunda gece eki uygulanmaz", () => {
  const plan = planla({ gonderiler: GONDERILER, tarife: TARIFE, saat: 10 });
  assert.equal(plan.toplam, 26272);
  assert.deepEqual(plan.satirlar, ["TR-4471,4990", "TR-4472,6567", "TR-4473,14715"]);
});

test("gece kosumunda her gonderiye sabit ek biner", () => {
  assert.equal(planla({ gonderiler: GONDERILER, tarife: TARIFE, saat: 22 }).toplam, 30173);
});

test("indirim asgari ucret tabanini delemez", () => {
  const tek = [{ kod: "TR-4477", agirlikGram: 500, bolge: 1, sozlesmeli: true }];
  assert.equal(planla({ gonderiler: tek, tarife: TARIFE, saat: 10 }).toplam, 4990);
});

test("kabuk okudugunu cekirdege verir, dondugunu yazar", () => {
  // SAHTE ORTAM
  const asil = { oku: fs.readFileSync, yaz: fs.writeFileSync,
    ekle: fs.appendFileSync, saat: Date.now };
  let csv = "";
  fs.readFileSync = () => JSON.stringify(GONDERILER);
  fs.writeFileSync = (_, icerik) => { csv = icerik; };
  fs.appendFileSync = () => {};
  Date.now = () => Date.UTC(2024, 0, 15, 10);
  try {
    assert.equal(gunlukKosum("g.json", "c.csv", "k.log"), 26272);
    assert.equal(csv, "TR-4471,4990\nTR-4472,6567\nTR-4473,14715\n");
  } finally {
    fs.readFileSync = asil.oku;
    fs.writeFileSync = asil.yaz;
    fs.appendFileSync = asil.ekle;
    Date.now = asil.saat;
  }
});
```

## İki Düzenin Ölçülmesi

Ölçüm iki tabloya ayrılır. Birincisi kaynak dosyalarda dış dünyaya dokunan satırları sayar:
dosya sistemi çağrısı ya da saat okuyan satırlar. İkincisi deneme dosyalarını sayar; sahte
ortam kuran denemeler gövdelerindeki işaretten bulunur.

```sh
# Dis dunyaya dokunan satir: dosya sistemi cagrisi ya da saat okuyan satir.
printf '%-22s %5s %10s\n' dosya satir "dis-dunya"
for f in icice.mjs kabuk.mjs cekirdek.mjs; do
  printf '%-22s %5d %10d\n' "$f" "$(awk 'END{print NR}' "$f")" \
    "$(grep -cE 'fs\.[a-zA-Z]|Date\.now' "$f")"
done
echo
# Sahteli deneme: govdesinde `// SAHTE ORTAM` isareti tasiyan deneme.
printf '%-22s %6s %7s %5s %6s\n' "deneme dosyasi" deneme sahteli satir gecen
for f in icice.test.mjs kabuk-duzen.test.mjs; do
  gecen=$(node --test --test-reporter=tap "$f" 2>&1 | grep '^# pass' | tr -dc '0-9')
  printf '%-22s %6d %7d %5d %6s\n' "$f" "$(grep -c '^test(' "$f")" \
    "$(grep -c 'SAHTE ORTAM' "$f")" "$(awk 'END{print NR}' "$f")" "$gecen"
done
```

```
dosya                  satir  dis-dunya
icice.mjs                 24          5
kabuk.mjs                 13          4
cekirdek.mjs              19          0

deneme dosyasi         deneme sahteli satir  gecen
icice.test.mjs              3       3    51      3
kabuk-duzen.test.mjs        4       1    48      4
```

Birinci tablodaki asıl sayı sondaki sıfırdır. Ücret kurallarını taşıyan on dokuz satırlık
dosyanın dış dünyaya dokunan hiçbir satırı yoktur. Serpiştirilmiş düzende aynı kurallar,
dışarıyla konuşan beş satırla aynı gövdededir; kuralı çalıştırmadan o beş satırdan
kaçınmanın yolu yoktur.

İkinci tablo bunun testteki karşılığıdır. Ayrılmış düzen bir deneme **fazla** içerir, çünkü
kabuğun kablolaması ayrıca sınanmaktadır; buna rağmen sahte ortam kuran deneme sayısı üçten
bire inmiştir. Fark oranlarda daha nettir: serpiştirilmiş düzende denemelerin yüzde yüzü,
ayrılmış düzende yüzde yirmi beşi sahte kurar.

Asıl kazanç ölçeklenmededir. Yeni bir ücret kuralı — hacim indirimi, kurumsal anlaşma, ikinci
bir bölge kademesi — ayrılmış düzende sahte ortam kurmayan bir deneme daha ekler; sahteli
deneme sayısı birde kalır. Serpiştirilmiş düzende her yeni kural denemesi dört sahteyi
yeniden kurar ve bozar. Sahte bağımlılık, sınanan kurala değil, kurala ulaşmak için geçilmesi
gereken yola ödenen bir bedeldir.

## Sınırın Denetlenmesi

Bu düzenin uygulamada bozulma biçimi tektir: acele bir düzeltmede çekirdeğe bir kayıt satırı
ya da bir saat sorgusu eklenir. İyi yanı, ihlalin ölçülebilir olmasıdır. Ölçüm betiğindeki
ilk tablo bir kurala çevrilebilir — çekirdek dosyalarının "dış-dünya" sütunu sıfır kalmalıdır
— ve bu koşul her değişiklikte yeniden sayılabilir.

Sınırın nereden geçtiğini belirleyen ölçüt, önceki derslerdekiyle aynıdır: gözlenebilirlik.
Çekirdek kendi içinde birikeç değiştirir, dizi doldurur, yerel değişken atar; bunların hiçbiri
dışarıdan görülmez. Dışarıdan görülen üç şey vardır ve üçü de kabuktadır: dosya sistemi,
zaman ve kayıt akışı. Rastgelelik ile ağ çağrısı da aynı kümeye girer.

Kabuğun kendisi saf değildir ve saflaştırılamaz. Buradaki hedef onu **küçültmektir**: on üç
satır, dördü dış dünyaya dokunan, hiçbir iş kuralı içermeyen bir dosya. Bir kusur belirdiğinde
soru ikiye ayrılabilir hâle gelir — yanlış olan hesap mı, yoksa kablolama mı. Serpiştirilmiş
düzende bu soru ayrılamaz.

## Özet

- Girdi/çıktı, zaman ve kayıt akışı ortadan kaldırılamaz; kaldırılabilecek olan, bunların
  ücret kurallarıyla aynı gövdede durmasıdır.
- Saf çekirdek kararı verir ve ne yapılacağının tanımını döndürür; kirli kabuk o tanımı
  gerçekleştirir. Karar veren her satır çekirdekte, karar vermeyen her satır kabuktadır.
- Çekirdek dosyasının dış dünyaya dokunan satır sayısı sıfırdır; serpiştirilmiş düzende aynı
  kurallar dışarıyla konuşan beş satırla aynı gövdededir.
- Ayrılmış düzende deneme sayısı üçten dörde çıkarken sahte ortam kuran deneme sayısı üçten
  bire iner; yeni her ücret kuralı sahteli deneme sayısını artırmaz.
- Sınır ihlali ölçülebilir bir koşula çevrilir: çekirdek dosyalarında dosya sistemi çağrısı
  ya da saat okuyan satır sayısı sıfır kalmalıdır.
- Kabuk saflaştırılamaz, küçültülür: on üç satır, hiç iş kuralı yok. Böylece bir kusurda
  hesap ile kablolama birbirinden ayrı sorulabilir.

## Sonraki Adım

Bu konu boyunca kurulan model tek bir birim tanıdı: fonksiyon. Kursun ilk konusu ise başka
bir birim kurmuştu — durumu ve onu koruyan davranışı bir arada tutan nesne. İki model aynı
alanı, gönderi ücretlendirmeyi, farklı biçimlerde böler. Son ders ikisini yan yana koyar:
aynı problem hem nesne ağırlıklı hem fonksiyon ağırlıklı modellenir, sonra iki ayrı değişiklik
istenir ve her birinin iki modelde kaç dosyaya dokunduğu sayılır. Çıkacak sonuç bir üstünlük
ilanı değil, hangi ölçüte göre hangisinin seçildiğidir.
