---
title: 'Kendini Tekrar Etme'
source: 'https://academia.sh/tr/kurslar/temiz-kod/kendini-tekrar-etme'
course: 'Temiz Kod'
language: tr
updated: '2026-08-17T18:10:37+00:00'
license: 'CC BY-SA 4.0'
---

# Kendini Tekrar Etme

Aynı bilginin iki yerde durmasıyla tesadüfen benzeyen iki kodun ayrılması, ayrışan kademe sınırlarının sayılması ve iki çağrı yerini tek fonksiyonda birleştirmenin ürettiği yanlış soyutlamanın bayrak sayısıyla ölçülmesi.

Arayüz ve Veri Kararları konusu, aynı işin projede tek biçimde yapılmasıyla kapandı.
Buraya kadar verilen kararların ortak yanı **yerel** olmalarıydı: bir adın niyeti
açıklaması, bir imzanın boş değer geçirmemesi, bir fonksiyonun tek soyutlama düzeyinde
durması. Her biri tek bir dosyaya, çoğu zaman tek bir satıra bakarak denetlenebilirdi.

Kod tabanı büyüdükçe bu denetimin yetmediği bir maliyet doğar. Aynı bilgi birden çok
yerde durur ve bir gün biri güncellenip diğeri unutulur; bugün gerekmeyen bir esneklik
bugünden yazılır ve her okuyanın bedelini ödediği bir dallanmaya dönüşür; bir kararı
değiştirmek kaç dosyaya dokunmayı gerektirdiğiyle ölçülür hâle gelir. Bu konu üç ilkeyi
bu maliyet üzerinden ele alır. İlki, aynı bilginin tek bir yerde durmasını isteyen
**kendini tekrar etme** ilkesidir (don't repeat yourself).

İlkenin sık rastlanan okunuşu "aynı satırları iki kez yazma" biçimindedir ve yanlıştır.
Birbirinden ayrılması gereken iki durum vardır: aynı **bilginin** iki yerde durması ile
şu an tesadüfen birbirine **benzeyen** iki kod.

## Bilgi Yinelemesi

Gönderi ücretlendirme ve yönlendirme kitaplığında ağırlık kademeleri iki modülde
geçiyor. Birincisi taban ücreti hesaplıyor.

```js
// ucret.mjs — agirlik kademesine gore taban ucret (kurus)
const KADEME = [
  { ustGram: 1000, kurus: 4990 },
  { ustGram: 5000, kurus: 7490 },
  { ustGram: 20000, kurus: 12900 },
  { ustGram: Infinity, kurus: 24900 },
];

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

İkincisi operasyon ekranında gönderinin hangi kademede olduğunu gösteriyor.

```js
// ozet.mjs — operasyon ekranindaki kademe rozeti
const KADEME = [
  { ustGram: 1000, ad: 'kucuk' },
  { ustGram: 5000, ad: 'orta' },
  { ustGram: 20000, ad: 'buyuk' },
  { ustGram: Infinity, ad: 'agir' },
];

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

İki tablo aynı görünmüyor: birinde kuruş, diğerinde ad var. Yinelenen şey kod değil,
**kademe sınırlarının nerede olduğu bilgisidir**. Bu bilginin tek olup olmadığı
ölçülebilir: iki fonksiyonun çıktısının değiştiği gram değerleri karşılaştırılır.

```js
// sinir.mjs — iki dosyanin kademe sinirlarini karsilastirir
import { agirlikUcreti } from './ucret.mjs';
import { kademeAdi } from './ozet.mjs';

function sinirlar(f) {
  const s = [];
  for (let g = 2; g <= 25000; g++) if (f(g) !== f(g - 1)) s.push(g);
  return s;
}

const ucret = sinirlar(agirlikUcreti);
const rozet = sinirlar(kademeAdi);
const ayrisan = ucret.filter((g) => !rozet.includes(g)).length
  + rozet.filter((g) => !ucret.includes(g)).length;

console.log('ucret sinirlari:', ucret.join(' '));
console.log('rozet sinirlari:', rozet.join(' '));
console.log('ayrisan sinir sayisi:', ayrisan);
```

```
ucret sinirlari: 1001 5001 20001
rozet sinirlari: 1001 5001 20001
ayrisan sinir sayisi: 0
```

Bugün sıfır. Yinelemenin maliyeti bugünde değil, ilk değişikliktedir. Tarifeye 10
kilogramlık bir ara kademe eklendiğini, değişikliği yapan kişinin ücret dosyasını bulup
rozet dosyasını görmediğini varsayalım.

```js
// ucret.mjs — 10 kg kademesi eklendi
const KADEME = [
  { ustGram: 1000, kurus: 4990 },
  { ustGram: 5000, kurus: 7490 },
  { ustGram: 10000, kurus: 9900 },
  { ustGram: 20000, kurus: 12900 },
  { ustGram: Infinity, kurus: 24900 },
];

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

```sh
node sinir.mjs
```

```
ucret sinirlari: 1001 5001 10001 20001
rozet sinirlari: 1001 5001 20001
ayrisan sinir sayisi: 1
```

Ayrışan sınır sayısı bire çıktı. Bunun alan karşılığı şudur: 5.001 ile 10.000 gram
arasındaki her gönderi için müşteriye 99,00 TL'lik kademe ücreti yansıtılırken operasyon
ekranı gönderiyi "buyuk" rozetiyle gösteriyor. İki modül aynı gönderi hakkında farklı
şey söylüyor ve hiçbir test kırılmadı, çünkü her modülün testi kendi tablosuna göre
yazılmıştı.

Çözüm, bilgiyi tek bir yere taşımaktı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 — tarifeden okur
import { kademe } from './tarife.mjs';

export function agirlikUcreti(gram) {
  return kademe(gram).kurus;
}
```

```js
// ozet.mjs — ayni tarifeden okur
import { kademe } from './tarife.mjs';

export function kademeAdi(gram) {
  return kademe(gram).ad;
}
```

```sh
node sinir.mjs
```

```
ucret sinirlari: 1001 5001 10001 20001
rozet sinirlari: 1001 5001 10001 20001
ayrisan sinir sayisi: 0
```

Kazanılan şey satır sayısı değil. Kademe sınırlarını değiştirmek için dokunulan dosya
sayısı ikiden bire indi ve ayrışma imkânı ortadan kalktı: iki modül aynı satırı
okuduğu için tutarsız olamazlar.

## Tesadüfi Benzerlik

İlkenin ikinci yüzü, birbirine benzeyen ama aynı bilgiyi taşımayan kodlardır. Kitaplıkta
iki belge satırı üretiliyor: birincisi pakete yapıştırılan etiket, ikincisi muhasebeye
giden fatura satırı.

```js
// belge.mjs — surum 0: iki cagri yeri, bugun ayni metin
export function gonderiEtiketi(g) {
  return `${g.no} ${g.bolge} ${(g.kurus / 100).toFixed(2)} TL`;
}

export function faturaSatiri(g) {
  return `${g.no} ${g.bolge} ${(g.kurus / 100).toFixed(2)} TL`;
}
```

İki gövde karakter karakter aynı. Kendini tekrar etme ilkesinin yüzeysel okunuşu bunları
birleştirmeyi söyler. Oysa iki satırın aynı olması bir **bilgi** değil, bir **rastlantı**:
etiketin biçimini depo operasyonu, fatura satırının biçimini muhasebe belirliyor ve bu
iki merci birbirinden habersiz karar veriyor. Kodun Aktöre Göre Düzenlenmesi dersindeki
ölçüt burada geçerlidir: iki parçanın değişme nedenleri farklıysa aynı gövdede
durmamalıdırlar.

Birleştirmenin ne ürettiğini görmek için birleştirilmiş sürümü yazmak gerekir. İlk iki
farklılaşma talebi geldiğinde ortak fonksiyon şu hâli alıyor.

```js
// belge.mjs — surum 1: iki cagri yeri tek fonksiyonda birlestirildi
export function belgeSatiri(g, { kdvEkle = false, rotaGoster = false, isoParaBirimi = false } = {}) {
  const tutar = kdvEkle ? Math.round(g.kurus * 1.2) : g.kurus;
  const alan = rotaGoster ? g.rota : g.bolge;
  const birim = isoParaBirimi ? 'TRY' : 'TL';
  return `${g.no} ${alan} ${(tutar / 100).toFixed(2)} ${birim}`;
}

export const gonderiEtiketi = (g) => belgeSatiri(g, { rotaGoster: true });
export const faturaSatiri = (g) => belgeSatiri(g, { kdvEkle: true, isoParaBirimi: true });
```

Bu, Arayüz ve Veri Kararları konusunda bölünmesi öğütlenen mantıksal bayrak
parametrelerinin üç tanesinin tek imzada toplanmış hâlidir. Bayrak uzayının ne kadarının
gerçekten kullanıldığı sayılabilir.

```js
// yollar.mjs — bayrak uzayi ile gercekten kullanilan yollari karsilastirir
import { belgeSatiri } from './belge.mjs';

const bayraklar = ['kdvEkle', 'rotaGoster', 'isoParaBirimi'];
const gonderi = { no: 'G-1042', bolge: 'B2', rota: 'IST-ANK', kurus: 7490 };

const tumu = new Set();
for (let m = 0; m < 2 ** bayraklar.length; m++) {
  const secenek = Object.fromEntries(bayraklar.map((b, i) => [b, Boolean(m & (1 << i))]));
  tumu.add(belgeSatiri(gonderi, secenek));
}
const kullanilan = new Set([
  belgeSatiri(gonderi, { rotaGoster: true }),
  belgeSatiri(gonderi, { kdvEkle: true, isoParaBirimi: true }),
]);

console.log('bayrak sayisi:', bayraklar.length);
console.log('kombinasyon sayisi:', tumu.size);
console.log('cagri yerlerinin kullandigi:', kullanilan.size);
console.log('hic calismayan kombinasyon:', tumu.size - kullanilan.size);
```

```
bayrak sayisi: 3
kombinasyon sayisi: 8
cagri yerlerinin kullandigi: 2
hic calismayan kombinasyon: 6
```

Sekiz kombinasyondan altısı hiçbir zaman çalışmıyor. Bu altı yol yine de okunuyor, yine
de bakımı yapılıyor ve fonksiyonu okuyan herkes bunların mümkün olduğunu varsaymak
zorunda kalıyor. **Yanlış soyutlama** budur: gövdesi ortak, sözleşmesi ortak olmayan bir
fonksiyon.

## Yanlış Soyutlamanın Ölçülen Bedeli

Asıl bedel, bayrak sayısında değil, iki çağrı yerinin birbirini kısıtlamasında görünür.
İki çağrı yerinin sözleşmesi testlerle sabitlenmiş durumda.

```js
// belge.test.mjs — iki cagri yerinin sozlesmesi
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { gonderiEtiketi, faturaSatiri } from './belge.mjs';

const gonderi = { no: 'G-1042', bolge: 'B2', rota: 'IST-ANK', kurus: 7490 };

test('etiket rota kodunu ve KDVsiz tutari yazar', () => {
  assert.equal(gonderiEtiketi(gonderi), 'G-1042 IST-ANK 74.90 TL');
});

test('fatura satiri bolgeyi ve KDVli tutari ISO birimiyle yazar', () => {
  assert.equal(faturaSatiri(gonderi), 'G-1042 B2 89.88 TRY');
});
```

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

```
ok 1 - etiket rota kodunu ve KDVsiz tutari yazar
ok 2 - fatura satiri bolgeyi ve KDVli tutari ISO birimiyle yazar
# tests 2
# pass 2
# fail 0
```

Muhasebe yeni bir kural bildiriyor: fatura tutarı tam TL'ye yuvarlanacak. Bu, yalnız
fatura satırını ilgilendiren bir karar. Değişiklik ortak fonksiyondaki tutar hesabına
yazılıyor.

```js
// belge.mjs — surum 2: tam TL yuvarlama ortak yere yazildi
export function belgeSatiri(g, { kdvEkle = false, rotaGoster = false, isoParaBirimi = false } = {}) {
  const ham = kdvEkle ? Math.round(g.kurus * 1.2) : g.kurus;
  const tutar = Math.round(ham / 100) * 100;
  const alan = rotaGoster ? g.rota : g.bolge;
  const birim = isoParaBirimi ? 'TRY' : 'TL';
  return `${g.no} ${alan} ${(tutar / 100).toFixed(2)} ${birim}`;
}

export const gonderiEtiketi = (g) => belgeSatiri(g, { rotaGoster: true });
export const faturaSatiri = (g) => belgeSatiri(g, { kdvEkle: true, isoParaBirimi: true });
```

Fatura testinin beklentisi yeni kurala göre güncelleniyor; etiket testi olduğu gibi
bırakılıyor, çünkü etiket hakkında hiçbir karar değişmedi.

```js
// belge.test.mjs — fatura beklentisi yeni gereksinime gore guncellendi
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { gonderiEtiketi, faturaSatiri } from './belge.mjs';

const gonderi = { no: 'G-1042', bolge: 'B2', rota: 'IST-ANK', kurus: 7490 };

test('etiket rota kodunu ve KDVsiz tutari yazar', () => {
  assert.equal(gonderiEtiketi(gonderi), 'G-1042 IST-ANK 74.90 TL');
});

test('fatura satiri tam TL yuvarlanmis tutari yazar', () => {
  assert.equal(faturaSatiri(gonderi), 'G-1042 B2 90.00 TRY');
});
```

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

```
not ok 1 - etiket rota kodunu ve KDVsiz tutari yazar
ok 2 - fatura satiri tam TL yuvarlanmis tutari yazar
# tests 2
# pass 1
# fail 1
```

Muhasebeye ait bir karar, depo operasyonuna ait bir çıktıyı bozdu. Etiketteki tutar
74,90 TL yerine 75,00 TL yazılmaya başladı. Kısıtlama sayısal olarak şudur: yuvarlamanın
yalnız faturaya uygulanması için dördüncü bir bayrak gerekir, bayrak sayısı üçten dörde,
kombinasyon sayısı sekizden on altıya çıkar, gerçekten kullanılan yol sayısı iki
kalır. Her farklılaşma talebi kombinasyon uzayını iki katına çıkarır ve kullanılan yol
sayısını sabit bırakır.

## Ayırmanın Maliyeti

Doğru hamle birleştirmeyi geri almaktır. İki gövde, ait oldukları kararla birlikte
yeniden ayrılır.

```js
// belge.mjs — surum 3: benzerlik tesadufiydi, iki fonksiyon ayrildi
export function gonderiEtiketi(g) {
  return `${g.no} ${g.rota} ${(g.kurus / 100).toFixed(2)} TL`;
}

export function faturaSatiri(g) {
  const kdvli = Math.round(g.kurus * 1.2);
  const tamTl = Math.round(kdvli / 100) * 100;
  return `${g.no} ${g.bolge} ${(tamTl / 100).toFixed(2)} TRY`;
}
```

```sh
node --test --test-reporter=tap belge.test.mjs | grep -E '^ *(ok|not ok|# (tests|pass|fail))'
printf 'ucluk isleci sayisi: %s\n' "$(grep -c ' ? ' belge.mjs)"
```

```
ok 1 - etiket rota kodunu ve KDVsiz tutari yazar
ok 2 - fatura satiri tam TL yuvarlanmis tutari yazar
# tests 2
# pass 2
# fail 0
ucluk isleci sayisi: 0
```

Bayrak sayısı üçten sıfıra, dallanma sayısı üçten sıfıra indi. Yinelenen görünen tek
şey `g.no` ve `toFixed(2)` çağrısı; ikisi de bilgi değil, dilin sözdizimi. İki
fonksiyonun bundan sonra ayrı ayrı değişmesi, birinin diğerini bozma ihtimali olmadan
mümkün.

İki durumu ayıran ölçüt tek bir sorudur: iki parçadan biri değiştiğinde diğerinin de
değişmesi **zorunlu** mu? Kademe sınırlarında zorunluydu; sınır bir yerde kaydığında
diğer yerde de kaymalıydı, kaymadığı için sistem tutarsızlaştı. Belge satırlarında
zorunlu değildi; muhasebe kararı depo çıktısını değiştirmemeliydi, değiştirdiği için
test kırıldı. Yineleme sorusu satırlara değil, **değişim nedenlerine** bakılarak
yanıtlanır.

## Özet

- Kendini tekrar etme ilkesi satır benzerliğiyle değil bilginin tekliğiyle ilgilidir;
  aynı bilginin iki yerde durması ile tesadüfen benzeyen iki kod ayrı durumlardır.
- Yinelenen bilginin ayrışması ölçülebilir: kademe tablosu iki dosyada tutulduğunda tek
  taraflı bir değişiklik ayrışan sınır sayısını sıfırdan bire çıkardı ve hiçbir test
  kırılmadı.
- Bilgiyi tek kaynağa taşımak dokunulan dosya sayısını ikiden bire indirdi ve iki
  modülün tutarsızlaşma imkânını kaldırdı.
- Tesadüfen benzeyen iki gövdeyi birleştirmek yanlış soyutlama üretir: üç bayrak, sekiz
  kombinasyon ve gerçekten kullanılan iki yol.
- Yanlış soyutlamanın bedeli çağrı yerlerinin birbirini kısıtlamasıdır; muhasebeye ait
  bir yuvarlama kararı etiket testini kırdı ve ayrımı korumak dördüncü bir bayrak
  gerektirdi.
- Ayırma kararı tek soruyla verilir: biri değiştiğinde diğerinin değişmesi zorunlu mu?

## Sonraki Adım

Bu derste iki değişiklik yapıldı ve ikisi de küçüktü: bir tablo tek dosyaya taşındı, bir
fonksiyon ikiye ayrıldı. Adımların küçük olması rastlantı değil, bir yöntem. Sonraki
ders aynı gereksinimi iki farklı biçimde karşılar — tek seferde tasarlanmış bir çözümle
ve küçük adımlarla evrilen bir çözümle — ve her adımda dokunulan satır sayısı ile
testlerin yeşil kaldığı adım sayısı karşılaştırılır.
