---
title: Bileşim
source: 'https://academia.sh/tr/kurslar/paradigmalar/bilesim'
course: 'Programlama Paradigmaları'
language: tr
updated: '2026-08-17T18:10:41+00:00'
license: 'CC BY-SA 4.0'
---

# Bileşim

Küçük fonksiyonlardan büyük davranış kurma: ücretlendirmenin dört adımlık bir boru hattına ayrılması, aynı kusurun iki tasarımda kaç testi düşürdüğünün sayılması ve iki adımın yeri değiştiğinde sonucun kaç kuruş kaydığının ölçülmesi.

Önceki ders kuralları fonksiyona çevirdi ve `reduce` ile sırayla uyguladı. Ara sonuçlar
hiçbir yerde adlandırılmadı: kural listesi bir dizi olarak geçti, çekirdek onları arka
arkaya çağırdı. Kural sayısı arttıkça asıl soru sıranın kendisi olur — indirimin ekten önce
mi sonra mı işlediği, asgari ücret tabanının nerede uygulandığı sonucu değiştirir.

Bu ders o ardışık uygulamayı bir boru hattı olarak kurar. Ücretlendirme dört adlandırılmış
adıma bölünür, her adım tek başına sınanır, sonra aynı kusur iki tasarıma da sokulup kaç
testin düştüğü sayılır. Son ölçüm sıraya aittir: iki adımın yeri değiştiğinde kaç gönderinin
ücreti kaç kuruş kayar.

## İki Ayrı Bileşim

**Bileşim (composition)** bu kursta iki ayrı anlamda geçer ve ikisi karıştırılmamalıdır.
Nesne bileşimi, bir nesnenin başka bir nesneyi üye olarak tutup ona iş devretmesidir; aynı
kursun nesneye dayalı konusunda kalıtımın karşısına konan seçenek odur. Bu dersteki
**fonksiyon bileşimi (function composition)** ise iki fonksiyonun birinin çıktısını
diğerinin girdisi yaparak tek bir fonksiyon oluşturmasıdır. Ortak yan, büyük davranışın
küçük parçalardan kurulmasıdır; birleştirilen şeyin ne olduğu farklıdır — biri nesne, diğeri
fonksiyondur.

## Adımların Ayrılması

Ücretlendirme dört karardan oluşur: ağırlık kademesinin seçilmesi, bölge katsayısının
uygulanması, sözleşme indirimi ve asgari ücret tabanı. Her karar ayrı bir fonksiyona
alınır. Adımların ortak bir biçimi vardır: hepsi bir **hesap** kaydı alır, aynı biçimde bir
hesap kaydı döndürür. Kayıt değiştirilmez; her adım yayma sözdizimiyle yeni bir kayıt üretir.

```js
// adim.mjs — tarife verisi ve boru hattinin adimlari; her adim hesap alir, hesap dondurur
export const TARIFE = {
  asgariUcret: 4990,
  kademeler: [
    { ustAgirlikGram: 1000, ucret: 4990 },
    { ustAgirlikGram: 5000, ucret: 6490 },
    { ustAgirlikGram: 15000, ucret: 10900 },
  ],
  bolgeKatsayisi: { 1: 1.0, 2: 1.15, 3: 1.35 },
};

export const GONDERILER = [
  { kod: "TR-4471", agirlikGram: 800, bolge: 1, sozlesmeli: true },
  { kod: "TR-4472", agirlikGram: 3200, bolge: 2, sozlesmeli: true },
  { kod: "TR-4473", agirlikGram: 12400, bolge: 3, sozlesmeli: false },
  { kod: "TR-4474", agirlikGram: 950, bolge: 2, sozlesmeli: true },
  { kod: "TR-4475", agirlikGram: 4800, bolge: 1, sozlesmeli: false },
  { kod: "TR-4476", agirlikGram: 14900, bolge: 2, sozlesmeli: true },
];

export const kademeSec = (h) => ({
  ...h,
  ucret: (h.tarife.kademeler.find((k) => h.gonderi.agirlikGram <= k.ustAgirlikGram)
    ?? h.tarife.kademeler.at(-1)).ucret,
});

export const bolgeCarpani = (h) => {
  const katsayi = h.tarife.bolgeKatsayisi[h.gonderi.bolge];
  return { ...h, ucret: Math.round(h.ucret * katsayi) };
};

export const sozlesmeIndirimi = (h) =>
  h.gonderi.sozlesmeli ? { ...h, ucret: Math.round(h.ucret * 0.88) } : h;

export const asgariTaban = (h) => ({
  ...h,
  ucret: Math.max(h.tarife.asgariUcret, h.ucret),
});
```

Adımları birleştiren şey ayrı ve çok kısadır. `hat`, aldığı adımları soldan sağa uygulayan
bir fonksiyon döndürür; önceki dersteki kural uygulayıcısıyla aynı `reduce` iskeletidir, ama
bu kez ürettiği şey bir sayı değil, çağrılabilir bir hattır.

```js
// hat.mjs — adimlari ardisik uygulayan bilesim ve kurulmus ucretlendirme hatti
import { kademeSec, bolgeCarpani, sozlesmeIndirimi, asgariTaban } from "./adim.mjs";

export const hat = (...adimlar) => (baslangic) =>
  adimlar.reduce((hesap, adim) => adim(hesap), baslangic);

export const ucretlendir = hat(kademeSec, bolgeCarpani, sozlesmeIndirimi, asgariTaban);
```

Aynı dört karar tek bir gövdede de yazılabilir. Karşılaştırmanın ölçülebilir olması için
denk bir sürüm gerekir.

```js
// tekparca.mjs — ayni dort adim tek fonksiyonun govdesinde
export function ucretlendirTekParca(gonderi, tarife) {
  const kademe = tarife.kademeler.find((k) => gonderi.agirlikGram <= k.ustAgirlikGram)
    ?? tarife.kademeler.at(-1);
  const katsayi = tarife.bolgeKatsayisi[gonderi.bolge];
  let ucret = Math.round(kademe.ucret * katsayi);
  if (gonderi.sozlesmeli) ucret = Math.round(ucret * 0.88);
  return Math.max(tarife.asgariUcret, ucret);
}
```

## Ara Adımların Sınanması

İki tasarımın farkı testte görünür. Boru hattında her adım dışa verilmiş bir fonksiyondur;
testi, o adımın okuduğu birkaç alandan ibaret küçük bir hesap kaydıyla yazılır.

```js
// hat.test.mjs — dort adim tek tek, hat bir kez sinanir
import test from "node:test";
import assert from "node:assert/strict";
import { TARIFE, GONDERILER, kademeSec, bolgeCarpani, sozlesmeIndirimi,
  asgariTaban } from "./adim.mjs";
import { ucretlendir } from "./hat.mjs";

const hesap = (gonderi, ucret = 0) => ({ gonderi, tarife: TARIFE, ucret });

test("kademe secimi ust siniri kapsar", () => {
  assert.equal(kademeSec(hesap({ agirlikGram: 1000 })).ucret, 4990);
  assert.equal(kademeSec(hesap({ agirlikGram: 1001 })).ucret, 6490);
});

test("bolge carpani ucreti olcekler", () => {
  assert.equal(bolgeCarpani(hesap({ bolge: 3 }, 10000)).ucret, 13500);
});

test("sozlesme indirimi yalnizca sozlesmeli gonderiye iner", () => {
  assert.equal(sozlesmeIndirimi(hesap({ sozlesmeli: true }, 10000)).ucret, 8800);
  assert.equal(sozlesmeIndirimi(hesap({ sozlesmeli: false }, 10000)).ucret, 10000);
});

test("asgari taban altina inilmez", () => {
  assert.equal(asgariTaban(hesap({}, 3000)).ucret, 4990);
});

test("hat alti gonderiyi uctan uca ucretlendirir", () => {
  assert.deepEqual(GONDERILER.map((g) => ucretlendir(hesap(g)).ucret),
    [4990, 6567, 14715, 5050, 6490, 11031]);
});
```

Tek parça sürümde aynı dört kararın hiçbirine doğrudan erişilemez. Testler aynı adları
taşıyabilir, ama hepsi uçtan uca çağrıdır: her biri tam bir gönderi ile tarifeyi kurar ve
yalnızca son sayıya bakar.

```js
// tekparca.test.mjs — ayni dort karar, yalnizca uctan uca sinanabiliyor
import test from "node:test";
import assert from "node:assert/strict";
import { TARIFE, GONDERILER } from "./adim.mjs";
import { ucretlendirTekParca } from "./tekparca.mjs";

test("kademe secimi ust siniri kapsar", () => {
  assert.equal(ucretlendirTekParca(
    { agirlikGram: 1000, bolge: 1, sozlesmeli: false }, TARIFE), 4990);
  assert.equal(ucretlendirTekParca(
    { agirlikGram: 1001, bolge: 1, sozlesmeli: false }, TARIFE), 6490);
});

test("bolge carpani ucreti olcekler", () => {
  assert.equal(ucretlendirTekParca(
    { agirlikGram: 12400, bolge: 3, sozlesmeli: false }, TARIFE), 14715);
});

test("sozlesme indirimi yalnizca sozlesmeli gonderiye iner", () => {
  assert.equal(ucretlendirTekParca(
    { agirlikGram: 3200, bolge: 2, sozlesmeli: true }, TARIFE), 6567);
  assert.equal(ucretlendirTekParca(
    { agirlikGram: 3200, bolge: 2, sozlesmeli: false }, TARIFE), 7463);
});

test("asgari taban altina inilmez", () => {
  assert.equal(ucretlendirTekParca(
    { agirlikGram: 800, bolge: 1, sozlesmeli: true }, TARIFE), 4990);
});

test("alti gonderi uctan uca ucretlendirilir", () => {
  assert.deepEqual(GONDERILER.map((g) => ucretlendirTekParca(g, TARIFE)),
    [4990, 6567, 14715, 5050, 6490, 11031]);
});
```

Her iki dosya da beş deneme içerir ve ikisi de geçer; son denemelerin aynı altı sayıyı
beklemesi, iki tasarımın davranışça denk olduğunu gösterir.

## Kusurun Yerini Testin Söylemesi

Fark, testler geçerken değil, bir test düştüğünde ortaya çıkar. Aynı kusur iki tasarıma da
sokulur: bölge katsayısı bir yerine iki kez uygulanır. Kusur, kaynak dosyaların birer
kopyasına metin değişikliğiyle sokulur; asıl dosyalar bozulmaz.

```sh
# Ayni kusur iki tasarima da sokulur: bolge katsayisi iki kez uygulaniyor.
perl -pe 's/h\.ucret \* katsayi\)/h.ucret * katsayi * katsayi)/' adim.mjs > adim-bozuk.mjs
perl -pe 's/kademe\.ucret \* katsayi\)/kademe.ucret * katsayi * katsayi)/' \
  tekparca.mjs > tekparca-bozuk.mjs
perl -pe 's{\./adim\.mjs}{./adim-bozuk.mjs}' hat.mjs > hat-bozuk.mjs
perl -pe 's{\./adim\.mjs}{./adim-bozuk.mjs}; s{\./hat\.mjs}{./hat-bozuk.mjs}' \
  hat.test.mjs > hat-bozuk.test.mjs
perl -pe 's{\./tekparca\.mjs}{./tekparca-bozuk.mjs}' \
  tekparca.test.mjs > tekparca-bozuk.test.mjs

for t in hat tekparca; do
  cikti=$(node --test --test-reporter=tap "$t-bozuk.test.mjs" 2>&1)
  echo "--- $t"
  printf '%s\n' "$cikti" | grep -E '^# (tests|fail)'
  printf '%s\n' "$cikti" | grep '^not ok' | sed 's/^not ok [0-9]* - /  basarisiz: /'
done
```

```
--- hat
# tests 5
# fail 2
  basarisiz: bolge carpani ucreti olcekler
  basarisiz: hat alti gonderiyi uctan uca ucretlendirir
--- tekparca
# tests 5
# fail 3
  basarisiz: bolge carpani ucreti olcekler
  basarisiz: sozlesme indirimi yalnizca sozlesmeli gonderiye iner
  basarisiz: alti gonderi uctan uca ucretlendirilir
```

Sayılar üç şey söyler. Boru hattında beş denemenin ikisi düşer; düşenlerden biri kusurlu
adımı adıyla gösterir, diğeri uçtan uca denemedir. Tek parça sürümde üç deneme düşer ve
düşenlerden biri sözleşme indirimini sınayan denemedir — indirimde hiçbir kusur olmadığı
hâlde. Neden şudur: o denemenin girdisi ikinci bölgeye ait olduğu için, bozulan çarpma o
yoldan da geçer.

Üçüncüsü, bölge birinci olan denemelerin ikisinin de geçmesidir. Birinci bölgenin katsayısı
1,0 olduğundan iki kez uygulanmak sonucu değiştirmez; kusur o yolda görünmez. Bu, uçtan uca
denemelerin ortak zayıflığıdır: her deneme yolun tamamından geçer, dolayısıyla düşen deneme
kümesi kusurun yerini değil, girdilerin hangi yollara denk geldiğini anlatır.

## Sıranın Sonuca Etkisi

Boru hattında adımlar birbirinin çıktısını okuduğu için sıra sonucun parçasıdır. Sözleşme
indirimi ile asgari ücret tabanının yeri değiştirilip iki koşum karşılaştırılır.

```js
// sira.mjs — ayni dort adim, iki farkli sira: sonuc kac gonderide kayiyor
import { TARIFE, GONDERILER, kademeSec, bolgeCarpani, sozlesmeIndirimi,
  asgariTaban } from "./adim.mjs";
import { hat } from "./hat.mjs";

const indirimOnce = hat(kademeSec, bolgeCarpani, sozlesmeIndirimi, asgariTaban);
const tabanOnce = hat(kademeSec, bolgeCarpani, asgariTaban, sozlesmeIndirimi);

const calis = (f) => GONDERILER.map((g) => f({ gonderi: g, tarife: TARIFE, ucret: 0 }).ucret);
const a = calis(indirimOnce);
const b = calis(tabanOnce);
const degisen = a.filter((u, i) => u !== b[i]).length;
const fark = a.reduce((t, u, i) => t + Math.abs(u - b[i]), 0);
const tabanAltinda = b.filter((u) => u < TARIFE.asgariUcret).length;

console.log(`indirim -> taban : ${a.join(", ")}`);
console.log(`taban -> indirim : ${b.join(", ")}`);
console.log(`degisen ucret    : ${degisen}/6   toplam fark: ${fark} kurus`);
console.log(`asgari altinda   : ${tabanAltinda}/6`);
```

```
indirim -> taban : 4990, 6567, 14715, 5050, 6490, 11031
taban -> indirim : 4391, 6567, 14715, 5050, 6490, 11031
degisen ucret    : 1/6   toplam fark: 599 kurus
asgari altinda   : 1/6
```

Altı gönderiden yalnızca birinin ücreti değişmiştir ve fark 599 kuruştur. Sayının küçüklüğü
aldatıcıdır: değişen tek gönderi, tabanın koruduğu gönderidir. Taban indirimden önce
uygulandığında indirim tabanı deler ve sonuç asgari ücretin altına iner. Kurumun kuralı —
hesaplanan ücret asgari ücretin altına düşemez — altı gönderiden birinde çiğnenmiştir.

Buradan iki sonuç çıkar. Birincisi, sıra bir uygulama ayrıntısı değil, iş kuralının
parçasıdır; taban, kendisinden sonra ücreti düşüren hiçbir adım olmayacak biçimde
konumlanmalıdır. İkincisi, hattın kendisi bir sınama konusudur: adımların hepsi tek tek
doğru olduğu hâlde hat yanlış olabilir. Adım testlerinin yanına uçtan uca bir denemenin
konmasının nedeni budur.

## Bileşimin Ödettiği

Ortak kayıt biçimi bileşimi mümkün kılan şeydir, ama aynı zamanda adımlar arasındaki tek
bağdır. Dört adımın hepsi `gonderi`, `tarife` ve `ucret` alanlarını tanıyan bir kayda
bağlıdır; yeni bir adım yeni bir alan istediğinde o alan kaydın biçimine girer ve kendisine
ihtiyaç duymayan adımların da taşıdığı bir yük olur. Kayıt büyüdükçe hangi adımın hangi
alanı okuduğu görünmez hâle gelir.

İkinci bedel ara değerlerin gözlenmesidir. Tek parça sürümde hata ayıklanırken `ucret`
değişkeninin her satırdaki değeri okunabilir. Boru hattında ara değer hiçbir yerde
adlandırılmadığı için, iki adımın arasına yazdıran bir izleme adımı sokulması gerekir. Bunun
maliyeti düşüktür — izleme adımı da hesabı olduğu gibi döndüren bir adımdır ve listeye
eklenir — ama o adım saf değildir ve hattın içinde kalıcı olarak bırakılmamalıdır. Yan
etkilerin nereye konacağı bir sonraki dersin konusudur.

## Özet

- Fonksiyon bileşimi, bir fonksiyonun çıktısını diğerinin girdisi yapmaktır; nesne
  bileşiminden farkı, birleştirilen şeyin nesne değil fonksiyon olmasıdır.
- Ücretlendirme dört adıma bölündüğünde her adım kendi küçük girdisiyle sınanabilir; tek
  parça sürümde aynı dört karar yalnızca uçtan uca çağrıyla sınanabilir.
- Aynı kusur iki tasarıma sokulduğunda boru hattında beş denemenin ikisi, tek parça sürümde
  üçü düşer; tek parça sürümde düşenlerden biri kusurla ilgisiz bir kuralı sınayan denemedir.
- Birinci bölgeye ait iki deneme her iki tasarımda da geçer: uçtan uca denemelerin düşme
  örüntüsü kusurun yerini değil, girdilerin denk geldiği yolları gösterir.
- Adımların sırası sonucun parçasıdır: taban ile indirimin yeri değiştiğinde altı gönderiden
  birinin ücreti 599 kuruş kayar ve asgari ücret kuralı bir gönderide çiğnenir.
- Bileşimin bedeli ortak kayıt biçimidir: her yeni alan, ona ihtiyaç duymayan adımların da
  taşıdığı bir yüktür.

## Sonraki Adım

Bu dersin dört adımı da saftır: hiçbiri okumadığı bir şeye bakmaz, hiçbiri dışarı yazmaz.
Gerçek bir ücretlendirme koşumu ise saf değildir — gönderiler bir dosyadan okunur, gece
tarifesinin geçerli olup olmadığı saate bakılarak belirlenir, sonuçlar bir kayıt dosyasına
yazılır. Sonraki ders bu üç bağımlılığın nereye konacağını ele alır: girdi/çıktı ve zaman
programın sınırına itildiğinde çekirdeğin testi için gereken sahte bağımlılık sayısı, iç içe
düzenle karşılaştırmalı olarak sayılır.
