---
title: 'Küçük Fonksiyonlar ve Sınıflar'
source: 'https://academia.sh/tr/kurslar/temiz-kod/kucuk-fonksiyonlar-ve-siniflar'
course: 'Temiz Kod'
language: tr
updated: '2026-08-17T18:10:37+00:00'
license: 'CC BY-SA 4.0'
---

# Küçük Fonksiyonlar ve Sınıflar

Bölme ölçütünün satır sayısından çıkarılıp tek soyutlama düzeyi kuralına bağlanması: bir gövdenin kaç ayrı soyutlama düzeyinin sözcüğünü taşıdığını sayan çözümleyici, ücret satırı üretiminin iki sürümünde düzey açıklığının karşılaştırılması ve aynı ölçütün sınıflara uygulanması.

Yorumları ada çevirirken tek bir fonksiyon dört parçaya bölünmüştü ve bölme yerlerini
yorumların durduğu yerler belirlemişti. Bu rastlantısal bir ölçüttür: yorum yazma
alışkanlığı kişiden kişiye değişir. Bölmenin ölçütü nedir? Satır sayısı akla gelen ilk
cevaptır, ama iki satırlık bir fonksiyon da bölünmesi gereken bir şey içerebilir.

Bu ders ölçütü satır sayısından çıkarıp **tek soyutlama düzeyi** kuralına bağlar. Kural
şudur: bir fonksiyonun gövdesi tek bir soyutlama düzeyinin sözcükleriyle konuşmalıdır.
Sözcüklerin düzeyleri sayılabildiği için kural da sayılabilir.

## Kitaplığın Üç Düzeyi

Ücretlendirme kitaplığında üç ayrı sözcük dağarcığı var ve bunlar birbirinden bağımsız
değişir.

- **Ölçü düzeyi:** gönderinin fiziksel ölçüleri ve bunlardan türetilen ağırlık.
  Sözcükleri `enCm`, `boyCm`, `yukseklikCm`, `agirlikKg`, `HACIMSEL_BOLEN`. Taşıyıcı
  hacimsel böleni değiştirdiğinde yalnız bu düzey değişir.
- **Ücret politikası düzeyi:** kademeler, bölge katsayıları, indirim ve asgari ücret.
  Sözcükleri `AGIRLIK_KADEMELERI`, `ustSinirKg`, `kiloBasiUcret`, `BOLGE_KATSAYILARI`,
  `SOZLESMELI_MUSTERI_INDIRIMI`, `ASGARI_UCRET`. Tarife görüşmesi bu düzeyi değiştirir.
- **Sunum düzeyi:** sonucun metne dönüşmesi. Sözcükleri `padEnd`, `toFixed`,
  `PARA_BIRIMI`. Raporun biçimi değiştiğinde yalnız bu düzey değişir.

Bir gövdede kaç düzeyin sözcüğü geçtiği sayılabilir. İki sayı kullanılacak: **düzey
sayısı** (gövdede sözcüğü geçen düzeylerin adedi) ve **açıklık** (en yüksek düzey ile en
düşük düzey arasındaki basamak farkı). Tek soyutlama düzeyi kuralı, düzey sayısının bir
olmasını ister.

## Her İşi Yapan Sürüm

İlk sürüm ücret satırını tek fonksiyonda üretir. Yanına, ölçütün satır sayısı olmadığını
göstermek için iki satırlık ikinci bir fonksiyon konuldu.

```sh
mkdir -p buyuk bolunmus sinif
```

```js
// buyuk/ucret.mjs — ucret satiri uretimi tek fonksiyonda
const HACIMSEL_BOLEN = 3000;
const ASGARI_UCRET = 52;
const SOZLESMELI_MUSTERI_INDIRIMI = 0.15;
const PARA_BIRIMI = "TL";
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 kisaOzet(gonderi) {
  const kg = Math.max(gonderi.agirlikKg, (gonderi.enCm * gonderi.boyCm * gonderi.yukseklikCm) / HACIMSEL_BOLEN);
  return `${AGIRLIK_KADEMELERI.find((k) => kg <= k.ustSinirKg).kiloBasiUcret.toFixed(2)} ${PARA_BIRIMI}/kg`;
}

export function ucretSatiriUret(gonderi, bolgeKodu) {
  const hacimsel = (gonderi.enCm * gonderi.boyCm * gonderi.yukseklikCm) / HACIMSEL_BOLEN;
  const agirlikKg = Math.max(gonderi.agirlikKg, hacimsel);
  const kademe = AGIRLIK_KADEMELERI.find((k) => agirlikKg <= k.ustSinirKg);
  const ham = kademe.kiloBasiUcret * agirlikKg * BOLGE_KATSAYILARI[bolgeKodu];
  const ucret = Math.max(ham * (1 - SOZLESMELI_MUSTERI_INDIRIMI), ASGARI_UCRET);
  return `${gonderi.kod.padEnd(8)} ${agirlikKg.toFixed(2)} kg ${ucret.toFixed(2)} ${PARA_BIRIMI}`;
}
```

## Düzey Ölçer

Çözümleyici kaynağı fonksiyon ve sınıf bloklarına ayırır, her gövdedeki adları toplar ve
düzey sözlüğüyle eşleştirir. Bir fonksiyonun **parametreleri ve yerel değişkenleri
sayılmaz**: bunlar fonksiyonun kendi sözcükleridir, üst düzeyle arasındaki bağdır. Sayılan
şey, gövdenin kendi dışından uzandığı adlardır — modül sabitleri, okuduğu alan adları ve
çağırdığı yerleşik işlemler.

```js
// duzey-olcer.mjs — her fonksiyonun ve sinifin kac soyutlama duzeyinin sozcugunu tasidigi
import { readFileSync } from "node:fs";

// Kitapligin uc soyutlama duzeyi ve her duzeyin sozcukleri.
const DUZEYLER = [
  ["olcu", ["enCm", "boyCm", "yukseklikCm", "agirlikKg", "HACIMSEL_BOLEN"]],
  ["ucret", ["AGIRLIK_KADEMELERI", "ustSinirKg", "kiloBasiUcret", "BOLGE_KATSAYILARI",
             "SOZLESMELI_MUSTERI_INDIRIMI", "ASGARI_UCRET"]],
  ["sunum", ["padEnd", "toFixed", "PARA_BIRIMI"]],
];
const ANAHTAR = new Set(["if", "for", "while", "switch", "catch", "return", "function"]);
const derinlikFarki = (s) => (s.match(/{/g) ?? []).length - (s.match(/}/g) ?? []).length;

// Bir baslangic oruntusunden kapanan suslu paranteze kadar suren bloklari ayirir.
function bloklar(satirlar, oruntu) {
  const cikti = [];
  let acik = null, derinlik = 0;
  satirlar.forEach((satir, i) => {
    if (acik === null) {
      const m = satir.match(oruntu);
      if (!m || ANAHTAR.has(m[1])) return;
      [acik, derinlik] = [{ ad: m[1], imza: m[2] ?? "", bas: i, govde: [] }, 0];
    } else acik.govde.push(satir);
    derinlik += derinlikFarki(satir);
    if (derinlik === 0) { cikti.push({ ...acik, son: i }); acik = null; }
  });
  return cikti;
}

// Parametreler ve yerel degiskenler fonksiyonun kendi sozcukleridir; sayilmaz.
function duzeyler(imza, govde) {
  const kendi = new Set([...imza.matchAll(/\w+/g), ...govde.matchAll(/(?:const|let)\s+(\w+)/g)]
    .map((m) => m[1] ?? m[0]));
  const adlar = new Set([...govde.matchAll(/\w+/g)].map((m) => m[0]).filter((a) => !kendi.has(a)));
  return DUZEYLER.map(([d], i) => [d, i]).filter(([, i]) => DUZEYLER[i][1].some((s) => adlar.has(s)));
}

const yaz = (ad, ek, bulunan) => {
  const i = bulunan.map(([, n]) => n);
  const aciklik = i.length ? Math.max(...i) - Math.min(...i) : 0;
  console.log(`  ${ad.padEnd(26)}${ek}  duzey=${bulunan.length} aciklik=${aciklik}` +
    `  [${bulunan.map(([d]) => d).join(", ") || "yalniz cagri"}]`);
};

for (const dosya of process.argv.slice(2)) {
  const satirlar = readFileSync(dosya, "utf8").split("\n");
  const siniflar = bloklar(satirlar, /^\s*(?:export\s+)?class\s+(\w+)/);
  console.log(dosya);
  for (const s of [...siniflar, null]) {
    const kapsam = (f) => (s ? f.bas > s.bas && f.son <= s.son
                             : siniflar.every((k) => f.bas < k.bas || f.son > k.son));
    const uyeler = bloklar(satirlar, /^\s*(?:export\s+)?(?:function\s+)?(\w+)\s*\(([^)]*)\)\s*{/)
      .filter(kapsam);
    const hepsi = new Map();
    for (const f of uyeler) {
      const bulunan = duzeyler(f.imza, f.govde.join("\n"));
      for (const [d, i] of bulunan) hepsi.set(d, i);
      yaz((s ? `${s.ad}.` : "") + f.ad, `satir=${String(f.govde.length - 1).padEnd(2)}`, bulunan);
    }
    if (s) yaz(`= ${s.ad}`, `uye=${String(uyeler.length).padEnd(4)}`, [...hepsi]);
  }
}
```

## Bölünmüş Sürüm

İkinci sürüm aynı aritmetiği yapar. Her fonksiyon tek düzeyin sözcükleriyle konuşur; en
üstteki fonksiyon hiçbir düzeyin sözcüğüne dokunmaz, yalnız çağırır.

```js
// bolunmus/ucret.mjs — ayni hesap, her fonksiyon tek duzeyde
const HACIMSEL_BOLEN = 3000;
const ASGARI_UCRET = 52;
const SOZLESMELI_MUSTERI_INDIRIMI = 0.15;
const PARA_BIRIMI = "TL";
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 },
];

function ucretlendirilenAgirlikKg(gonderi) {
  const hacimsel = (gonderi.enCm * gonderi.boyCm * gonderi.yukseklikCm) / HACIMSEL_BOLEN;
  return Math.max(gonderi.agirlikKg, hacimsel);
}

function kademeUcreti(agirlikKg) {
  const kademe = AGIRLIK_KADEMELERI.find((k) => agirlikKg <= k.ustSinirKg);
  return kademe.kiloBasiUcret * agirlikKg;
}

function bolgeliUcret(ucret, bolgeKodu) {
  return ucret * BOLGE_KATSAYILARI[bolgeKodu];
}

function indirimliUcret(ucret) {
  return Math.max(ucret * (1 - SOZLESMELI_MUSTERI_INDIRIMI), ASGARI_UCRET);
}

function ucretSatiri(gonderiKodu, agirlikKg, ucret) {
  return `${gonderiKodu.padEnd(8)} ${agirlikKg.toFixed(2)} kg ${ucret.toFixed(2)} ${PARA_BIRIMI}`;
}

export function ucretSatiriUret(gonderi, bolgeKodu) {
  const agirlik = ucretlendirilenAgirlikKg(gonderi);
  const ucret = indirimliUcret(bolgeliUcret(kademeUcreti(agirlik), bolgeKodu));
  return ucretSatiri(gonderi.kod, agirlik, ucret);
}
```

İki sürümün aynı satırı ürettiği doğrulanır.

```js
// cagri.mjs — iki surumun ayni satiri urettigi dogrulanir
import { ucretSatiriUret as buyuk } from "./buyuk/ucret.mjs";
import { ucretSatiriUret as bolunmus } from "./bolunmus/ucret.mjs";

const gonderi = { kod: "GN-4172", agirlikKg: 2.4, enCm: 30, boyCm: 24, yukseklikCm: 18 };
console.log(buyuk(gonderi, 2));
console.log(bolunmus(gonderi, 2));
```

```
GN-4172  4.32 kg 128.89 TL
GN-4172  4.32 kg 128.89 TL
```

## Ölçüm

```sh
node duzey-olcer.mjs buyuk/ucret.mjs bolunmus/ucret.mjs
```

```
buyuk/ucret.mjs
  kisaOzet                  satir=2   duzey=3 aciklik=2  [olcu, ucret, sunum]
  ucretSatiriUret           satir=6   duzey=3 aciklik=2  [olcu, ucret, sunum]
bolunmus/ucret.mjs
  ucretlendirilenAgirlikKg  satir=2   duzey=1 aciklik=0  [olcu]
  kademeUcreti              satir=2   duzey=1 aciklik=0  [ucret]
  bolgeliUcret              satir=1   duzey=1 aciklik=0  [ucret]
  indirimliUcret            satir=1   duzey=1 aciklik=0  [ucret]
  ucretSatiri               satir=1   duzey=1 aciklik=0  [sunum]
  ucretSatiriUret           satir=3   duzey=0 aciklik=0  [yalniz cagri]
```

Tablo satır sayısı ile düzey sayısını yan yana koyuyor ve ikisinin bağımsız olduğunu
gösteriyor. `kisaOzet` iki satırdır ve üç düzeyi birden taşır: hacimsel ağırlığı hesaplar,
kademe seçer, biçimlendirir. Bölünmüş sürümdeki `ucretSatiriUret` üç satırdır ve hiçbir
düzeyin sözcüğüne dokunmaz. Kısalık ölçüt olsaydı ilki kabul edilir, ikincisi ile arasında
fark görülmezdi.

Bölünmüş sürümde altı fonksiyonun beşi tek düzeyde, biri sıfır düzeydedir. Sıfır, gövdenin
yalnız çağrılardan oluştuğu anlamına gelir: en üstteki fonksiyon işin **sırasını** söyler,
işin kendisini söylemez. Bir kişi ücretin nasıl hesaplandığını değil hangi adımlardan
geçtiğini öğrenmek istiyorsa yalnız o üç satırı okur.

## Bölme Nerede Durur

Ölçüt kendi durma koşulunu taşır. Düzey sayısı bire indiğinde ayıklanacak bir şey kalmaz;
bir düzeyi ikiye bölmek yeni bir düzey üretmez, aynı düzeyde iki fonksiyon üretir ve
çağrı sayısını arttırır. `bolgeliUcret` tek satırlıktır ve daha küçüğe bölünemez.

Ters yönde de bir sınır var. `indirimliUcret` iki işi yapıyor gibi görünür: indirimi
uygular ve asgari ücrete çeker. Bunlar ayrılabilir, ama ikisi de ücret politikası
düzeyindedir ve **sıraları anlamlıdır** — indirim asgari ücretten önce uygulanır, tersi
farklı bir sonuç verir. Aynı düzeyde ve sırası bağlayıcı olan iki adım, ayrıldığında
sıranın korunması çağıran tarafın sorumluluğuna geçer. Bölme kararı burada düzey sayısına
değil, sıranın nerede korunacağına bakar.

## Aynı Kural Sınıflarda

Sınıf, durumu paylaşan fonksiyonların bir araya getirilmiş hâlidir; ölçüt de aynı ölçütün
toplamı olur. Bir sınıfın küçüklüğü yöntem sayısıyla değil, üyelerinin toplamda kaç düzeye
yayıldığıyla ölçülür.

```js
// sinif/tek.mjs — tek sinif, uc duzeyin durumunu birlikte tutuyor
const HACIMSEL_BOLEN = 3000;
const PARA_BIRIMI = "TL";
const AGIRLIK_KADEMELERI = [{ ustSinirKg: 1, kiloBasiUcret: 38 }, { ustSinirKg: 5, kiloBasiUcret: 26 },
  { ustSinirKg: 10, kiloBasiUcret: 21 }, { ustSinirKg: 30, kiloBasiUcret: 17 }];

export class GonderiUcreti {
  constructor(gonderi) {
    this.gonderi = gonderi;
  }
  agirlik() {
    const hacimsel = (this.gonderi.enCm * this.gonderi.boyCm * this.gonderi.yukseklikCm) / HACIMSEL_BOLEN;
    return Math.max(this.gonderi.agirlikKg, hacimsel);
  }
  ucret() {
    const kg = this.agirlik();
    return AGIRLIK_KADEMELERI.find((k) => kg <= k.ustSinirKg).kiloBasiUcret * kg;
  }
  satir() {
    return `${this.ucret().toFixed(2)} ${PARA_BIRIMI}`;
  }
}
```

Bu sınıfın her yöntemi tek düzeydedir; fonksiyon ölçütü hiçbir kusur görmez. Kusur
toplamdadır: üç yöntem üç ayrı düzeye ait olduğu için sınıfın üç ayrı değişme nedeni
vardır. Ayrılmış sürümde her birim tek düzeyde kalır.

```js
// sinif/ayri.mjs — ayni is, her sinif tek duzeyde
const HACIMSEL_BOLEN = 3000;
const PARA_BIRIMI = "TL";
const AGIRLIK_KADEMELERI = [{ ustSinirKg: 1, kiloBasiUcret: 38 }, { ustSinirKg: 5, kiloBasiUcret: 26 },
  { ustSinirKg: 10, kiloBasiUcret: 21 }, { ustSinirKg: 30, kiloBasiUcret: 17 }];

export class Olcum {
  constructor(gonderi) {
    this.gonderi = gonderi;
  }
  ucretlendirilenKg() {
    const hacimsel = (this.gonderi.enCm * this.gonderi.boyCm * this.gonderi.yukseklikCm) / HACIMSEL_BOLEN;
    return Math.max(this.gonderi.agirlikKg, hacimsel);
  }
}

export class Tarife {
  kademeUcreti(kg) {
    return AGIRLIK_KADEMELERI.find((k) => kg <= k.ustSinirKg).kiloBasiUcret * kg;
  }
}

export function ucretSatiri(ucret) {
  return `${ucret.toFixed(2)} ${PARA_BIRIMI}`;
}
```

```sh
node duzey-olcer.mjs sinif/tek.mjs sinif/ayri.mjs
```

```
sinif/tek.mjs
  GonderiUcreti.constructor satir=1   duzey=0 aciklik=0  [yalniz cagri]
  GonderiUcreti.agirlik     satir=2   duzey=1 aciklik=0  [olcu]
  GonderiUcreti.ucret       satir=2   duzey=1 aciklik=0  [ucret]
  GonderiUcreti.satir       satir=1   duzey=1 aciklik=0  [sunum]
  = GonderiUcreti           uye=4     duzey=3 aciklik=2  [olcu, ucret, sunum]
sinif/ayri.mjs
  Olcum.constructor         satir=1   duzey=0 aciklik=0  [yalniz cagri]
  Olcum.ucretlendirilenKg   satir=2   duzey=1 aciklik=0  [olcu]
  = Olcum                   uye=2     duzey=1 aciklik=0  [olcu]
  Tarife.kademeUcreti       satir=1   duzey=1 aciklik=0  [ucret]
  = Tarife                  uye=1     duzey=1 aciklik=0  [ucret]
  ucretSatiri               satir=1   duzey=1 aciklik=0  [sunum]
```

`= ` ile başlayan satırlar sınıfın toplamıdır. `GonderiUcreti` dört üyeyle üç düzeye
yayılıyor; `Olcum` iki üyeyle bir düzeyde, `Tarife` bir üyeyle bir düzeyde. Üye sayısı
küçüldüğü için değil, düzey sayısı bire indiği için küçüldüler. Dört yöntemi de ölçü
düzeyinde olan bir sınıf, üç yöntemi üç düzeye yayılan bir sınıftan küçüktür.

## Özet

- Bölmenin ölçütü satır sayısı değil, bir gövdenin kaç soyutlama düzeyinin sözcüğünü
  taşıdığıdır; ücretlendirme kitaplığının üç düzeyi ölçü, ücret politikası ve sunumdur.
- Ölçüm parametreleri ve yerel değişkenleri saymaz; sayılan şey gövdenin kendi dışından
  uzandığı adlardır.
- İki satırlık `kisaOzet` üç düzey taşıyor, üç satırlık `ucretSatiriUret` hiçbir düzeye
  dokunmuyor: kısalık ile düzey sayısı bağımsız ölçütlerdir.
- Bölünmüş sürümde altı fonksiyonun beşi tek düzeyde, biri yalnız çağrıdan oluşuyor; iki
  sürüm aynı satırı üretiyor.
- Düzey sayısı bire indiğinde bölme durur; aynı düzeydeki iki adımın sırası bağlayıcıysa
  ayırma kararı sıranın nerede korunacağına bakar.
- Sınıfın küçüklüğü yöntem sayısıyla değil üyelerinin toplam düzey sayısıyla ölçülür:
  üç düzeye yayılan dört üyeli sınıf, tek düzeyde kalan iki sınıfa bölündü.

## Sonraki Adım

Bölünmüş sürümde her fonksiyon tek düzeyde konuşuyor, ama dosyanın kendisi hakkında henüz
bir şey söylenmedi: satırların nerede biteceği, hangi bildirimlerin yan yana duracağı,
girintinin ne kadar olacağı. Bunlar üslup tercihi sayılır ve tartışması genellikle zevke
bağlanır. Oysa biçim kararının ölçülebilir bir sonucu vardır: aynı içerik değişikliği,
farklı biçimlerde farklı sayıda fark satırı üretir. Sonraki ders tarife tablosunu iki ayrı
biçimde yazıp aynı değişikliği ikisine de uygular ve `diff` çıktısındaki satır sayısını
karşılaştırır.
