Ders 07 / 16
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.
İçindekiler
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.
mkdir -p bos sozlesme
// 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.
// 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.
// 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”.
// 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ş toplamadı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.
// 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 }])], ];
// 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
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
tasiyiciGecikmesiiç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.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.