---
title: 'Zarif Bozulma'
source: 'https://academia.sh/tr/kurslar/gozlemlenebilirlik/zarif-bozulma'
course: 'Gözlemlenebilirlik ve Güvenilirlik'
language: tr
updated: '2026-08-17T18:06:51+00:00'
license: 'CC BY-SA 4.0'
---

# Zarif Bozulma

Zarif bozulmanın koda yerleştirilmesi: çekirdek ve isteğe bağlı alan ayrımının şema nesnesinde toplanması, çağrı yerinde try/catch ile paylaşılan sarmalayıcının 7 ve 12 satır olarak karşılaştırılması, 14 çağrı yerinin 6'sının sarmalanıp 2'sinin atlanması ve yedek değerin gövdeyi gerçek sıfırdan ayırt edilemez kılması.

Bölme kalıbı bozulan bağımlılığı kendi havuzuna kapattı: havuz dolduğunda çağrı hızla başarısız
oluyor ve öteki bağımlılıkların havuzları etkilenmiyor. Kurtarılan şey aşağı akıştır. Yukarı akışta
hiçbir şey değişmedi — çağrı yeri hâlâ bir hata nesnesi alıyor, uç hâlâ o hatayı yukarı fırlatıyor
ve istemci hâlâ boş dönüyor. Panel yanıtının beş alanından dördü elde olsa bile, beşincisi gelmediği
için beşi birden atılıyor.

Zarif bozulma bu boşluğu kapatır. Etkisi **Dayanıklılık ve Güvenilirlik** kursunda ölçüldü: hangi
politikanın kaç isteği kurtardığı, arızasız günün bedeli ve çekirdek bir bağımlılık düştüğünde
kalıbın elinden ne geldiği orada sayıldı. Burada ölçülen başka bir şey: bu kalıp **koda nereye
yerleşiyor**, kaç çağrı yerini kapsıyor ve hangi çağrı yerlerini atlıyor.

## Şema Nesnesi ve Çağrı Yerleri

Düzenek bölünmüş ödünç sisteminin beş servisidir. Her biri yerel bir `node:http` sunucusudur ve
bayrağı kapatıldığında bağlantıyı reddeder — çağıran taraf için bu, üzerinde denetimi olmayan bir
hata nesnesi demektir.

```js
// bozulma/servisler.mjs — bolunmus odunc sisteminin yukari akis servisleri.
// Her biri yerel bir node:http surecidir; CALISIYOR bayragi kapatilinca baglanti reddedilir.
import http from "node:http";

export const PORT = {};
export const CALISIYOR = { uyelik: 1, odunc: 1, ucretlendirme: 1, bildirim: 1, katalog: 1 };
export const GOVDE = {
  uyelik: { ad: "N.A.", durum: "etkin" },
  odunc: { acik: 3, gecikmis: 1 },
  ucretlendirme: { borc: 12.5 },
  bildirim: { okunmamis: 4 },
  katalog: { oneri: ["K-1180"] },
};
const AYAK = [];

export async function baslat() {
  for (const ad of Object.keys(GOVDE)) {
    const s = http.createServer((req, res) => {
      if (CALISIYOR[ad] === 0) { res.socket.destroy(); return; }
      res.writeHead(200, { "content-type": "application/json" });
      res.end(JSON.stringify(GOVDE[ad]));
    });
    await new Promise((r) => s.listen(0, "127.0.0.1", r));
    PORT[ad] = s.address().port;
    AYAK.push(s);
  }
}
export function durdur() { for (const s of AYAK) s.close(); }

// Yalin cagri yeri: servis dusunce hata firlatir. Sarmalanmamis her cagri boyle davranir.
export async function cagir(ad) {
  const y = await fetch(`http://127.0.0.1:${PORT[ad]}/`);
  if (y.ok === false) throw new Error(`${ad}: HTTP ${y.status}`);
  return y.json();
}
```

**DY10 — panelin alan ayrımı.** Üye panelinin beş alanı vardır. `uye` ile `odunc` çekirdektir: bu
ikisi olmadan sayfa bir şey göstermez. `borc`, `bildirim` ve `oneri` isteğe bağlıdır ve her birinin
yerine konacak bir yedek değeri vardır. **DY11 — makbuz ve özet uçları panelden önce yazılmıştır** ve
kendi çağrılarını yaparlar; bu, bozulma kalıbı sisteme sonradan girdiğinde tipik olan durumdur.

Ayrım kodun içine dağılmaz, tek bir şema nesnesinde durur. İki yerleşim aynı şemayı okur.

```js
// bozulma/panel.mjs — panel yanitinin semasi ve kalibin iki yerlesimi.
import { cagir } from "./servisler.mjs";

// DY10: yanitin cekirdek/istege bagli ayrimi sema nesnesinde durur, kodun icine dagilmaz.
export const SEMA = {
  uye: { servis: "uyelik", cekirdek: 1 },
  odunc: { servis: "odunc", cekirdek: 1 },
  borc: { servis: "ucretlendirme", cekirdek: 0, yedek: { borc: 0 } },
  bildirim: { servis: "bildirim", cekirdek: 0, yedek: { okunmamis: 0 } },
  oneri: { servis: "katalog", cekirdek: 0, yedek: { oneri: [] } },
};

// Yerlesim A — cagri yerinde: her istege bagli cagri kendi try/catch'ini ve kendi yedegini tasir.
export async function panelA() {
  const y = { uye: await cagir("uyelik"), odunc: await cagir("odunc") };
  try { y.borc = await cagir("ucretlendirme"); } catch { y.borc = { borc: 0 }; }
  try { y.bildirim = await cagir("bildirim"); } catch { y.bildirim = { okunmamis: 0 }; }
  try { y.oneri = await cagir("katalog"); } catch { y.oneri = { oneri: [] }; }
  return y;
}

// Yerlesim B — paylasilan sarmalayici: yedek semadan okunur, dusen alan yanitta isaretlenir.
export async function istegeBagli(alan, eksik) {
  const t = SEMA[alan];
  try { return await cagir(t.servis); } catch { eksik.push(alan); return t.yedek; }
}
export async function panelB() {
  const eksik = [];
  const y = { uye: await cagir("uyelik"), odunc: await cagir("odunc") };
  y.borc = await istegeBagli("borc", eksik);
  y.bildirim = await istegeBagli("bildirim", eksik);
  y.oneri = await istegeBagli("oneri", eksik);
  return { ...y, eksik };
}

// Ayni servislere dokunan oteki iki uc. Ikisi de panelden once yazildi.
export async function makbuz() {
  const u = await cagir("uyelik");
  const b = await cagir("ucretlendirme");
  return { uye: u.ad, borc: b.borc };
}
export async function ozet() {
  const o = await cagir("odunc");
  const b = await cagir("bildirim");
  return { acik: o.acik, okunmamis: b.okunmamis };
}
```

## Kaynak Taraması ve Koşum

Ölçüm iki bölümdür. Önce kaynak metni taranır ve her çağrı yeri sınıflandırılır: çekirdek bir
servise mi gidiyor, isteğe bağlı bir servise gidip sarmalanmış mı, yoksa isteğe bağlı bir servise
gidip sarmalanmamış mı. Üçüncüsü **atlanan çağrı yeridir**. Sonra aynı kod arızalı senaryolarda
koşturulur ve tarama ile koşum karşılaştırılır.

```js
// bozulma/olcum.mjs — once kaynak sayilir, sonra ayni kod arizali senaryolarda kosturulur.
import { readFileSync } from "node:fs";
import { baslat, durdur, CALISIYOR, GOVDE } from "./servisler.mjs";
import { SEMA, panelA, panelB, makbuz, ozet } from "./panel.mjs";

const ISTEGE_BAGLI = new Set(Object.values(SEMA).filter((t) => !t.cekirdek).map((t) => t.servis));
const yer = [], satir = {};              // her cagri yeri: hangi islev, hangi servis, sarmali mi
let simdi = null;
for (const s of readFileSync(new URL("./panel.mjs", import.meta.url), "utf8").split("\n")) {
  const bas = s.match(/^export async function (\w+)/);
  if (bas) simdi = bas[1];
  if (simdi === null) continue;
  if (s.trim() !== "") satir[simdi] = (satir[simdi] ?? 0) + 1;
  for (const m of s.matchAll(/cagir\("(\w+)"\)/g))            // adi yazili yalin cagri yeri
    yer.push({ islev: simdi, servis: m[1], sarmali: s.includes("try {") });
  for (const m of s.matchAll(/istegeBagli\("(\w+)"/g))        // sarmalayiciya verilmis cagri yeri
    yer.push({ islev: simdi, servis: SEMA[m[1]].servis, sarmali: true });
  if (s.startsWith("}")) simdi = null;
}
const sinif = (y) => (ISTEGE_BAGLI.has(y.servis) ? (y.sarmali ? "sarmalanan" : "atlanan") : "cekirdek");

console.log("-- kaynak taramasi --");
console.log(`${"islev".padEnd(13)}${"satir".padStart(6)}${"cagri yeri".padStart(11)}${"cekirdek".padStart(9)}${"sarmalanan".padStart(11)}${"atlanan".padStart(8)}`);
for (const i of [...new Set(yer.map((y) => y.islev))]) {
  const t = yer.filter((y) => y.islev === i);
  const n = (k) => String(t.filter((y) => sinif(y) === k).length);
  console.log(`${i.padEnd(13)}${String(satir[i]).padStart(6)}${String(t.length).padStart(11)}${n("cekirdek").padStart(9)}${n("sarmalanan").padStart(11)}${n("atlanan").padStart(8)}`);
}
console.log(`toplam ${yer.length} cagri yeri; ${yer.filter((y) => sinif(y) === "sarmalanan").length} sarmalandi, ${yer.filter((y) => sinif(y) === "atlanan").length} atlandi`);
console.log(`yerlesim A ${satir.panelA} satir, yerlesim B ${satir.panelB}+${satir.istegeBagli}=${satir.panelB + satir.istegeBagli} satir\n`);

await baslat();
const UC = { panelA, panelB, makbuz, ozet };
const SENARYO = [["hepsi saglam", []], ["ucretlendirme durdu", ["ucretlendirme"]],
  ["ucretlendirme+bildirim+katalog", ["ucretlendirme", "bildirim", "katalog"]], ["uyelik durdu", ["uyelik"]]];
console.log("-- kosum: dusen servise giden cagri yerlerinin akibeti --");
console.log(`${"senaryo".padEnd(31)}${"dokunulan".padStart(10)}${"yedek donen".padStart(12)}${"hata firlatan".padStart(14)}${"yanitlayan uc".padStart(14)}`);
const govde = {};
for (const [ad, dusen] of SENARYO) {
  for (const s of Object.keys(CALISIYOR)) CALISIYOR[s] = dusen.includes(s) ? 0 : 1;
  let yanit = 0;
  for (const [uc, f] of Object.entries(UC)) {
    try { govde[`${ad}|${uc}`] = JSON.stringify(await f()); yanit += 1; } catch { /* uc dustu */ }
  }
  const d = yer.filter((y) => dusen.includes(y.servis));
  const yedek = d.filter((y) => sinif(y) === "sarmalanan").length;
  console.log(`${ad.padEnd(31)}${String(d.length).padStart(10)}${String(yedek).padStart(12)}${String(d.length - yedek).padStart(14)}${`${yanit}/4`.padStart(14)}`);
}

for (const s of Object.keys(CALISIYOR)) CALISIYOR[s] = 1;
GOVDE.ucretlendirme.borc = 0;                        // borcu gercekten olmayan bir uye
const sifirA = JSON.stringify(await panelA()), sifirB = JSON.stringify(await panelB());
const arizaliA = govde["ucretlendirme durdu|panelA"], arizaliB = govde["ucretlendirme durdu|panelB"];
console.log("\n-- yedegin sessiz yanlisi: servis dustu / borc gercekten 0 --");
console.log(`A dustu: ${arizaliA}`);
console.log(`A sifir: ${sifirA}`);
console.log(`ayni govde mi: ${arizaliA === sifirA}`);
console.log(`B dustu: ${arizaliB.slice(arizaliB.indexOf('"borc"'))}`);
console.log(`B sifir: ${sifirB.slice(sifirB.indexOf('"borc"'))}`);
console.log(`ayni govde mi: ${arizaliB === sifirB}; isaretin bedeli ${arizaliB.length - arizaliA.length} bayt, yanitin %${(((arizaliB.length - arizaliA.length) / arizaliA.length) * 100).toFixed(1)}'i`);
durdur();
```

```
-- kaynak taramasi --
islev         satir cagri yeri cekirdek sarmalanan atlanan
panelA            7          5        2          3       0
panelB            8          5        2          3       0
makbuz            5          2        1          0       1
ozet              5          2        1          0       1
toplam 14 cagri yeri; 6 sarmalandi, 2 atlandi
yerlesim A 7 satir, yerlesim B 8+4=12 satir

-- kosum: dusen servise giden cagri yerlerinin akibeti --
senaryo                         dokunulan yedek donen hata firlatan yanitlayan uc
hepsi saglam                            0           0             0           4/4
ucretlendirme durdu                     3           2             1           3/4
ucretlendirme+bildirim+katalog          8           6             2           2/4
uyelik durdu                            3           0             3           1/4

-- yedegin sessiz yanlisi: servis dustu / borc gercekten 0 --
A dustu: {"uye":{"ad":"N.A.","durum":"etkin"},"odunc":{"acik":3,"gecikmis":1},"borc":{"borc":0},"bildirim":{"okunmamis":4},"oneri":{"oneri":["K-1180"]}}
A sifir: {"uye":{"ad":"N.A.","durum":"etkin"},"odunc":{"acik":3,"gecikmis":1},"borc":{"borc":0},"bildirim":{"okunmamis":4},"oneri":{"oneri":["K-1180"]}}
ayni govde mi: true
B dustu: "borc":{"borc":0},"bildirim":{"okunmamis":4},"oneri":{"oneri":["K-1180"]},"eksik":["borc"]}
B sifir: "borc":{"borc":0},"bildirim":{"okunmamis":4},"oneri":{"oneri":["K-1180"]},"eksik":[]}
ayni govde mi: false; isaretin bedeli 17 bayt, yanitin %11.9'i
```

## İki Yerleşim ve Atlanan Çağrı Yeri

İki yerleşim aynı işi görüyor ve panel ucunda aynı sonucu veriyor: her ikisi de üç isteğe bağlı
çağrıyı sarmalıyor, ikisi de ücretlendirme düştüğünde yanıt üretiyor. Satır bedeli farklı — çağrı
yerindeki yerleşim 7 satır, paylaşılan sarmalayıcılı yerleşim 8 artı 4, toplam 12 satır. İlk bakışta
çağrı yerindeki yerleşim ucuz görünüyor.

Farkı yaratan şey satır sayısı değil, **yedeğin nerede yazıldığı**. Çağrı yerindeki yerleşimde
`{ borc: 0 }` ifadesi `catch` bloğunun içine gömülüdür; şemadaki `yedek` alanı ile aralarında hiçbir
bağ yoktur. İkisi ayrı ayrı değiştirilebilir ve sessizce ayrışabilirler. Paylaşılan sarmalayıcıda
yedek tek yerden, şemadan okunur; bir alanın çekirdek olup olmadığına dair karar da orada durur.

Asıl ölçü ikinci tablodadır. Ücretlendirme servisi düştüğünde o servise giden **üç** çağrı yeri var:
paneldeki iki yerleşim ve makbuz ucundaki çağrı. İkisi yedek değer döndürüyor, biri hâlâ hata
fırlatıyor — makbuz ucu yanıt veremiyor, dört uçtan üçü ayakta. Üç servis birden düştüğünde
dokunulan çağrı yeri sekize çıkıyor, altısı yedek döndürüyor, ikisi fırlatıyor ve özet ucu da
düşüyor.

Bozulma kalıbı panele yerleşti, sisteme yerleşmedi. Şema nesnesi `borc` alanını "isteğe bağlı" ilan
ediyor, ama bu ilan yalnızca o alanı okuyan kod yolunu bağlıyor. Makbuz ucu aynı servisi çağırıyor,
aynı hatayı alıyor ve o hatayı yukarı fırlatmayı sürdürüyor. **Kalıbın kapsamı, kalıbın kurulduğu
yer değil, kalıbın çağrıldığı yerlerin kümesidir.** Kaynak taraması bu kümeyi altı sarmalanan ve iki
atlanan çağrı yeri olarak sayıyor; iki atlanan çağrı yeri koşumda iki düşen uca dönüşüyor.

Son satır ayrımın bir başka yüzünü gösteriyor. Üyelik servisi düştüğünde ona giden üç çağrı yerinin
üçü de çekirdek sınıfındadır ve hiçbiri sarmalanmamıştır; bu bir eksiklik değil, tasarımın kendisidir.
Yine de özet ucu ayakta kalıyor, çünkü üyelik onun için bir bağımlılık değil. **Çekirdeklik alanın
değil, ucun özelliğidir**: aynı servis bir uçta çekirdek, başka bir uçta hiç yoktur.

## Yedeğin Sessiz Yanlışı

Son bölüm kalıbın kendi ürettiği kusuru ölçüyor. Ücretlendirme servisi düşükken çağrı yerindeki
yerleşimin ürettiği gövde ile borcu gerçekten sıfır olan bir üyenin sağlam gündeki gövdesi
karşılaştırılıyor: **bayt bayt aynı**. `borc: 0` değeri iki anlama geliyor ve istemcinin bu ikisini
ayırt etmesinin hiçbir yolu yok. Üye ekranında "borcunuz yok" yazıyor, gerçekte on iki buçuk liralık
bir borç var ve kimse bir hata görmüyor.

Bu, bozulma kalıbının hata fırlatan koddan **daha kötü** olabildiği tek noktadır. Fırlatılan hata
görünürdür: uç düşer, kayda geçer, sayaca yansır. Yedek değer görünmez, çünkü başarılı bir yanıtın
içinde durur. Sarmalayıcılı yerleşim bunu düzeltiyor: `eksik` listesi hangi alanın yedekle
doldurulduğunu yanıtın kendisinde taşıyor ve iki gövde artık ayrışıyor. Bedeli on yedi bayt, bu
yanıtın yüzde 11,9'u.

Bu on yedi bayt yalnızca bir ek alan değil, kalıbın **gözlemlenebilir** hâlidir. İşareti taşımayan
bir bozulma, bu kursun ilk iki konusundaki bütün araçlar için görünmezdir: günlük kaydı üretilmez,
sayaç artmaz, gösterge kıpırdamaz. Yedek değerin bir işareti yoksa bozulma ölçülemez; ölçülemeyen
bozulma da bir süre sonra normal sanılır.

## Özet

- Zarif bozulma koda bir şema nesnesi olarak yerleşir: hangi alanın çekirdek olduğu, hangisinin
  isteğe bağlı olduğu ve yedeğinin ne olduğu tek yerde durur. Kalıbın etkisi Dayanıklılık ve
  Güvenilirlik kursunda ölçüldü; buradaki ölçü yerleşimin kendisidir.
- İki yerleşim karşılaştırıldı: çağrı yerinde `try/catch` 7 satır, paylaşılan sarmalayıcı 12 satır.
  Satır farkının karşılığı, yedek değerin `catch` bloğuna gömülmek yerine şemadan okunmasıdır.
- Kaynak taraması 14 çağrı yeri buldu: 6'sı sarmalandı, 2'si atlandı, kalanı çekirdek çağrılardır.
  Atlanan iki çağrı yeri, panelden önce yazılmış makbuz ve özet uçlarındadır.
- Koşum taramayı doğruladı: ücretlendirme düştüğünde 3 çağrı yerinin 2'si yedek döndürdü, 1'i hata
  fırlattı ve dört uçtan biri düştü. Üç servis birden düştüğünde 8 çağrı yerinin 6'sı yedek döndürdü
  ve iki uç düştü.
- İşaretsiz yedek sessizce yanlış veri üretir: servis düşükken üretilen gövde ile borcu gerçekten
  sıfır olan üyenin gövdesi bayt bayt aynıdır. `eksik` listesi bu ikisini ayırır ve yanıta 17 bayt,
  yüzde 11,9 ekler.
- Çekirdeklik alanın değil ucun özelliğidir: üyelik servisi panel ve makbuz için çekirdek, özet ucu
  için hiç yoktur.

## Sonraki Adım

Buraya kadarki beş kalıp da aynı yöne bakıyor: aşağı akıştaki bir bağımlılık bozulduğunda bu sistemin
ne yapacağı. Zaman aşımı beklemeyi kesiyor, yeniden deneme yeniden soruyor, devre kesici sormayı
bırakıyor, bölme kaynakları ayırıyor, bozulma yanıtı küçültüyor. Hepsinin öznesi aynı: **çağıran
taraf**.

Aynı sistemin öteki yüzü hiç ele alınmadı. Bu servisler yalnız çağırmıyor, çağrılıyor da; ve çağıran
tarafın kaç istek göndereceğine dair hiçbir sınır yok. Bir istemcinin yanlış yazılmış bir döngüsü,
tek bir uca dakikada binlerce istek gönderebilir ve buradaki hiçbir kalıp bunu durduramaz — hepsi
zaten gelmiş bir isteğin başına ne geleceğini düzenler. Sıradaki ders sınırı **kabul tarafına**
koyar ve şunu ölçer: sınırlayıcı kodun hangi katmanında durur, sınır kime uygulanır, hangi yollar
sınırın dışında kalır ve anahtar yanlış seçildiğinde ne olur.
