---
title: 'Boş Değer Geçirmemek'
source: 'https://academia.sh/tr/kurslar/temiz-kod/bos-deger-gecirmemek'
course: 'Temiz Kod'
language: tr
updated: '2026-08-17T18:10:37+00:00'
license: 'CC BY-SA 4.0'
---

# Boş Değer Geçirmemek

Boşluğun sözleşmede görünmesinin ölçülmesi: boş değer döndüren bir çağrı zincirinde hatanın kaç çağrı sonra ortaya çıktığı, boşluğu sonuç türünde gösteren sürümde aynı mesafenin sıfıra inmesi ve boş değeri argüman olarak geçiren imzaların boş toplamayla değiştirilmesi.

Buraya kadarki kararlar kodun iç yapısı üzerineydi: adlar, soyutlama düzeyleri, biçim,
dallanma ve seçenek yapısı. Bir fonksiyonun dışarıya **söylediği** şey henüz ele alınmadı.
Önceki dersteki `tarife` fonksiyonu bilinmeyen bir tür için hata atıyordu; aynı sorunun
yaygın çözümü boş bir değer döndürmektir.

Boş değer döndüren bir imza çağırana hiçbir şey söylemez. `tasiyiciBul(bolgeKodu)`
imzasına bakan kişi sonucun her zaman bir taşıyıcı olduğunu varsayar; boş dönebileceğini
ancak gerçekleştirimi okuyarak öğrenir. Varsayım yanlış çıktığında hata, boşluğun
üretildiği yerde değil kullanıldığı yerde ortaya çıkar. Aradaki mesafe ölçülebilir.

## Boş Değerin Yolculuğu

Yönlendirme modülü bir gönderi için taşıyıcı seçer, aktarma sayısını belirler ve teslim
tarihini hesaplar. Her fonksiyon çağrıldığında adını bir ize yazıyor; bu iz ölçüm için
kullanılacak.

```sh
mkdir -p bos sozlesme
```

```js
// bos/rota.mjs — tasiyici bulunamazsa bos deger dondurulur
export const IZ = [];
const iz = (ad) => IZ.push(ad);

const TASIYICILAR = { 1: { ad: "sehirici", teslimGunu: 1 }, 2: { ad: "bolgesel", teslimGunu: 3 },
  3: { ad: "uzak", teslimGunu: 5 } };

export function tasiyiciBul(bolgeKodu) {
  iz("tasiyiciBul");
  return TASIYICILAR[bolgeKodu] ?? null;
}

export function rotaSec(gonderi) {
  iz("rotaSec");
  return { tasiyici: tasiyiciBul(gonderi.bolgeKodu), aktarmaSayisi: gonderi.bolgeKodu > 2 ? 1 : 0 };
}

export function aktarmaGecikmesi(rota) {
  iz("aktarmaGecikmesi");
  return rota.aktarmaSayisi * 2;
}

export function tasiyiciGecikmesi(rota) {
  iz("tasiyiciGecikmesi");
  return rota.tasiyici.teslimGunu;
}

export function teslimTarihi(rota, bugun) {
  iz("teslimTarihi");
  return bugun + aktarmaGecikmesi(rota) + tasiyiciGecikmesi(rota);
}

export function teslimatPlani(gonderi, bugun) {
  iz("teslimatPlani");
  const rota = rotaSec(gonderi);
  return { rota, tarih: teslimTarihi(rota, bugun) };
}

export function siparisiIsle(gonderi, bugun) {
  iz("siparisiIsle");
  return `${gonderi.kod}: ${teslimatPlani(gonderi, bugun).tarih}. gun`;
}
```

Tanımlı olmayan bir bölge kodu geldiğinde `tasiyiciBul` boş döner, `rotaSec` bu boşluğu
bir alan olarak paketler ve döndürür, `teslimatPlani` rotayı alır, `teslimTarihi` iki
gecikme fonksiyonunu çağırır ve hata bunlardan ikincisinde ortaya çıkar. Boşluk, üretildiği
yerden çıkıp bir veri yapısının içine yerleşerek yolculuk eder.

## Sözleşmede Görünen Boşluk

İkinci sürümde tek bir şey değişiyor: taşıyıcı arayan fonksiyonun **sonuç türü**. Artık
ya bir taşıyıcı ya da bulunamama bilgisi dönüyor ve ikisi birbirinden ayırt edilebiliyor.
Çağıran taraf sonucu kullanmadan önce hangisi olduğuna bakmak zorunda.

```js
// sozlesme/rota.mjs — boslugun sonuc turunde gorunmesi
export const IZ = [];
const iz = (ad) => IZ.push(ad);

const TASIYICILAR = { 1: { ad: "sehirici", teslimGunu: 1 }, 2: { ad: "bolgesel", teslimGunu: 3 },
  3: { ad: "uzak", teslimGunu: 5 } };

// Sonuc iki bicimden biridir: { bulundu: true, tasiyici } ya da { bulundu: false, bolgeKodu }.
export function tasiyiciSec(bolgeKodu) {
  iz("tasiyiciSec");
  const tasiyici = TASIYICILAR[bolgeKodu];
  return tasiyici === undefined ? { bulundu: false, bolgeKodu } : { bulundu: true, tasiyici };
}

export function rotaSec(gonderi) {
  iz("rotaSec");
  const sonuc = tasiyiciSec(gonderi.bolgeKodu);
  if (sonuc.bulundu === false) {
    throw new RangeError(`bolge ${sonuc.bolgeKodu} icin tasiyici tanimli degil`);
  }
  return { tasiyici: sonuc.tasiyici, aktarmaSayisi: gonderi.bolgeKodu > 2 ? 1 : 0 };
}

export function aktarmaGecikmesi(rota) {
  iz("aktarmaGecikmesi");
  return rota.aktarmaSayisi * 2;
}

export function tasiyiciGecikmesi(rota) {
  iz("tasiyiciGecikmesi");
  return rota.tasiyici.teslimGunu;
}

export function teslimTarihi(rota, bugun) {
  iz("teslimTarihi");
  return bugun + aktarmaGecikmesi(rota) + tasiyiciGecikmesi(rota);
}

export function teslimatPlani(gonderi, bugun) {
  iz("teslimatPlani");
  const rota = rotaSec(gonderi);
  return { rota, tarih: teslimTarihi(rota, bugun) };
}

export function siparisiIsle(gonderi, bugun) {
  iz("siparisiIsle");
  return `${gonderi.kod}: ${teslimatPlani(gonderi, bugun).tarih}. gun`;
}
```

Aşağı akış — gecikme hesapları, teslim tarihi, plan ve özet — iki sürümde birebir aynı.
Değişen tek şey boşluğun nerede karşılandığıdır.

## Ölçüm

Ölçer aynı geçersiz gönderiyi iki sürüme de verir ve üç şeyi kaydeder: çağrı izi, boşluğun
üretildiği çağrı ile hatanın görüldüğü çağrı arasındaki mesafe, hata anındaki yığıt izinde
(stack trace) modülün hangi fonksiyonlarının bulunduğu.

```js
// olc.mjs — bos degerin uretildigi cagri ile hatanin goruldugu cagri arasindaki mesafe
import { IZ as bosIz, siparisiIsle as bosIsle } from "./bos/rota.mjs";
import { IZ as sozIz, siparisiIsle as sozIsle } from "./sozlesme/rota.mjs";

const gonderi = { kod: "GN-4172", bolgeKodu: 9 };

function olc(baslik, isle, iz, ureten) {
  console.log(baslik);
  try {
    console.log(`  sonuc: ${isle(gonderi, 12)}`);
  } catch (hata) {
    const cerceve = hata.stack.split("\n").slice(1)
      .map((s) => s.match(/at (\w+)/)?.[1]).filter((a) => iz.includes(a));
    console.log(`  cagri izi: ${iz.join(" -> ")}`);
    console.log(`  boslugu ureten cagri #${iz.indexOf(ureten)}, hatanin goruldugu cagri #${iz.length - 1},` +
      ` aradaki cagri = ${iz.length - 1 - iz.indexOf(ureten)}`);
    console.log(`  yigit izindeki modul cerceveleri: ${cerceve.join(", ")}`);
    console.log(`  hata: ${hata.constructor.name}: ${hata.message}`);
  }
}

olc("bos deger donduren surum", bosIsle, bosIz, "tasiyiciBul");
olc("sozlesmede gorunen surum", sozIsle, sozIz, "tasiyiciSec");
```

```
bos deger donduren surum
  cagri izi: siparisiIsle -> teslimatPlani -> rotaSec -> tasiyiciBul -> teslimTarihi -> aktarmaGecikmesi -> tasiyiciGecikmesi
  boslugu ureten cagri #3, hatanin goruldugu cagri #6, aradaki cagri = 3
  yigit izindeki modul cerceveleri: tasiyiciGecikmesi, teslimTarihi, teslimatPlani, siparisiIsle
  hata: TypeError: Cannot read properties of null (reading 'teslimGunu')
sozlesmede gorunen surum
  cagri izi: siparisiIsle -> teslimatPlani -> rotaSec -> tasiyiciSec
  boslugu ureten cagri #3, hatanin goruldugu cagri #3, aradaki cagri = 0
  yigit izindeki modul cerceveleri: rotaSec, teslimatPlani, siparisiIsle
  hata: RangeError: bolge 9 icin tasiyici tanimli degil
```

Mesafe üçten sıfıra indi. Sayının anlamı, hatayı arayan kişinin kat etmesi gereken yoldur:
boş değer döndüren sürümde hata `tasiyiciGecikmesi` içinde görünüyor ve o fonksiyonda
hiçbir kusur yok. Yığıt izi de yardım etmiyor, çünkü boşluğu üreten `tasiyiciBul` çoktan
dönmüş; izde adı bile geçmiyor. Arayan kişi, listelenen dört çerçevenin hiçbirinde
bulunmayan bir nedeni bulmak zorunda.

Hata iletileri de ölçümün bir parçası. Birincisi bir tür hatasıdır ve alan hakkında hiçbir
şey söylemez; ikincisi bölge kodunu ve eksik olan şeyi adıyla söyler.

## Boş Değeri Geçirmek

Aynı sorunun ters yönü, boş değerin **argüman olarak** geçirilmesidir. İndirim hesabı buna
örnek: indirim yoksa boş değer geçmek, imzada iki anlam üretir — "indirim nesnesi" ve
"indirim yok".

```js
// bos/indirim.mjs — indirim ya da kampanya yoksa bos deger geciriliyor
export function ucretHesapla(temelUcret, indirim, kampanya) {
  const oran = indirim === null ? 0 : indirim.oran;
  const ek = kampanya === null ? 0 : kampanya.ekIndirim;
  return temelUcret * (1 - oran - ek);
}

export const ORNEKLER = [
  ["indirimsiz", ucretHesapla(100, null, null)],
  ["sozlesmeli", ucretHesapla(100, { oran: 0.15 }, null)],
  ["iki indirim", ucretHesapla(100, { oran: 0.15 }, { ekIndirim: 0.05 })],
];
```

Boşluğun doğal karşılığı bir **boş toplama**dır. İndirim sayısı sıfır, bir ya da daha çok
olabiliyorsa parametre tekil bir nesne değil bir listedir; indirim yokluğu boş liste olarak
temsil edilir ve aynı işlemleri destekler.

```js
// sozlesme/indirim.mjs — boslugu bos dizi tasir, imza bos deger kabul etmez
export function ucretHesapla(temelUcret, indirimler) {
  const toplam = indirimler.reduce((t, i) => t + i.oran, 0);
  return temelUcret * (1 - toplam);
}

export const ORNEKLER = [
  ["indirimsiz", ucretHesapla(100, [])],
  ["sozlesmeli", ucretHesapla(100, [{ oran: 0.15 }])],
  ["iki indirim", ucretHesapla(100, [{ oran: 0.15 }, { oran: 0.05 }])],
];
```

```js
// indirim-olc.mjs — iki imzanin ayni sonuclari verdigi ve bos deger sayisi
import { ORNEKLER as bosOrnek } from "./bos/indirim.mjs";
import { ORNEKLER as sozOrnek } from "./sozlesme/indirim.mjs";

for (const [i, [ad, deger]] of bosOrnek.entries()) {
  const oteki = sozOrnek[i][1];
  console.log(`${ad.padEnd(12)} bos=${deger.toFixed(2)}  sozlesme=${oteki.toFixed(2)}` +
    `  ${deger.toFixed(2) === oteki.toFixed(2) ? "ayni" : "FARKLI"}`);
}
```

```
indirimsiz   bos=100.00  sozlesme=100.00  ayni
sozlesmeli   bos=85.00  sozlesme=85.00  ayni
iki indirim  bos=80.00  sozlesme=80.00  ayni
```

```sh
for d in bos sozlesme; do echo "$d/indirim.mjs: $(grep -o null $d/indirim.mjs | wc -l | tr -d ' ') bos deger"; done
```

```
bos/indirim.mjs: 5 bos deger
sozlesme/indirim.mjs: 0 bos deger
```

Aynı üç sonuç, beş boş değer yerine sıfır boş değerle üretiliyor. Kazanç yalnız sayıda
değil: boş toplamalı imza yeni bir indirim türü eklendiğinde değişmez, boş değerli imza
ise her yeni indirim için bir parametre ve bir denetim daha ister.

## Boşluğu Görünür Kılmanın Yolları

Ölçüm hangi tekniğin seçileceğini söylemez; üç seçenek de mesafeyi sıfıra indirir ve
farkları başka yerdedir.

**Erken hata.** Boşluk bir programlama hatasının belirtisiyse, üretildiği yerde atılır.
`rotaSec` bunu yapıyor: tanımlı olmayan bölge kodu bir veri kusurudur, hesaba devam etmek
anlamsızdır. Bu seçenek çağıran tarafa karar bırakmaz.

**Boş toplama.** Boşluk "hiç yok" anlamındaysa ve işlem sıfır ögeyle de anlamlıysa boş
liste, boş eşleme ya da sıfır kullanılır. İndirim örneği budur; çağıran taraf hiçbir
denetim yazmaz, çünkü boşluk zaten desteklenen bir durumdur.

**Açık sonuç türü.** Boşluk beklenen bir sonuçsa ve ne yapılacağına çağıran taraf karar
vermeliyse, sonuç iki biçimden birini alır ve hangisi olduğu okunabilir. `tasiyiciSec`
bunu yapıyor: bir çağıran hata atmayı, başka bir çağıran varsayılan taşıyıcıya düşmeyi
seçebilir.

Üçünün ortak yanı, boşluğun **imzanın parçası** olmasıdır. Boş değer döndürmek ise
boşluğu imzadan gizler; gerçekleştirimi okumayan hiç kimse varlığını bilmez.

## Özet

- Boş değer döndüren bir imza boşluğu gizler; çağıran taraf varsayımını gerçekleştirimi
  okumadan doğrulayamaz.
- Ölçümde boşluk üretildikten üç çağrı sonra hata verdi; hata `tasiyiciGecikmesi` içinde
  göründü ve boşluğu üreten fonksiyon yığıt izinde yer almıyordu.
- Boşluğu sonuç türünde gösteren sürümde mesafe sıfıra indi ve hata iletisi tür hatası
  yerine bölge kodunu adıyla anan bir alan hatasına dönüştü.
- Boş değer argüman olarak geçirildiğinde her parametre bir denetim ister; boş toplamalı
  imza aynı üç sonucu beş boş değer yerine sıfır boş değerle üretti.
- Boşluğu görünür kılmanın üç yolu erken hata, boş toplama ve açık sonuç türüdür; üçü de
  boşluğu imzanın parçası yapar.

## Sonraki Adım

`ucretHesapla` imzası boş değerlerden kurtuldu, ama benzer bir sorun başka bir kılıkta
geri geliyor. Ücretlendirme kitaplığında iade gönderileri de hesaplanıyor ve iki hesap
büyük ölçüde ortak; aradaki farkı taşımak için imzaya bir doğruluk değeri eklemek akla
yakın görünür. Böyle bir parametre çağrı yerinde tek bir sözcük bile söylemez ve fonksiyonun
içinde bir dallanma açar. Sonraki ders bunun bedelini sınamayla ölçer: bayraklı tek
fonksiyonun ve bölünmüş iki fonksiyonun sınanması için kaç durum gerekiyor.
