---
title: 'Doğru Yapıyı Seçmek'
source: 'https://academia.sh/tr/kurslar/temiz-kod/dogru-yapiyi-secmek'
course: 'Temiz Kod'
language: tr
updated: '2026-08-17T18:10:37+00:00'
license: 'CC BY-SA 4.0'
---

# Doğru Yapıyı Seçmek

Aynı davranışın koşul zinciri, arama tablosu ve çok biçimlilikle üç sürümünün yazılması ve seçimin ölçüye bağlanması: yeni bir tarife türü eklendiğinde dokunulan dosya ve satır sayısı, tür adının kaç yerde geçtiği ve bir türün kendi durumunu taşımasının maliyeti.

Önceki derste bölge katsayıları koşul zincirinden bir tabloya taşındı ve karmaşıklık
düştü. Bu dönüşüm her zaman doğru değildir. Tablo, bir anahtara karşılık bir **değer**
döndürür; karşılık bir değer değil de bir davranışsa, ya da her seçenek kendi verisini
taşıyorsa üçüncü bir seçenek belirir: çok biçimlilik.

Üçü arasında seçim, üslup meselesi gibi görünür. Ölçülebilir bir yanı vardır: tasarımın
maliyeti, üzerinde yapılacak değişiklikte ortaya çıkar. Bu ders aynı davranışı üç yapıyla
yazar, sonra üçüne de aynı değişikliği uygular ve dokunulan dosya ile satır sayısını
karşılaştırır.

## Ortak Hesap

Üç tasarım da aynı temel ücretle başlar: kademe ücreti çarpı bölge katsayısı. Ayrıldıkları
nokta, tarife türünün seçildiği yerdir. Üç tür var (standart, ekspres, ekonomik) ve her
tür üç işlemde birden görünür: ücret, teslim günü, etiket.

```sh
mkdir -p kosul tablo cokbicim
```

```js
// ortak.mjs — uc tasarimin paylastigi temel ucret hesabi
const BOLGE_KATSAYILARI = { 1: 1, 2: 1.35, 3: 1.8 };
const AGIRLIK_KADEMELERI = [{ ustSinirKg: 1, kiloBasiUcret: 38 }, { ustSinirKg: 5, kiloBasiUcret: 26 },
  { ustSinirKg: 10, kiloBasiUcret: 21 }, { ustSinirKg: 30, kiloBasiUcret: 17 }];

export function temelUcret(gonderi) {
  const kademe = AGIRLIK_KADEMELERI.find((k) => gonderi.agirlikKg <= k.ustSinirKg);
  return kademe.kiloBasiUcret * gonderi.agirlikKg * BOLGE_KATSAYILARI[gonderi.bolgeKodu];
}
```

## Üç Tasarım

**Koşul zinciri.** Tür kararı her işlemde yeniden verilir.

```js
// kosul/ucret.mjs — tarife turu her islemde kosul zinciriyle secilir
import { temelUcret } from "../ortak.mjs";

export function ucret(gonderi, tur) {
  const temel = temelUcret(gonderi);
  if (tur === "standart") return temel;
  if (tur === "ekspres") return temel * 1.6 + 25;
  if (tur === "ekonomik") return Math.max(temel * 0.8, 65);
  throw new RangeError(`bilinmeyen tarife: ${tur}`);
}

export function teslimGunu(tur) {
  if (tur === "standart") return 3;
  if (tur === "ekspres") return 1;
  if (tur === "ekonomik") return 6;
  throw new RangeError(`bilinmeyen tarife: ${tur}`);
}

export function etiket(tur) {
  if (tur === "standart") return "Standart";
  if (tur === "ekspres") return "Ekspres";
  if (tur === "ekonomik") return "Ekonomik";
  throw new RangeError(`bilinmeyen tarife: ${tur}`);
}
```

**Arama tablosu.** Tür kararı bir kez verilir; bir tür bir veri satırıdır.

```js
// tablo/ucret.mjs — tarife turleri tek tabloda, satir basina bir tur
import { temelUcret } from "../ortak.mjs";

const TARIFELER = {
  standart: { etiket: "Standart", gun: 3, hesap: (temel) => temel },
  ekspres: { etiket: "Ekspres", gun: 1, hesap: (temel) => temel * 1.6 + 25 },
  ekonomik: { etiket: "Ekonomik", gun: 6, hesap: (temel) => Math.max(temel * 0.8, 65) },
};

function tarife(tur) {
  const secilen = TARIFELER[tur];
  if (secilen === undefined) throw new RangeError(`bilinmeyen tarife: ${tur}`);
  return secilen;
}

export const ucret = (gonderi, tur) => tarife(tur).hesap(temelUcret(gonderi));
export const teslimGunu = (tur) => tarife(tur).gun;
export const etiket = (tur) => tarife(tur).etiket;
```

**Çok biçimlilik.** Bir tür bir tiptir; tipler aynı üyeleri gerçekleştirir ve bir kütüğe
kaydedilir.

```js
// cokbicim/turler.mjs — her tur ayni uc uyeyi gerceklestirir
export class Standart {
  etiket = "Standart";
  gun = 3;
  hesap(temel) { return temel; }
}

export class Ekspres {
  etiket = "Ekspres";
  gun = 1;
  hesap(temel) { return temel * 1.6 + 25; }
}

export class Ekonomik {
  etiket = "Ekonomik";
  gun = 6;
  hesap(temel) { return Math.max(temel * 0.8, 65); }
}
```

```js
// cokbicim/ucret.mjs — turler bir kutuge kaydedilir, secim kutukten yapilir
import { temelUcret } from "../ortak.mjs";
import { Standart, Ekspres, Ekonomik } from "./turler.mjs";

const KUTUK = { standart: new Standart(), ekspres: new Ekspres(), ekonomik: new Ekonomik() };

function tarife(tur) {
  const secilen = KUTUK[tur];
  if (secilen === undefined) throw new RangeError(`bilinmeyen tarife: ${tur}`);
  return secilen;
}

export const ucret = (gonderi, tur) => tarife(tur).hesap(temelUcret(gonderi));
export const teslimGunu = (tur) => tarife(tur).gun;
export const etiket = (tur) => tarife(tur).etiket;
```

Üç tasarımın aynı sonuçları verdiği doğrulanır.

```js
// karsilastir.mjs — uc tasarimin ayni sonuclari verdigi dogrulanir
import * as kosul from "./kosul/ucret.mjs";
import * as tablo from "./tablo/ucret.mjs";
import * as cokbicim from "./cokbicim/ucret.mjs";

const gonderi = { agirlikKg: 4.2, bolgeKodu: 2 };
for (const tur of ["standart", "ekspres", "ekonomik"]) {
  const satir = [kosul, tablo, cokbicim].map((t) =>
    `${t.etiket(tur)}/${t.teslimGunu(tur)}/${t.ucret(gonderi, tur).toFixed(2)}`);
  const ayni = satir.every((s) => s === satir[0]);
  console.log(`${tur.padEnd(10)} ${ayni ? "ayni  " : "FARKLI"} ${satir[0]}`);
}
```

```
standart   ayni   Standart/3/147.42
ekspres    ayni   Ekspres/1/260.87
ekonomik   ayni   Ekonomik/6/117.94
```

## Değişiklik: Dördüncü Tür

Soğuk zincir tarifesi açılıyor: ücreti temel ücretin 2,2 katı artı kırk lira soğutma
bedeli, teslim süresi iki gün. Değişiklik üç tasarımın kopyasına uygulanır.

```sh
cp -r kosul kosul-yeni && cp -r tablo tablo-yeni && cp -r cokbicim cokbicim-yeni
```

```js
// kosul-yeni/ucret.mjs — her zincire bir dal daha
import { temelUcret } from "../ortak.mjs";

export function ucret(gonderi, tur) {
  const temel = temelUcret(gonderi);
  if (tur === "standart") return temel;
  if (tur === "ekspres") return temel * 1.6 + 25;
  if (tur === "ekonomik") return Math.max(temel * 0.8, 65);
  if (tur === "soguk-zincir") return temel * 2.2 + 40;
  throw new RangeError(`bilinmeyen tarife: ${tur}`);
}

export function teslimGunu(tur) {
  if (tur === "standart") return 3;
  if (tur === "ekspres") return 1;
  if (tur === "ekonomik") return 6;
  if (tur === "soguk-zincir") return 2;
  throw new RangeError(`bilinmeyen tarife: ${tur}`);
}

export function etiket(tur) {
  if (tur === "standart") return "Standart";
  if (tur === "ekspres") return "Ekspres";
  if (tur === "ekonomik") return "Ekonomik";
  if (tur === "soguk-zincir") return "Soguk Zincir";
  throw new RangeError(`bilinmeyen tarife: ${tur}`);
}
```

```js
// tablo-yeni/ucret.mjs — tabloya bir satir daha
import { temelUcret } from "../ortak.mjs";

const TARIFELER = {
  standart: { etiket: "Standart", gun: 3, hesap: (temel) => temel },
  ekspres: { etiket: "Ekspres", gun: 1, hesap: (temel) => temel * 1.6 + 25 },
  ekonomik: { etiket: "Ekonomik", gun: 6, hesap: (temel) => Math.max(temel * 0.8, 65) },
  "soguk-zincir": { etiket: "Soguk Zincir", gun: 2, hesap: (temel) => temel * 2.2 + 40 },
};

function tarife(tur) {
  const secilen = TARIFELER[tur];
  if (secilen === undefined) throw new RangeError(`bilinmeyen tarife: ${tur}`);
  return secilen;
}

export const ucret = (gonderi, tur) => tarife(tur).hesap(temelUcret(gonderi));
export const teslimGunu = (tur) => tarife(tur).gun;
export const etiket = (tur) => tarife(tur).etiket;
```

```js
// cokbicim-yeni/soguk-zincir.mjs — yeni tur, kendi dosyasinda
export class SogukZincir {
  etiket = "Soguk Zincir";
  gun = 2;
  hesap(temel) { return temel * 2.2 + 40; }
}
```

```js
// cokbicim-yeni/ucret.mjs — kutuge bir kayit daha
import { temelUcret } from "../ortak.mjs";
import { Standart, Ekspres, Ekonomik } from "./turler.mjs";
import { SogukZincir } from "./soguk-zincir.mjs";

const KUTUK = { standart: new Standart(), ekspres: new Ekspres(), ekonomik: new Ekonomik(),
  "soguk-zincir": new SogukZincir() };

function tarife(tur) {
  const secilen = KUTUK[tur];
  if (secilen === undefined) throw new RangeError(`bilinmeyen tarife: ${tur}`);
  return secilen;
}

export const ucret = (gonderi, tur) => tarife(tur).hesap(temelUcret(gonderi));
export const teslimGunu = (tur) => tarife(tur).gun;
export const etiket = (tur) => tarife(tur).etiket;
```

## Ölçüm

Ölçer her tasarımın eski ve yeni dizinini dosya dosya karşılaştırır; çıkan satır
değiştirilmiş ya da silinmiş, giren satır eklenmiş demektir.

```js
// degisim-olc.mjs — ayni tur eklemesi uc tasarimda kac dosyaya ve kac satira dokunuyor
import { execSync } from "node:child_process";
import { readdirSync } from "node:fs";

// Ilk satir dosya adi isaretidir, karsilastirmaya girmez.
const fark = (eski, yeni) =>
  execSync(`diff <(tail -n +2 ${eski} 2>/dev/null) <(tail -n +2 ${yeni} 2>/dev/null) || true`,
    { shell: "/bin/bash" }).toString().split("\n");

for (const tasarim of ["kosul", "tablo", "cokbicim"]) {
  const adlar = [...new Set([...readdirSync(tasarim), ...readdirSync(`${tasarim}-yeni`)])].sort();
  let dokunulan = 0, cikan = 0, giren = 0;
  console.log(tasarim);
  for (const ad of adlar) {
    const satirlar = fark(`${tasarim}/${ad}`, `${tasarim}-yeni/${ad}`);
    const c = satirlar.filter((s) => s.startsWith("< ")).length;
    const g = satirlar.filter((s) => s.startsWith("> ")).length;
    if (c + g > 0) dokunulan += 1;
    [cikan, giren] = [cikan + c, giren + g];
    console.log(`  ${ad.padEnd(18)} cikan=${c}  giren=${g}`);
  }
  console.log(`  ${"= toplam".padEnd(18)} dosya=${dokunulan}  cikan=${cikan}  giren=${giren}`);
}
```

```
kosul
  ucret.mjs          cikan=0  giren=3
  = toplam           dosya=1  cikan=0  giren=3
tablo
  ucret.mjs          cikan=0  giren=1
  = toplam           dosya=1  cikan=0  giren=1
cokbicim
  soguk-zincir.mjs   cikan=0  giren=5
  turler.mjs         cikan=0  giren=0
  ucret.mjs          cikan=1  giren=3
  = toplam           dosya=2  cikan=1  giren=8
```

İkinci ölçüm, tür adının kaç yerde geçtiğini sayar. Bu sayı, bir türün değişmesi hâlinde
aranacak yer sayısıdır.

```sh
grep -c ekonomik kosul/ucret.mjs tablo/ucret.mjs cokbicim/ucret.mjs
```

```
kosul/ucret.mjs:3
tablo/ucret.mjs:1
cokbicim/ucret.mjs:1
```

## Sayıların Söylediği

Bu değişiklikte en ucuz tasarım tablodur: bir dosya, bir satır. Koşul zinciri üç satır
ekletti, çünkü tür kararı üç ayrı yerde veriliyor. Bu üç sayının kaynağı ikinci ölçümdedir:
`ekonomik` adı koşul zincirinde üç yerde geçiyor, diğer ikisinde bir yerde.

Fark, işlem sayısıyla birlikte büyür. Tarife türlerine dördüncü bir işlem eklendiğinde
(örneğin sigorta oranı) koşul zincirine dördüncü bir fonksiyon eklenir ve o fonksiyon her
türü yeniden sayar; tabloda her satıra bir alan eklenir. İşlem sayısı N, tür sayısı T
iken koşul zinciri N × T karar taşır, tablo T satır taşır.

Çok biçimlilik bu değişiklikte en pahalısıdır: iki dosya, sekiz giren satır. Sekiz satırın
beşi yeni tür dosyasının kendisi, üçü tür kaydına ekleme. Buna karşılık `turler.mjs` hiç
açılmadı: var olan türlerin tanımlarına dokunulmadı. Tabloda da var olan satırlar
değişmedi, ama yeni satır var olan tablo nesnesinin içine girdi.

Aradaki asıl fark satır sayısında değil, bir türün ne taşıyabildiğindedir. Tablo satırı
bir veri kaydıdır; her satır aynı alanları aynı biçimde doldurmak zorundadır. Tip ise
kendi durumunu taşıyabilir.

```js
// sozlesmeli.mjs — durumunu tasiyan tarife turu: bir davranis tanimi, uc ornek
import { temelUcret } from "./ortak.mjs";

class SozlesmeliTarife {
  constructor(etiket, gun, carpan, sabitUcret) {
    Object.assign(this, { etiket, gun, carpan, sabitUcret });
  }
  hesap(temel) { return temel * this.carpan + this.sabitUcret; }
}

const SOZLESMELER = [
  new SozlesmeliTarife("Musteri A", 2, 1.1, 0),
  new SozlesmeliTarife("Musteri B", 3, 0.9, 15),
  new SozlesmeliTarife("Musteri C", 1, 1.4, 30),
];

const gonderi = { agirlikKg: 4.2, bolgeKodu: 2 };
for (const t of SOZLESMELER) {
  console.log(`${t.etiket.padEnd(10)} ${t.gun} gun  ${t.hesap(temelUcret(gonderi)).toFixed(2)}`);
}
console.log(`tarife sayisi=${SOZLESMELER.length}  davranis tanimi=1`);
```

```
Musteri A  2 gun  162.16
Musteri B  3 gun  147.68
Musteri C  1 gun  236.39
tarife sayisi=3  davranis tanimi=1
```

Üç sözleşmeli tarife tek bir davranış tanımından üretildi. Sözleşme sayısı üç yerine üç
yüz olsaydı kod satırı sayısı değişmezdi; artan şey yalnız veri olurdu. Koşul zincirinde
her sözleşme üç dal, tabloda her sözleşme bir satır eklerdi.

## Seçim Ölçütü

Ölçümler üç kuralı destekliyor.

**Koşul zinciri**, tür sayısı da işlem sayısı da küçük ve sabit olduğunda yerindedir;
kararın tek bir yerde ve göz önünde durması bir kazançtır. Aynı ayrım ikinci bir yerde
tekrarlandığı anda maliyeti çarpmaya başlar.

**Arama tablosu**, seçenekler aynı biçimi paylaşan veri kayıtlarıysa ve seçim bir anahtara
bakmaktan ibaretse en ucuzudur. Sınırı, her satırın aynı alanları doldurmak zorunda
olmasıdır: bir seçenek fazladan bir bilgiye ihtiyaç duyduğunda tüm satırların biçimi
değişir.

**Çok biçimlilik**, her seçenek kendi durumunu taşıdığında, birden çok işlemi birlikte
gerçekleştirdiğinde ya da seçenek sayısı veriyle belirlendiğinde karşılığını verir. Sabit
bedeli vardır — bu değişiklikte iki dosya ve sekiz satır — ve seçenekler basit veriyken bu
bedel boşa gider.

## Özet

- Aynı davranış üç yapıyla yazıldı ve üçü de aynı sonuçları üretti; karşılaştırma davranış
  sabitken yapıldı.
- Dördüncü tarife türü eklendiğinde tablo bir dosyada bir satır, koşul zinciri bir dosyada
  üç satır, çok biçimlilik iki dosyada sekiz satır değişiklik istedi.
- Tür adının geçtiği yer sayısı koşul zincirinde üç, diğer ikisinde birdir; işlem sayısı N
  ve tür sayısı T iken koşul zinciri N × T karar taşır, tablo T satır taşır.
- Çok biçimlilikte var olan türlerin dosyası hiç açılmadı; bedeli, yeni tür için ayrı bir
  dosya ve tür kaydına bir satırdır.
- Tablo satırı sabit biçimli bir veri kaydıdır; tip kendi durumunu taşır. Üç sözleşmeli
  tarife tek bir davranış tanımından üretildi, sözleşme sayısı arttığında kod satırı
  artmaz.

## Sonraki Adım

Bu konu boyunca kararlar kodun **iç** yapısı üzerineydi: adlar, düzeyler, biçim, dallanma
ve seçenek yapısı. Bir fonksiyonun dışarıya söyledikleri henüz ele alınmadı. `tarife`
fonksiyonu bilinmeyen bir tür için hata atıyor; oysa aynı sorunun yaygın çözümü boş bir
değer döndürmektir. Boş değer döndüren bir imza, çağıran tarafa hiçbir şey söylemez ve
hata, boşluğun üretildiği yerde değil kullanıldığı yerde ortaya çıkar. Sonraki konu bu
mesafeyi ölçerek başlar: boş değer geçirilen bir çağrı zincirinde hata, kaç çağrı sonra
görünür hâle gelir.
