---
title: 'İhtiyacın Olmayacak'
source: 'https://academia.sh/tr/kurslar/temiz-kod/ihtiyacin-olmayacak'
course: 'Temiz Kod'
language: tr
updated: '2026-08-17T18:10:37+00:00'
license: 'CC BY-SA 4.0'
---

# İhtiyacın Olmayacak

Ücret hesabına bugünden eklenen bir eklenti noktasıyla yapılandırma katmanının karar noktası, dal kapsamı ve çalışmayan yol sayısıyla ölçülmesi; gelen gereksinimin bu yapıya uymadığının başarısız koşumla gösterilmesi ve genelleştirmenin atılması.

Önceki derste her adım gelmiş bir gereksinime karşılık geldi ve dört kural beş adımda
karşılandı. Gelmemiş bir gereksinim için bugünden yazılan yapının ne kadara mal olduğu
ölçülmedi. Bu ders ücret hesabına bugünden bir eklenti noktası ile bir yapılandırma
katmanı ekler, ikisinin ürettiği karar noktalarını ve hiç çalışmayan yolları sayar, sonra
gerçekten gelen gereksinimin bu yapıya uymadığını gösterir.

İlkenin adı **ihtiyacın olmayacak** (you aren't gonna need it): bir yetenek, ona duyulan
gereksinim ortaya çıkana kadar yazılmaz. İlke bir tahmin yasağıdır — yazılım geliştiricinin
gelecekteki gereksinimi doğru tahmin edemeyeceğini değil, tahminin **yanlış çıkma
maliyetinin** ölçülebilir olduğunu söyler.

Başlangıç noktası önceki dersin son adımıdır.

```js
// tarife.mjs — kademe bilgisinin tek kaynagi
export const KADEME = [
  { ustGram: 1000, kurus: 4990, ad: 'kucuk' },
  { ustGram: 5000, kurus: 7490, ad: 'orta' },
  { ustGram: 10000, kurus: 9900, ad: 'buyuk' },
  { ustGram: 20000, kurus: 12900, ad: 'cok-buyuk' },
  { ustGram: Infinity, kurus: 24900, ad: 'agir' },
];

export function kademe(gram) {
  return KADEME.find((k) => gram <= k.ustGram);
}
```

```js
// ucret.mjs — onceki dersin son adimindan devralindi
import { kademe } from './tarife.mjs';

const BOLGE_KATSAYI = { B1: 1.0, B2: 1.25, B3: 1.6 };
const ASGARI_KURUS = 6990;

export function etkinAgirlik(g) {
  return Math.max(g.gram, Math.ceil((g.en * g.boy * g.yukseklik) / 3));
}

export function gonderiUcreti(g) {
  const bolgeli = Math.round(kademe(etkinAgirlik(g)).kurus * BOLGE_KATSAYI[g.bolge]);
  return Math.max(bolgeli, ASGARI_KURUS);
}
```

```js
// ucret.test.mjs — onceki dersten devralinan dort test
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { gonderiUcreti } from './ucret.mjs';

const kutu = { en: 20, boy: 15, yukseklik: 10 };

test('kademe ucreti agirliktan okunur', () => {
  assert.equal(gonderiUcreti({ ...kutu, gram: 3000, bolge: 'B1' }), 7490);
});

test('bolge katsayisi taban ucreti carpar', () => {
  assert.equal(gonderiUcreti({ ...kutu, gram: 3000, bolge: 'B3' }), 11984);
});

test('asgari ucret bolge carpimindan sonra uygulanir', () => {
  assert.equal(gonderiUcreti({ ...kutu, gram: 500, bolge: 'B2' }), 6990);
});

test('hacim agirligi gercek agirliktan buyukse o kullanilir', () => {
  assert.equal(gonderiUcreti({ en: 40, boy: 30, yukseklik: 30, gram: 500, bolge: 'B2' }), 16125);
});
```

Ölçü için bir sayaç gerekiyor: bir dosyadaki **karar noktalarını**, yani programın iki
ayrı yoldan birini seçtiği yerleri sayan bir betik.

```js
// dal.mjs — verilen dosyalardaki karar noktalarini sayar
import { readFileSync } from 'node:fs';

const ORUNTU = /\bif\b|\bcase\b|\?\?|&&|\|\||\?\.|\?(?![?.])/g;

let toplam = 0;
for (const yol of process.argv.slice(2)) {
  const n = (readFileSync(yol, 'utf8').match(ORUNTU) ?? []).length;
  console.log(`${yol}: ${n} karar noktasi`);
  toplam += n;
}
console.log(`toplam: ${toplam}`);
```

```sh
node --test --test-reporter=tap ucret.test.mjs | grep -E '^ *(ok|not ok|# (tests|pass|fail))'
node dal.mjs ucret.mjs
```

```
ok 1 - kademe ucreti agirliktan okunur
ok 2 - bolge katsayisi taban ucreti carpar
ok 3 - asgari ucret bolge carpimindan sonra uygulanir
ok 4 - hacim agirligi gercek agirliktan buyukse o kullanilir
# tests 4
# pass 4
# fail 0
ucret.mjs: 0 karar noktasi
toplam: 0
```

## Bugünden Yazılan İki Esneklik

Tarife kuralları zamanla değişecek. Bu doğru bir gözlem ve yanlış bir gerekçe: değişimin
geleceğini bilmek, hangi biçimde geleceğini bilmek anlamına gelmez. Yine de iki yapı
bugünden yazılıyor. Birincisi, hesabın her ayrıntısını dışarıdan seçilebilir kılan bir
yapılandırma katmanı.

```js
// yapilandirma.mjs — bugunden yazilan yapilandirma katmani
export const VARSAYILAN = {
  hacimUygula: true,
  hacimBolen: 3,
  asgariUygula: true,
  asgariKurus: 6990,
  yuvarlama: 'yakin',
  katsayiKaynagi: 'sabit',
};

export function yapilandir(secenek = {}) {
  return { ...VARSAYILAN, ...secenek };
}
```

İkincisi, ileride gelecek ücret kurallarının hesaba dışarıdan takılmasını sağlayan bir
**eklenti noktası** (extension point).

```js
// eklenti.mjs — ucret kurallari icin eklenti noktasi
const kurallar = [];

export function kuralEkle(ad, oncelik, uygula) {
  kurallar.push({ ad, oncelik, uygula });
  kurallar.sort((a, b) => a.oncelik - b.oncelik);
}

export function kurallariSifirla() {
  kurallar.length = 0;
}

export function kurallariUygula(kurus, g, yap) {
  let sonuc = kurus;
  for (const k of kurallar) {
    sonuc = k.uygula(sonuc, g, yap);
  }
  return sonuc;
}
```

Ücret hesabı ikisini de kullanacak biçimde yeniden yazılıyor. Davranış aynı kalıyor:
varsayılan yapılandırmayla ve kayıtlı kural olmadan sonuçlar değişmez.

```js
// ucret.mjs — bugunden genellestirilmis surum
import { kademe } from './tarife.mjs';
import { yapilandir } from './yapilandirma.mjs';
import { kurallariUygula } from './eklenti.mjs';

const SABIT_KATSAYI = { B1: 1.0, B2: 1.25, B3: 1.6 };

function katsayi(g, yap) {
  if (yap.katsayiKaynagi === 'dis') return yap.katsayiTablosu?.[g.bolge] ?? 1;
  return SABIT_KATSAYI[g.bolge] ?? 1;
}

function yuvarla(kurus, yap) {
  if (yap.yuvarlama === 'yok') return kurus;
  if (yap.yuvarlama === 'yukari') return Math.ceil(kurus);
  return Math.round(kurus);
}

export function etkinAgirlik(g, yap) {
  if (!yap.hacimUygula) return g.gram;
  return Math.max(g.gram, Math.ceil((g.en * g.boy * g.yukseklik) / yap.hacimBolen));
}

export function gonderiUcreti(g, secenek = {}) {
  const yap = yapilandir(secenek);
  const bolgeli = yuvarla(kademe(etkinAgirlik(g, yap)).kurus * katsayi(g, yap), yap);
  const asgarili = yap.asgariUygula ? Math.max(bolgeli, yap.asgariKurus) : bolgeli;
  return kurallariUygula(asgarili, g, yap);
}
```

## Ödenen Bedelin Ölçülmesi

Testler değişmedi ve yeşil kalıyor. Değişen şey testlerin **kapsayabildiği** yapının
büyüklüğü. Karar noktaları ile birlikte, koşumun gerçekten hangi yolları geçtiğini veren
dal kapsamı da ölçülüyor.

```sh
node --test --test-reporter=tap ucret.test.mjs | grep -E '^ *(ok|not ok|# (tests|pass|fail))'
node dal.mjs ucret.mjs yapilandirma.mjs eklenti.mjs
node --test --experimental-test-coverage --test-reporter=tap ucret.test.mjs 2>&1 \
  | sed -n '/start of coverage report/,/end of coverage report/p'
```

```
ok 1 - kademe ucreti agirliktan okunur
ok 2 - bolge katsayisi taban ucreti carpar
ok 3 - asgari ucret bolge carpimindan sonra uygulanir
ok 4 - hacim agirligi gercek agirliktan buyukse o kullanilir
# tests 4
# pass 4
# fail 0
ucret.mjs: 8 karar noktasi
yapilandirma.mjs: 0 karar noktasi
eklenti.mjs: 0 karar noktasi
toplam: 8
# start of coverage report
# -----------------------------------------------------------------
# file             | line % | branch % | funcs % | uncovered lines
# -----------------------------------------------------------------
# eklenti.mjs      |  63.16 |    66.67 |   33.33 | 5-7 10-11 16-17
# tarife.mjs       | 100.00 |   100.00 |  100.00 | 
# ucret.mjs        | 100.00 |    45.45 |  100.00 | 
# yapilandirma.mjs | 100.00 |   100.00 |  100.00 | 
# -----------------------------------------------------------------
# all files        |  90.41 |    63.16 |   80.00 | 
# -----------------------------------------------------------------
# end of coverage report
```

Üç sayı okunmaya değer. Karar noktası sayısı sıfırdan sekize çıktı; bunların hiçbiri bir
alan kuralından gelmiyor, hepsi bir seçeneğin hangi değerde olduğunu sınıyor. Ücret
dosyasının satır kapsamı yüzde 100 ama dal kapsamı yüzde 45,45: her satır çalışıyor,
yolların yarısından fazlası çalışmıyor. Eklenti dosyasında `kuralEkle` ve `kurallariSifirla`
hiç çağrılmadığı için işlev kapsamı yüzde 33,33'te kalıyor ve kapsanmayan satırlar tek tek
listeleniyor.

Bu yolların bakımsız kalmasının bir yolu yok: dal kapsamını yüzde 100'e çıkarmak, altı ek
karar noktasının her biri için en az bir test daha yazmak demek. Bu testlerin sınayacağı
davranışı bugün hiçbir çağrı yeri istemiyor. Yapılandırma katmanı altı alan taşıyor ve
çağrı yerlerinin geçtiği seçenek sayısı sıfır; eklenti listesine kayıtlı kural sayısı da
sıfır.

## Gelen Gereksinim

Sözleşmeli müşteriler için bir kural geliyor: bir fatura döneminde elliden çok gönderisi
olan müşterinin **o dönemdeki bütün gönderilerine** yüzde on iki indirim uygulanacak.
Eklenti noktası tam bu tür kurallar için yazılmıştı; kural oraya takılmayı deniyor.

```js
// donem.mjs — donem indirimini eklenti noktasi uzerinden yazma denemesi
import { gonderiUcreti } from './ucret.mjs';
import { kuralEkle, kurallariSifirla } from './eklenti.mjs';

const ESIK = 50;
const ORAN = 0.88;

export function donemUcreti(gonderiler) {
  kurallariSifirla();
  let sayac = 0;
  kuralEkle('donem-indirimi', 10, (kurus) => {
    sayac += 1;
    return sayac > ESIK ? Math.round(kurus * ORAN) : kurus;
  });
  return gonderiler.reduce((t, g) => t + gonderiUcreti(g), 0);
}
```

```js
// donem.test.mjs — gelen gereksinimin tek testi
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { donemUcreti } from './donem.mjs';

const donem = Array.from({ length: 60 }, () => ({
  en: 20, boy: 15, yukseklik: 10, gram: 3000, bolge: 'B1',
}));

test('esigi asan donemde butun gonderiler indirimli toplanir', () => {
  assert.equal(donemUcreti(donem), Math.round(60 * 7490 * 0.88));
});
```

```sh
node --test --test-reporter=tap donem.test.mjs \
  | grep -E '^ *(ok|not ok|expected:|actual:|# (tests|pass|fail))'
```

```
not ok 1 - esigi asan donemde butun gonderiler indirimli toplanir
  expected: 395472
  actual: 440410
# tests 1
# pass 0
# fail 1
```

Aradaki 44.938 kuruşluk fark, eklenti noktasının **yanlış eksende** genelleştirdiğini
gösteriyor. Kural imzası `(kurus, gonderi, yapilandirma)` biçimindedir: kural tek bir
gönderiyi görür. Gelen gereksinim ise bir gönderinin ücretini **kümenin büyüklüğüne** göre
belirliyor; elli birinci gönderi işlenirken önceki ellisinin ücreti çoktan toplanmış
oluyor. Eklenti bunu ancak kümeyi iki kez dolaşarak karşılayabilir, o zaman da toplamayı
yapan çağıran taraf işin tamamını üstlenmiş olur; eklenti noktası bir işe yaramaz.

Genelleştirme, gelecekteki değişimin **gönderi başına bir kural** biçiminde geleceğini
varsaymıştı. Gerçekte gelen değişim gönderi başına değil dönem başına. Yapılandırma
katmanında da benzeri geçerli: altı alanın hiçbiri indirim eşiğini ya da oranını
karşılamıyor.

## Genelleştirmeyi Atmak

Atılan yapının büyüklüğü sayılabilir.

```sh
printf 'atilan dosyalarin satir sayisi: %s\n' \
  "$(cat yapilandirma.mjs eklenti.mjs donem.mjs | wc -l | tr -d ' ')"
rm yapilandirma.mjs eklenti.mjs donem.mjs donem.test.mjs
```

```
atilan dosyalarin satir sayisi: 48
```

Yerine gelen gereksinim doğrudan karşılanıyor: dönem ücretini hesaplayan bir fonksiyon,
tek eşik ve tek oran.

```js
// ucret.mjs — genellestirme atildi, gelen gereksinim dogrudan karsilandi
import { kademe } from './tarife.mjs';

const BOLGE_KATSAYI = { B1: 1.0, B2: 1.25, B3: 1.6 };
const ASGARI_KURUS = 6990;
const INDIRIM_ESIGI = 50;
const INDIRIM_ORANI = 0.88;

export function etkinAgirlik(g) {
  return Math.max(g.gram, Math.ceil((g.en * g.boy * g.yukseklik) / 3));
}

export function gonderiUcreti(g) {
  const bolgeli = Math.round(kademe(etkinAgirlik(g)).kurus * BOLGE_KATSAYI[g.bolge]);
  return Math.max(bolgeli, ASGARI_KURUS);
}

export function donemUcreti(gonderiler) {
  const toplam = gonderiler.reduce((t, g) => t + gonderiUcreti(g), 0);
  if (gonderiler.length <= INDIRIM_ESIGI) return toplam;
  return Math.round(toplam * INDIRIM_ORANI);
}
```

```js
// ucret.test.mjs — dort eski test ve donem indiriminin iki testi
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { gonderiUcreti, donemUcreti } from './ucret.mjs';

const kutu = { en: 20, boy: 15, yukseklik: 10 };
const donem = (adet) => Array.from({ length: adet }, () => ({ ...kutu, gram: 3000, bolge: 'B1' }));

test('kademe ucreti agirliktan okunur', () => {
  assert.equal(gonderiUcreti({ ...kutu, gram: 3000, bolge: 'B1' }), 7490);
});

test('bolge katsayisi taban ucreti carpar', () => {
  assert.equal(gonderiUcreti({ ...kutu, gram: 3000, bolge: 'B3' }), 11984);
});

test('asgari ucret bolge carpimindan sonra uygulanir', () => {
  assert.equal(gonderiUcreti({ ...kutu, gram: 500, bolge: 'B2' }), 6990);
});

test('hacim agirligi gercek agirliktan buyukse o kullanilir', () => {
  assert.equal(gonderiUcreti({ en: 40, boy: 30, yukseklik: 30, gram: 500, bolge: 'B2' }), 16125);
});

test('esigi asan donemde butun gonderiler indirimli toplanir', () => {
  assert.equal(donemUcreti(donem(60)), Math.round(60 * 7490 * 0.88));
});

test('esigin altindaki donemde indirim uygulanmaz', () => {
  assert.equal(donemUcreti(donem(10)), 10 * 7490);
});
```

```sh
node --test --test-reporter=tap ucret.test.mjs | grep -E '^ *(ok|not ok|# (tests|pass|fail))'
node dal.mjs ucret.mjs
node --test --experimental-test-coverage --test-reporter=tap ucret.test.mjs 2>&1 \
  | sed -n '/start of coverage report/,/end of coverage report/p'
```

```
ok 1 - kademe ucreti agirliktan okunur
ok 2 - bolge katsayisi taban ucreti carpar
ok 3 - asgari ucret bolge carpimindan sonra uygulanir
ok 4 - hacim agirligi gercek agirliktan buyukse o kullanilir
ok 5 - esigi asan donemde butun gonderiler indirimli toplanir
ok 6 - esigin altindaki donemde indirim uygulanmaz
# tests 6
# pass 6
# fail 0
ucret.mjs: 1 karar noktasi
toplam: 1
# start of coverage report
# -----------------------------------------------------------
# file       | line % | branch % | funcs % | uncovered lines
# -----------------------------------------------------------
# tarife.mjs | 100.00 |   100.00 |  100.00 | 
# ucret.mjs  | 100.00 |   100.00 |  100.00 | 
# -----------------------------------------------------------
# all files  | 100.00 |   100.00 |  100.00 | 
# -----------------------------------------------------------
# end of coverage report
```

Tek karar noktası kaldı ve o karar noktası bir alan kuralına karşılık geliyor: dönem
eşiğin altında mı, değil mi. Dal kapsamı yüzde 100; kod tabanında koşumun geçmediği yol
yok. Ücret dosyası bir modül ithal ediyor, önceki sürüm üçünü ithal ediyordu.

| Ölçüt | Genelleştirilmiş | Doğrudan |
|---|---|---|
| Karar noktası | 8 | 1 |
| Ücret dosyasının dal kapsamı | %45,45 | %100 |
| Bütün dosyaların satır kapsamı | %90,41 | %100 |
| İthal edilen modül | 3 | 1 |
| Gelen gereksinimi karşılıyor mu | hayır | evet |

## İlkenin Ne Yasakladığı

Bu ders ile önceki ders farklı şeyler ölçtü. Basit tut ilkesi **gelmiş** bir gereksinim
için en az yapıyı ister; ihtiyacın olmayacak ilkesi **gelmemiş** bir gereksinim için
yapı yazılmasını yasaklar. Bir tanesi bugünkü işin biçimiyle, diğeri yarınki iş hakkındaki
tahminle ilgilidir.

İlkenin sınırı, atmanın maliyetindedir. Burada atılan 48 satır kitaplığın içinde kaldı;
hiçbir dış tüketiciyi etkilemedi, `git` deposundaki tek bir işlemeyle geri alınabilirdi.
Aynı hesap, dışarıya yayımlanmış bir arayüzde ya da geriye dönük dönüştürme gerektiren
kalıcı bir veri biçiminde başka sonuç verir: orada yanlış tahminin bedeli tahmini
yapmamanın bedelinden büyük olabilir. İlkeyi uygulanabilir kılan soru "bu ileride gerekir
mi" değil, **"gerektiğinde eklemek bugün eklemekten ne kadar pahalı"** sorusudur. Ücret
kitaplığında bu fark sıfıra yakındı: dönem indirimi, hiçbir eklenti noktası olmadan altı
satırda yazıldı.

## Özet

- İhtiyacın olmayacak ilkesi gelmemiş bir gereksinim için bugünden yapı yazılmasını
  yasaklar; ölçülen büyüklük tahminin yanlış çıkma maliyetidir.
- Bugünden yazılan yapılandırma katmanı ile eklenti noktası, ücret dosyasının karar
  noktası sayısını sıfırdan sekize çıkardı ve dal kapsamını yüzde 45,45'e düşürdü.
- Satır kapsamının yüzde 100 olması yolların çalıştığı anlamına gelmez; çalışmayan yollar
  dal ve işlev kapsamında görünür.
- Gerçekten gelen gereksinim gönderi başına değil dönem başınaydı; eklenti noktasının
  imzası bunu ifade edemedi ve tek testli koşum 44.938 kuruşluk farkla kırmızı bitti.
- Genelleştirmenin atılması 48 satır sildi; doğrudan çözüm tek karar noktası, yüzde 100
  dal kapsamı ve altı testle aynı gereksinimi karşıladı.
- İlkenin ölçütü "ileride gerekir mi" değil, gerektiğinde eklemenin bugün eklemeye göre
  ne kadar pahalı olduğudur.

## Sonraki Adım

Genelleştirme atıldıktan sonra kalan dosya çalışıyor ama düzenlenmiş değil: bölge
katsayısı tablosu tarifeye ait bilgiyi ücret dosyasında tutuyor, dönem hesabındaki eşik
karşılaştırması adsız duruyor ve `g` adı bir gönderiyi temsil ettiğini söylemiyor. Sonraki
ders bu üç kusuru üç mekanik teknikle giderir — ayıklama, taşıma ve yeniden adlandırma —
her tekniği ara adımlarıyla uygular ve her adımdan sonra test koşumunu gösterir.
