---
title: 'Komut–Sorgu Ayrımı'
source: 'https://academia.sh/tr/kurslar/tasarim-ilkeleri/komut-sorgu-ayrimi'
course: 'Tasarım İlkeleri'
language: tr
updated: '2026-08-17T18:10:40+00:00'
license: 'CC BY-SA 4.0'
---

# Komut–Sorgu Ayrımı

Durum değiştiren ve okuyan işlemlerin ayrılması: hem yazan hem döndüren yöntemleri bulan bir taramanın yazılması, yalnız rapor üreten bir yolun kaç kaydı değiştirdiğinin sayılması ve günlük satırının gerçekte olanın tersini yazdığının koşumla gösterilmesi.

Önceki dersin `yolaCikar` yöntemi bir emir gibi adlandırılmıştı ama yalnız bir sonuç
döndürüyordu; gönderinin durumu değişmiyordu. Gerçek bir sevk işlemi durumu da değiştirmeli.
Yöntem bu iki işi birlikte üstlendiğinde yeni bir soru doğar: aynı yöntemi ikinci kez
çağırmak neyi değiştirir?

**Komut–sorgu ayrımı** (command–query separation) Programlama Temelleri kursunda tanıtılmıştı:
bir işlem ya durumu değiştirir ve değer döndürmez (komut), ya da değer döndürür ve durumu
değiştirmez (sorgu). Bu ders ilkeyi nesne tasarımına taşır ve ihlalinin bedelini iki sayıyla
ölçer: hem yazan hem döndüren yöntem sayısı ve yalnız okumak isteyen bir yolun değiştirdiği
kayıt sayısı.

## Karışık Yöntem

Birinci tasarımda `yolaCikar` hem `durum` alanını yazar hem sonucu döndürür.

```sh
mkdir -p karisik ayrik
```

```js
// karisik/kayit.mjs — her deney taze kayitlarla baslasin diye uretici
export const yeniKayitlar = () => [
  { kod: "GN-1", durum: "depoda", odendi: true, agirlik: 12 },
  { kod: "GN-2", durum: "depoda", odendi: true, agirlik: 45 },
  { kod: "GN-3", durum: "yolda", odendi: true, agirlik: 8 },
  { kod: "GN-4", durum: "depoda", odendi: false, agirlik: 5 },
  { kod: "GN-5", durum: "depoda", odendi: true, agirlik: 25 },
];
```

```js
// karisik/gonderi.mjs — tek yontem hem durumu degistiriyor hem deger donduruyor
const AGIRLIK_SINIRI = 30;

export const gonderi = (kayit) => ({
  kod: kayit.kod,
  yolaCikar() {
    if (kayit.durum !== "depoda") return { ok: false, sebep: "depoda_degil" };
    if (kayit.odendi === false) return { ok: false, sebep: "odenmedi" };
    if (kayit.agirlik > AGIRLIK_SINIRI) return { ok: false, sebep: "agirlik_siniri" };
    kayit.durum = "yolda";
    return { ok: true, sebep: "yola_cikti" };
  },
});
```

```js
// karisik/sevk.mjs — once sevk eder, sonra gunluge yazmak icin sebebi yeniden sorar
export function sevkEt(gonderiler) {
  const cikan = gonderiler.filter((g) => g.yolaCikar().ok).map((g) => g.kod);
  const gunluk = gonderiler.map((g) => `${g.kod}=${g.yolaCikar().sebep}`);
  return { cikan, gunluk };
}
```

```js
// karisik/rapor.mjs — yalniz sebepleri yazmak isteyen rapor
export const rapor = (gonderiler) => gonderiler.map((g) => `${g.kod}=${g.yolaCikar().sebep}`);
```

Rapor modülü hiçbir şeyi sevk etmek istemiyor; yalnız her gönderinin durumunu yazacak. Elinde
soracak tek yöntem olduğu için onu çağırıyor.

## Ayrık Yöntemler

İkinci tasarımda aynı bilgi iki yönteme bölünür. `sevkEngeli` bir sorgudur: durumu okur,
değiştirmez. `yolaCikar` bir komuttur: durumu değiştirir, değer döndürmez; sevk edilemeyen
bir gönderi için hata verir.

```js
// ayrik/kayit.mjs — her deney taze kayitlarla baslasin diye uretici
export const yeniKayitlar = () => [
  { kod: "GN-1", durum: "depoda", odendi: true, agirlik: 12 },
  { kod: "GN-2", durum: "depoda", odendi: true, agirlik: 45 },
  { kod: "GN-3", durum: "yolda", odendi: true, agirlik: 8 },
  { kod: "GN-4", durum: "depoda", odendi: false, agirlik: 5 },
  { kod: "GN-5", durum: "depoda", odendi: true, agirlik: 25 },
];
```

```js
// ayrik/gonderi.mjs — sorgu durum degistirmez, komut deger dondurmez
const AGIRLIK_SINIRI = 30;

export const gonderi = (kayit) => ({
  kod: kayit.kod,
  sevkEngeli() {
    if (kayit.durum !== "depoda") return "depoda_degil";
    if (kayit.odendi === false) return "odenmedi";
    if (kayit.agirlik > AGIRLIK_SINIRI) return "agirlik_siniri";
    return null;
  },
  yolaCikar() {
    const engel = this.sevkEngeli();
    if (engel !== null) throw new RangeError(`${kayit.kod} sevk edilemez: ${engel}`);
    kayit.durum = "yolda";
  },
});
```

```js
// ayrik/sevk.mjs — once sorar, gunluge yazar, sonra komutu verir
export function sevkEt(gonderiler) {
  const cikan = [];
  const gunluk = [];
  for (const g of gonderiler) {
    const engel = g.sevkEngeli();
    gunluk.push(`${g.kod}=${engel ?? "yola_cikti"}`);
    if (engel === null) {
      g.yolaCikar();
      cikan.push(g.kod);
    }
  }
  return { cikan, gunluk };
}
```

```js
// ayrik/rapor.mjs — yalniz sebepleri yazmak isteyen rapor
export const rapor = (gonderiler) =>
  gonderiler.map((g) => `${g.kod}=${g.sevkEngeli() ?? "yola_cikti"}`);
```

## Sürprizin Koşumla Gösterilmesi

Sürücü betik iki deney yapar. Birincisinde yalnız rapor çağrılır ve raporun kaç kaydın
durumunu değiştirdiği sayılır. İkincisinde sevk işlemi çalıştırılır, günlük satırları yazılır
ve aynı işlem ikinci kez çağrılır.

```js
// kur.mjs — uc deney: rapor, sevk gunlugu ve ayni islemin ikinci kez cagrilmasi
const kok = process.argv[2];
const { yeniKayitlar } = await import(`./${kok}/kayit.mjs`);
const { gonderi } = await import(`./${kok}/gonderi.mjs`);
const { rapor } = await import(`./${kok}/rapor.mjs`);
const { sevkEt } = await import(`./${kok}/sevk.mjs`);

const kayitlar = yeniKayitlar();
const once = kayitlar.map((k) => k.durum);
console.log("rapor      ", rapor(kayitlar.map(gonderi)).join(" "));
const sonra = kayitlar.map((k) => k.durum);
console.log(`raporun degistirdigi kayit = ${once.filter((d, i) => d !== sonra[i]).length}`);

const ikinciKayitlar = yeniKayitlar();
const gonderiler = ikinciKayitlar.map(gonderi);
const birinci = sevkEt(gonderiler);
console.log("sevk 1 cikan", birinci.cikan.join(" ") || "(bos)");
console.log("sevk 1 gunluk", birinci.gunluk.join(" "));
const ikinci = sevkEt(gonderiler);
console.log("sevk 2 cikan", ikinci.cikan.join(" ") || "(bos)");
```

```sh
node kur.mjs karisik
```

```
rapor       GN-1=yola_cikti GN-2=agirlik_siniri GN-3=depoda_degil GN-4=odenmedi GN-5=yola_cikti
raporun degistirdigi kayit = 2
sevk 1 cikan GN-1 GN-5
sevk 1 gunluk GN-1=depoda_degil GN-2=agirlik_siniri GN-3=depoda_degil GN-4=odenmedi GN-5=depoda_degil
sevk 2 cikan (bos)
```

İki sürpriz birden. Rapor iki kaydın durumunu "yolda" yaptı: hiçbir şey sevk etmek istemeyen
bir kod yolu iki gönderiyi yola çıkardı. Günlük satırı ise gerçekte olanın tersini yazdı —
sevk edilen GN-1 ve GN-5 için `depoda_degil` görünüyor, çünkü sebep sorulduğunda durum artık
değişmişti.

```sh
node kur.mjs ayrik
```

```
rapor       GN-1=yola_cikti GN-2=agirlik_siniri GN-3=depoda_degil GN-4=odenmedi GN-5=yola_cikti
raporun degistirdigi kayit = 0
sevk 1 cikan GN-1 GN-5
sevk 1 gunluk GN-1=yola_cikti GN-2=agirlik_siniri GN-3=depoda_degil GN-4=odenmedi GN-5=yola_cikti
sevk 2 cikan (bos)
```

Rapor satırı aynı, ama değiştirdiği kayıt sayısı sıfır. Günlük satırı da gerçeği yazıyor.
Sevk sonucu iki sürümde de aynı; ayrım davranışı değiştirmedi, yalnız gözlemi güvenli hâle
getirdi.

## Taramayla Bulunması

İhlal koşum beklemeden de bulunabilir. Aşağıdaki betik nesne yöntemlerinin gövdesine bakar:
gövdede kayda yazma varsa yöntem komuttur, değer döndüren `return` varsa sorgudur, ikisi de
varsa bulgudur.

```js
// komut-sorgu-tara.mjs — hem durum degistiren hem deger donduren yontemleri bulur
import { readdirSync, readFileSync } from "node:fs";
import { join } from "node:path";

const YONTEM = /^ {2}(\w+)\(/gm;

function govde(metin, acilis) {
  let i = acilis;
  let derinlik = 0;
  do {
    if (metin[i] === "{") derinlik += 1;
    else if (metin[i] === "}") derinlik -= 1;
    i += 1;
  } while (i < metin.length && derinlik > 0);
  return metin.slice(acilis, i);
}

const kok = process.argv[2];
let bulgu = 0;

for (const d of readdirSync(kok).filter((a) => a.endsWith(".mjs")).sort()) {
  const metin = readFileSync(join(kok, d), "utf8");
  for (const m of metin.matchAll(YONTEM)) {
    const g = govde(metin, metin.indexOf("{", m.index + m[0].length));
    const yazar = /\bkayit\.\w+\s*=[^=]/.test(g);
    const doner = /\breturn\s+[^;\s]/.test(g);
    const tur = yazar && doner ? "komut+sorgu" : yazar ? "komut" : doner ? "sorgu" : "bos";
    if (yazar && doner) bulgu += 1;
    console.log(`  ${d.padEnd(12)} ${m[1].padEnd(12)} yazar=${yazar} doner=${doner}  ${tur}`);
  }
}
console.log(`${kok.padEnd(8)} hem yazan hem donduren yontem = ${bulgu}`);
```

```sh
node komut-sorgu-tara.mjs karisik
node komut-sorgu-tara.mjs ayrik
```

```
  gonderi.mjs  yolaCikar    yazar=true doner=true  komut+sorgu
karisik  hem yazan hem donduren yontem = 1
  gonderi.mjs  sevkEngeli   yazar=false doner=true  sorgu
  gonderi.mjs  yolaCikar    yazar=true doner=false  komut
ayrik    hem yazan hem donduren yontem = 0
```

## Ayrımın Kabul Edilen İstisnaları

Ayrım mutlak uygulanamaz. Bir yığıttan öge çıkaran işlem hem durumu değiştirir hem çıkardığı
ögeyi döndürür; ayrıştırıldığında iki çağrı arasında yarış oluşabilir. Aynı biçimde bir
kayıt ekleyip üretilen tanıtıcıyı döndüren işlem de karışıktır ve bölünmesi tanıtıcıyı ikinci
bir sorguyla aramayı gerektirir.

İstisnayı kabul edilebilir yapan iki koşul vardır. Birincisi, döndürülen değerin işlemin
**sonucu** olmasıdır — çıkarılan öge, üretilen tanıtıcı — yeniden hesaplanan bir durum
değil. İkincisi, çağrının okuma amacıyla tekrarlanmamasıdır. Yukarıdaki karışık sürümde iki
koşul da ihlal edilmişti: dönen değer sevkin sonucu değil koşulların yeniden değerlendirmesiydi
ve günlük satırı için yeniden çağrıldı.

Adlandırma bu yüzden ölçünün bir parçasıdır. `sevkEngeli` bir soru sorar, `yolaCikar` bir emir
verir; ikisinin karışması genellikle adın da karışmasıyla başlar.

## Özet

- Komut–sorgu ayrımı bir işlemin ya durumu değiştirmesini ve değer döndürmemesini, ya da değer
  döndürüp durumu değiştirmemesini ister.
- Tarama, gövdesinde hem kayda yazma hem değer döndürme bulunan yöntemleri sayar: karışık
  tasarımda 1, ayrık tasarımda 0 bulgu çıktı.
- Yalnız rapor üreten kod yolu karışık tasarımda 2 kaydın durumunu değiştirdi, ayrık tasarımda
  0 kayıt değiştirdi.
- Günlük satırı karışık tasarımda gerçeğin tersini yazdı: sevk edilen iki gönderi için
  `depoda_degil` göründü, çünkü sebep sorulduğunda durum çoktan değişmişti.
- İstisna, dönen değerin işlemin sonucu olduğu ve çağrının okuma amacıyla tekrarlanmadığı
  durumlarda kabul edilir; adlandırma ayrımın görünür parçasıdır.

## Sonraki Adım

Ayrık tasarımda `AGIRLIK_SINIRI` sabiti gönderi modülünün içinde duruyor ve sevk kuralı da
orada. Bu iki karar aynı dosyada, ama aynı nedenle değişmiyorlar: sınır fiyat listesi
yenilendiğinde, kural kurum politikası değiştiğinde oynar. Bir modülde birden çok değişim
ekseni bulunduğunda, iki ayrı eksenden gelen iki değişiklik aynı dosyalara dokunur ve
birbirinin sınamasını kırar. Sonraki ders iki eksende gelen değişikliğin dokunduğu dosya
kümelerini çıkarır, kesişimlerini ölçer ve eksenleri yalıtmanın bu kesişimi nasıl boşalttığını
gösterir.
