---
title: 'Sorma, Söyle'
source: 'https://academia.sh/tr/kurslar/tasarim-ilkeleri/sorma-soyle'
course: 'Tasarım İlkeleri'
language: tr
updated: '2026-08-17T18:10:40+00:00'
license: 'CC BY-SA 4.0'
---

# Sorma, Söyle

Davranışın veriye taşınması: aynı sevk kuralını dört istemcinin ayrı ayrı kurduğu tasarım ile kararı nesneye söyleyen tasarımın karşılaştırılması, kuralın alanlarını bilen dosya sayısının sayılması, istemcilerin ayrık sonuç kümesinin ölçülmesi ve kural değiştiğinde düzenlenen dosya sayısının bulunması.

Demeter dersinde zincirler kısaldı, ama ara nesneler hâlâ değer döndürüyordu: `ilIlce()` bir
dize verir, kararı çağıran verir. Bu, sorunun bir başka biçimini gizler. Bir nesneden alan
alıp o alanlara bakarak karar veren her istemci, kuralın bir kopyasını kendi içinde taşır.
Kopya sayısı arttıkça kuralın tek bir tanımı kalmaz.

**Sorma, söyle** (tell, don't ask) bu tekrarı hedefler: veriye sahip olan nesneye ne yapması
gerektiği söylenir, verisi alınıp karar dışarıda verilmez. Ölçüsü iki sayıdır — kuralın
dayandığı alanları kaç dosya biliyor ve aynı kural kaç dosyada tekrarlanıyor.

## Kuralı Dört Kez Yazan Tasarım

Sevk kuralı üç koşuldan oluşuyor: gönderi depoda olmalı, ödemesi yapılmış olmalı ve ağırlığı
sınırı aşmamalı. Dört istemci bu kuralı ayrı ayrı kuruyor.

```sh
mkdir -p sorma soyle
```

```js
// sorma/kayit.mjs — sevk edilecek gonderiler
export const KAYITLAR = [
  { 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
// sorma/web.mjs — HTTP ucu kurali kendisi kuruyor
export function gonder(kayit) {
  if (kayit.durum === "depoda" && kayit.odendi && kayit.agirlik <= 30) {
    return { httpKodu: 200, kod: kayit.kod };
  }
  return { httpKodu: 409, kod: kayit.kod };
}
```

```js
// sorma/toplu.mjs — toplu isleme ayni kurali yeniden yaziyor
export const secilenler = (kayitlar) =>
  kayitlar
    .filter((k) => k.durum === "depoda" && k.odendi && k.agirlik <= 30)
    .map((k) => k.kod);
```

```js
// sorma/kurye.mjs — kurye uygulamasi agirlik kosulunu yazmamis
export const listele = (kayitlar) =>
  kayitlar.filter((k) => k.durum === "depoda" && k.odendi).map((k) => k.kod);
```

```js
// sorma/zamanli.mjs — zamanlanmis is kurali ucuncu kez yaziyor
export function tarama(kayitlar) {
  const cikacak = [];
  for (const k of kayitlar) {
    if (k.durum === "depoda" && k.odendi && k.agirlik <= 30) cikacak.push(k.kod);
  }
  return cikacak;
}
```

## Kararı Nesneye Söyleyen Tasarım

İkinci tasarımda kural gönderi nesnesinin içindedir. İstemciler alan okumaz; nesneye yola
çıkmasını söyler ve sonucu alır.

```js
// soyle/kayit.mjs — sevk edilecek gonderiler
export const KAYITLAR = [
  { 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
// soyle/gonderi.mjs — kural nesnenin icinde, tek yerde
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" };
    return { ok: true, sebep: "yola_cikti" };
  },
});
```

```js
// soyle/web.mjs — karari nesneye soyler
export function gonder(g) {
  return { httpKodu: g.yolaCikar().ok ? 200 : 409, kod: g.kod };
}
```

```js
// soyle/toplu.mjs — ayni emri verir
export const secilenler = (gonderiler) =>
  gonderiler.filter((g) => g.yolaCikar().ok).map((g) => g.kod);
```

```js
// soyle/kurye.mjs — ayni emri verir; kosul yazma firsati yok
export const listele = (gonderiler) =>
  gonderiler.filter((g) => g.yolaCikar().ok).map((g) => g.kod);
```

```js
// soyle/zamanli.mjs — ayni emri verir
export function tarama(gonderiler) {
  const cikacak = [];
  for (const g of gonderiler) if (g.yolaCikar().ok) cikacak.push(g.kod);
  return cikacak;
}
```

## Tekrarın Sayılması

İlk ölçü kuralın dayandığı alan adlarını kaç dosyanın bildiğidir. Bir dosya bu alanlardan
ikisini birden biliyorsa kuralın bir kopyasını taşıyor sayılır; veri dosyası bu sayımın
dışındadır.

```js
// kural-say.mjs — kuralin dayandigi alanlari kac dosyanin bildigini sayar
import { readdirSync, readFileSync } from "node:fs";
import { join } from "node:path";

const ALANLAR = ["durum", "odendi", "agirlik"];
const kok = process.argv[2];
let bilenDosya = 0;
let tekrar = 0;

for (const d of readdirSync(kok).filter((a) => a.endsWith(".mjs")).sort()) {
  const metin = readFileSync(join(kok, d), "utf8").replace(/^\/\/.*$/gm, "");
  const bilinen = ALANLAR.filter((a) => new RegExp(`\\b${a}\\b`).test(metin));
  if (bilinen.length > 0) bilenDosya += 1;
  if (bilinen.length >= 2 && d !== "kayit.mjs") tekrar += 1;
  console.log(`  ${d.padEnd(12)} bilinen alan=${bilinen.length}  ${bilinen.join(" ")}`);
}
console.log(`${kok.padEnd(6)} alani bilen dosya=${bilenDosya}, kurali tekrarlayan dosya=${tekrar}`);
```

```sh
node kural-say.mjs sorma
node kural-say.mjs soyle
```

```
  kayit.mjs    bilinen alan=3  durum odendi agirlik
  kurye.mjs    bilinen alan=2  durum odendi
  toplu.mjs    bilinen alan=3  durum odendi agirlik
  web.mjs      bilinen alan=3  durum odendi agirlik
  zamanli.mjs  bilinen alan=3  durum odendi agirlik
sorma  alani bilen dosya=5, kurali tekrarlayan dosya=4
  gonderi.mjs  bilinen alan=3  durum odendi agirlik
  kayit.mjs    bilinen alan=3  durum odendi agirlik
  kurye.mjs    bilinen alan=0
  toplu.mjs    bilinen alan=0
  web.mjs      bilinen alan=0
  zamanli.mjs  bilinen alan=0
soyle  alani bilen dosya=2, kurali tekrarlayan dosya=1
```

Birinci tasarımda dört dosya kuralı tekrarlıyor; ikincisinde bir. Tarama aynı zamanda
kuralın çatladığı yeri de gösterdi: kurye modülü üç alandan yalnız ikisini biliyor.

## Ayrık Sonuç Kümesi

Kopyalar arasındaki sapmanın koşumdaki karşılığı, aynı veriyle çalışan istemcilerin farklı
listeler üretmesidir. Sürücü betik dört istemciyi aynı beş gönderiyle çalıştırır ve kaç ayrı
sonuç çıktığını sayar.

```js
// kur.mjs — dort istemci ayni veriyle calisir, sonuclari karsilastirilir
const kok = process.argv[2];
const { KAYITLAR } = await import(`./${kok}/kayit.mjs`);
const { gonder } = await import(`./${kok}/web.mjs`);
const { secilenler } = await import(`./${kok}/toplu.mjs`);
const { listele } = await import(`./${kok}/kurye.mjs`);
const { tarama } = await import(`./${kok}/zamanli.mjs`);

const girdi = kok.startsWith("soyle")
  ? KAYITLAR.map((await import(`./${kok}/gonderi.mjs`)).gonderi)
  : KAYITLAR;

const sonuclar = {
  web: girdi.filter((g) => gonder(g).httpKodu === 200).map((g) => g.kod),
  toplu: secilenler(girdi),
  kurye: listele(girdi),
  zamanli: tarama(girdi),
};
for (const [ad, liste] of Object.entries(sonuclar)) console.log(`${ad.padEnd(9)} ${liste.join(" ")}`);
const ayrik = new Set(Object.values(sonuclar).map((l) => l.join(" ")));
console.log(`${kok}: farkli sonuc veren istemci kumesi = ${ayrik.size}`);
```

```sh
node kur.mjs sorma
node kur.mjs soyle
```

```
web       GN-1 GN-5
toplu     GN-1 GN-5
kurye     GN-1 GN-2 GN-5
zamanli   GN-1 GN-5
sorma: farkli sonuc veren istemci kumesi = 2
web       GN-1 GN-5
toplu     GN-1 GN-5
kurye     GN-1 GN-5
zamanli   GN-1 GN-5
soyle: farkli sonuc veren istemci kumesi = 1
```

Kırk beş kilogramlık GN-2 kurye listesinde görünüyor, diğer üç istemcide görünmüyor. İkinci
tasarımda böyle bir sapma için yer yok, çünkü koşulu yazacak dört ayrı gövde yok.

## Kural Değiştiğinde

Ağırlık sınırı 30'dan 20'ye iniyor. İki ağaç kopyalanır ve değişiklik ikisine de uygulanır.
Yerinde düzenlemede yedek uzantısı verilmiştir; GNU ve BSD `sed` bu biçimde aynı davranır.

```sh
cp -r sorma sorma-yeni
cp -r soyle soyle-yeni
sed -i.y 's/agirlik <= 30/agirlik <= 20/' sorma-yeni/*.mjs
sed -i.y 's/AGIRLIK_SINIRI = 30/AGIRLIK_SINIRI = 20/' soyle-yeni/gonderi.mjs
rm -f sorma-yeni/*.y soyle-yeni/*.y
node kur.mjs sorma-yeni
node kur.mjs soyle-yeni
for k in sorma soyle; do echo "$k: duzenlenen dosya = $(diff -rq $k $k-yeni | grep -c '^Files')"; done
```

```
web       GN-1
toplu     GN-1
kurye     GN-1 GN-2 GN-5
zamanli   GN-1
sorma-yeni: farkli sonuc veren istemci kumesi = 2
web       GN-1
toplu     GN-1
kurye     GN-1
zamanli   GN-1
soyle-yeni: farkli sonuc veren istemci kumesi = 1
sorma: duzenlenen dosya = 3
soyle: duzenlenen dosya = 1
```

Üç dosyaya karşı bir dosya. Sapmanın büyüdüğüne de dikkat: kurye listesi eskiden yalnız GN-2
fazlasını içeriyordu, sınır düşünce GN-5 de eklendi. Kural kopyalarının bir kez ayrışması
kalıcıdır; ayrışma her kural değişikliğinde büyür.

## İlkenin Sınırı

"Sorma, söyle" her alan okumasını yasaklamaz. Bir alanın dışarıya verilmesi, o alanla ilgili
bir **karar** dışarıda veriliyorsa sorun olur. Gönderi kodunu raporda yazdırmak için okumak
karar değildir; kodun biçimine bakıp gönderinin yurt içi mi olduğuna karar vermek karardır.

İlkenin aşırı uygulanışı da ölçülebilir bir maliyet üretir. Nesneye her soru için ayrı bir
emir yöntemi eklenirse arayüz büyür ve arayüz ayrımı dersinde ölçülen fazlalık geri gelir.
Ölçüt, yöntem sayısı değil, kuralı tekrarlayan dosya sayısıdır: bu sayı birden büyükse
davranış yanlış yerdedir.

## Özet

- Sorma–söyle ilkesi, veriye sahip nesneden alan alıp dışarıda karar vermek yerine karara
  konu olan işi nesneye söylemeyi ister.
- Kuralın alanlarını bilen dosya sayısı 5'ten 2'ye, kuralı tekrarlayan dosya sayısı 4'ten
  1'e indi.
- Tekrar sapmayı üretti: dört istemci aynı veriyle 2 ayrı sonuç kümesi verdi, tek tanımlı
  sürümde 1 küme çıktı.
- Ağırlık sınırı değiştiğinde tekrarlı tasarımda 3, tek tanımlı tasarımda 1 dosya düzenlendi;
  sapma kural değişikliğiyle büyüdü.
- İlke alan okumasını değil, alan üzerinde dışarıda karar vermeyi sınırlar; ölçüt kuralı
  tekrarlayan dosya sayısıdır.

## Sonraki Adım

`yolaCikar` bir emir gibi adlandırıldı ama aslında yalnız bir sonuç döndürüyor: gönderinin
durumu değişmiyor. Gerçek bir sevk işlemi durumu da değiştirmeli. Aynı yöntem hem durumu
değiştirip hem bir değer döndürdüğünde ne olur? Yöntemi iki kez çağırmak aynı sonucu verir
mi, bir günlük satırına yazmak için yapılan fazladan bir çağrı sistemin durumunu değiştirir
mi? Sonraki ders hem durum değiştiren hem değer döndüren bir işlemin ürettiği sürprizi bir
koşumla gösterir ve iki sorumluluğu ayırmanın ölçüsünü kurar.
