Ders 10 / 14
Değişmezlik
Paylaşılan durumun ortadan kaldırılması: sığ kopyanın bıraktığı sessiz değişikliklerin sayılması, derin kopya ile yapısal paylaşımın ürettiği nesne sayısıyla karşılaştırılması ve kuralın dondurmayla zorlanması.
İçindekiler
Önceki ders saf fonksiyonun iki koşulunu kurdu ve ucretHesapla bu koşulları sağladı:
tarifeyi parametre olarak aldı, hiçbir yere yazmadı. Ancak fonksiyonun kendi parametresini
değiştirmemesini sağlayan hiçbir şey yoktu. Tarife nesnesi tek bir nesnedir ve onu kullanan
her yer aynı nesneyi görür; bir tek satır onu değiştirdiğinde, birbirinden habersiz iki
çağrının sonucu birbirine bağlanır.
Bu ders o bağı ele alıyor. Ölçülecek üç şey var: paylaşılan veri değiştirildiğinde kaç ücret sessizce kayar, kopyalamanın bedeli kaç nesnedir ve bu bedelin ne kadarı ödenmeyebilir.
İki Ayrı Terim
Karışması olağan iki terim vardır ve bu platformda ayrı tutulur. Değiştirilemez (immutable) bir değer, üretildikten sonra içeriği değiştirilemeyen değerdir; tarifedeki bir kademe ücreti buna örnektir. Değişmez (invariant) ise her zaman doğru kalması gereken bir koşuldur; “hesaplanan ücret asgari ücretin altına düşemez” bir değişmezdir.
İlişkileri tek yönlüdür: değiştirilemez veri, değişmezleri korumayı ucuzlatır. Bir değer kurulduğu anda doğrulanıp bir daha değiştirilemiyorsa, koşulun sonradan bozulup bozulmadığı sorusu ortadan kalkar. Kapsülleme aynı sonucu başka bir yoldan üretir: durumu gizler ve yalnızca değişmezi koruyan davranışlara açar. İkisi de aynı soruna, sonradan bozulan koşullara karşı çalışır.
Paylaşılan Tarife
Ücretlendirme kitaplığı tarifeyi yapılandırmadan yükler. Yapı iç içedir: kademeler, bölge katsayıları, taşıyıcı seçenekleri ve indirim tanımları.
// tarife-verisi.mjs — tarife yapilandirmadan yuklenir; her yukleme taze bir yapi verir export const tarifeYukle = () => ({ ad: "yurtici-standart", 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 }, tasiyicilar: [ { ad: "hizli", ekUcret: 2500, rota: ["IST", "ANK"] }, { ad: "ekonomik", ekUcret: 0, rota: ["IST", "ESK", "ANK"] }, { ad: "teslim-noktasi", ekUcret: -750, rota: ["IST", "ANK", "nokta"] }, ], indirimler: [{ ad: "sozlesmeli", oran: 0.88 }, { ad: "hacim", oran: 0.95 }], }); export const GONDERILER = [ { kod: "TR-4471", agirlikGram: 800, bolge: 1 }, { kod: "TR-4472", agirlikGram: 3200, bolge: 2 }, { kod: "TR-4473", agirlikGram: 12400, bolge: 3 }, { kod: "TR-4474", agirlikGram: 950, bolge: 2 }, { kod: "TR-4475", agirlikGram: 4800, bolge: 1 }, { kod: "TR-4476", agirlikGram: 14900, bolge: 2 }, ]; export function ucretHesapla(gonderi, tarife) { const kademe = tarife.kademeler.find((k) => gonderi.agirlikGram <= k.ustAgirlikGram) ?? tarife.kademeler.at(-1); return Math.max(tarife.asgariUcret, Math.round(kademe.ucret * tarife.bolgeKatsayisi[gonderi.bolge])); }
Sözleşmeli bir müşteri için kademe ücretlerinin yüzde on iki düşürülmesi isteniyor. Bu, standart tarifeden yeni bir tarife türetmektir ve üç ayrı yolla yazılabilir.
// turetme.mjs — sozlesmeli tarifeyi ayni kaynaktan turetmenin uc yolu let uretilen = 0; const uret = (nesne) => { uretilen++; return nesne; }; export const sayacSifirla = () => { uretilen = 0; }; export const sayacOku = () => uretilen; // 1. Yerinde degistirme: ust duzey kopyalanir, ic ice yapi paylasilmaya devam eder. export function sigKopyaylaTuret(tarife, oran) { const yeni = uret({ ...tarife }); for (const k of yeni.kademeler) k.ucret = Math.round(k.ucret * oran); return yeni; } // 2. Derin kopya: her dugum yeniden uretilir, hicbir sey paylasilmaz. export function derinKopyaylaTuret(tarife, oran) { return uret({ ...tarife, kademeler: uret(tarife.kademeler.map((k) => uret({ ...k, ucret: Math.round(k.ucret * oran) }))), bolgeKatsayisi: uret({ ...tarife.bolgeKatsayisi }), tasiyicilar: uret(tarife.tasiyicilar.map((t) => uret({ ...t, rota: uret([...t.rota]) }))), indirimler: uret(tarife.indirimler.map((i) => uret({ ...i }))), }); } // 3. Yapisal paylasim: yalnizca degisikligin yolu yeniden uretilir, gerisi paylasilir. export function paylasarakTuret(tarife, oran) { return uret({ ...tarife, kademeler: uret(tarife.kademeler.map((k) => uret({ ...k, ucret: Math.round(k.ucret * oran) }))), }); }
Birinci yol yaygın bir yanılgının ürünüdür: yayma sözdizimiyle bir kopya alındığı için
kaynağın korunduğu sanılır. Alınan kopya sığ kopyadır; kök nesne yenidir, ama
kademeler dizisi ve içindeki nesneler aynı nesnelerdir. Aynı nesneye iki addan
erişilmesine takma ad (aliasing) denir ve sorunun kaynağı budur.
Sessiz Değişikliğin Sayılması
Ölçüm iki koşum yolunu yan yana koyar: aynı tarifeyle altı gönderi ücretlendirilir, sonra sözleşmeli tarife türetilir, sonra aynı altı gönderi yeniden ücretlendirilir. Standart tarifenin ücretlerinden kaçının değiştiği sayılır.
// olcum.mjs — uc turetme yolunun sessiz degisiklik ve uretilen nesne sayisi import { tarifeYukle, GONDERILER, ucretHesapla } from "./tarife-verisi.mjs"; import { sigKopyaylaTuret, derinKopyaylaTuret, paylasarakTuret, sayacSifirla, sayacOku } from "./turetme.mjs"; const SOZLESME_ORANI = 0.88; const fiyatlar = (t) => GONDERILER.map((g) => ucretHesapla(g, t)); function olc(ad, turet) { const tarife = tarifeYukle(); const once = fiyatlar(tarife); sayacSifirla(); const sozlesmeli = turet(tarife, SOZLESME_ORANI); const uretilenNesne = sayacOku(); const sonra = fiyatlar(tarife); const sessiz = once.filter((u, i) => u !== sonra[i]).length; const paylasilan = ["bolgeKatsayisi", "tasiyicilar", "indirimler"] .filter((alan) => sozlesmeli[alan] === tarife[alan]).length; console.log(`${ad.padEnd(18)} sessiz degisiklik: ${sessiz}/6 ` + `uretilen nesne: ${String(uretilenNesne).padStart(2)} paylasilan alt agac: ${paylasilan}/3`); return sonra; } const s = olc("sig kopya", sigKopyaylaTuret); olc("derin kopya", derinKopyaylaTuret); olc("yapisal paylasim", paylasarakTuret); console.log(`\nstandart tarife once : ${fiyatlar(tarifeYukle()).join(", ")}`); console.log(`sig kopyadan sonra : ${s.join(", ")}`);
sig kopya sessiz degisiklik: 5/6 uretilen nesne: 1 paylasilan alt agac: 3/3 derin kopya sessiz degisiklik: 0/6 uretilen nesne: 16 paylasilan alt agac: 0/3 yapisal paylasim sessiz degisiklik: 0/6 uretilen nesne: 5 paylasilan alt agac: 3/3 standart tarife once : 4990, 7463, 14715, 5739, 6490, 12535 sig kopyadan sonra : 4990, 6568, 12949, 5050, 5711, 11031
Sığ kopya yolunda altı standart ücretin beşi düşmüştür. Kimse bu ücretleri değiştirmek istememiştir; tek istenen, ayrı bir sözleşme tarifesi türetmekti. Değişmeyen tek ücret ilk gönderininkidir ve onu koruyan şey de tasarım değil, asgari ücret tabanının hesabı 4990’da tutmasıdır.
Bu tür bir kusurun iki özelliği vardır. Sessizdir: hiçbir hata fırlamaz, hiçbir kayıt düşülmez. Uzaktır: kusurun belirtisi standart tarifeyle ücretlendirme yapan yerdedir, nedeni ise sözleşme tarifesini türeten yerdedir. Aradaki mesafe, iki yerin aynı nesneye takma adla erişmesinden ibarettir.
Kopyalamanın Bedeli
Derin kopya sessiz değişikliği sıfıra indirir, ama on altı nesne üretir. Tarifedeki değişiklik yalnızca kademelerdedir; bölge katsayıları, üç taşıyıcı, üç rota dizisi ve iki indirim tanımı olduğu gibi kopyalanmıştır. Kopyalanan bu nesneler asla değiştirilmeyecektir.
Üçüncü yol bu gözlemi kullanır. Yapısal paylaşım (structural sharing), yalnızca
değişikliğin yolu üzerindeki düğümleri yeniden üretir; değişmeyen alt ağaçları başvuruyla
paylaşır. Sonuç aynı korumayı beş nesneyle sağlar: kök, kademe dizisi ve üç kademe.
Paylaşılan alt ağaç sayısı üçtür — türetilen tarifedeki tasiyicilar dizisi ile
standarttaki tasiyicilar dizisi aynı nesnedir.
Paylaşımın güvenli olmasının koşulu, paylaşılan parçaların da değiştirilmemesidir. Kural tutarlı biçimde uygulandığında paylaşım bedava korumadır; tek bir yerde bozulduğunda, paylaşılan alt ağaç sığ kopyadakiyle aynı sessiz kusuru üretir. Ödünleşim burada nettir: derin kopya kuralın ihlaline dayanıklıdır ve on altı nesne öder, yapısal paylaşım beş nesne öder ve kuralın her yerde uygulanmasına güvenir.
Kuralın Dille Zorlanması
Güvenmek yerine zorlamak da bir seçenektir. Dondurma (freeze) JavaScript’te Nesneler ve Fonksiyonlar kursunda tanıtılmıştı; bir nesnenin özellikleri yazılamaz hâle gelir ve katı kipte yazma denemesi hata fırlatır. Modüller katı kipte çalıştığı için ek bir ayar gerekmez.
// dondurma.mjs — degistirilemezligin dille zorlanmasi: sessiz degisiklik hataya donusur import { tarifeYukle, GONDERILER, ucretHesapla } from "./tarife-verisi.mjs"; import { sigKopyaylaTuret } from "./turetme.mjs"; function derinDondur(nesne) { for (const deger of Object.values(nesne)) { if (deger !== null && typeof deger === "object") derinDondur(deger); } return Object.freeze(nesne); } const tarife = derinDondur(tarifeYukle()); const once = GONDERILER.map((g) => ucretHesapla(g, tarife)); let hata = 0; let tur = "-"; try { sigKopyaylaTuret(tarife, 0.88); } catch (e) { hata = 1; tur = e.constructor.name; } const sonra = GONDERILER.map((g) => ucretHesapla(g, tarife)); console.log(`firlatilan hata sayisi : ${hata} (${tur})`); console.log(`sessiz degisiklik : ${once.filter((u, i) => u !== sonra[i]).length}/6`);
firlatilan hata sayisi : 1 (TypeError) sessiz degisiklik : 0/6
Kod değişmemiştir; aynı sigKopyaylaTuret çağrılmıştır. Değişen tek şey, kusurun
belirdiği yerdir: beş sessiz ücret kayması yerine, kusuru üreten satırda bir hata. Hata
türü diller ve çalışma zamanları arasında farklılık gösterir, ama davranışın niteliği
aynıdır — sessiz olan görünür olmuştur.
Dondurmanın da bir bedeli vardır. Derin dondurma her düğümü dolaşır ve her yükleme sırasında çalışır; büyük yapılandırmalarda bu maliyet ölçülmeden benimsenmemelidir. Yaygın uzlaşma, dondurmayı yalnızca geliştirme koşumlarında açık tutmak ve üretimde kuralın kod incelemesiyle sürdürülmesidir.
Nerede Ödenmez
Değiştirilemezlik kuralı her satıra uygulanmak zorunda değildir. Ölçüt yine
gözlenebilirliktir: bir fonksiyonun kendi gövdesinde ürettiği ve dışarı sızdırmadığı bir
nesne serbestçe değiştirilebilir. Önceki dersteki toplamUcret bunun örneğiydi; birikeç
her adımda değişir, ama fonksiyondan yalnızca son değer çıkar.
Aynı gerekçe toplu kurma için de geçerlidir. Bin gönderilik bir listeyi kurarken her ekleme için yeni dizi üretmek gereksizdir; dizi yerel olarak doldurulur, dolduktan sonra dondurulup dışarı verilir. Kural, verinin paylaşıldığı andan itibaren değiştirilemez olmasıdır, üretildiği andan itibaren değil.
Özet
- Değiştirilemez (immutable) bir değerin içeriği üretildikten sonra değişmez; değişmez (invariant) her zaman doğru kalması gereken bir koşuldur. İlki ikincisini korumayı ucuzlatır.
- Yayma sözdizimiyle alınan kopya sığdır: iç içe yapı takma adla paylaşılmaya devam eder. Sözleşme tarifesini bu yolla türetmek altı standart ücretin beşini sessizce düşürmüştür.
- Sessiz kusurun iki özelliği vardır: hiçbir hata üretmez ve belirtisi nedeninden uzaktadır.
- Derin kopya korumayı on altı nesne üreterek sağlar; yapısal paylaşım aynı korumayı beş nesneyle sağlar ve üç alt ağacı başvuruyla paylaşır, ama kuralın her yerde uygulanmasına güvenir.
- Dondurma güveni zorlamaya çevirir: aynı türetme koşumu sessiz beş kayma yerine bir
TypeErrorüretir, dolayısıyla kusur kendi satırında görünür. - Kural paylaşılan veriye uygulanır; bir fonksiyonun gövdesinde üretilip dışarı sızmayan yapı serbestçe doldurulabilir.
Sonraki Adım
İki ders de veriyi ele aldı: ücret hesabı girdiden çıktıya bir eşlemeye indirgendi, tarife
değiştirilemez hâle getirildi. Davranış ise hâlâ sabittir — ucretHesapla tek bir kural
kümesini gövdesinde taşır. Sözleşmeli indirim, hacim indirimi, gece teslimi eki ve
kurumsal anlaşmalar eklendiğinde bu gövde büyür. Sonraki ders davranışın kendisini
parametreye çevirir: aynı ücretlendirme mantığı bayrak parametresiyle, arama tablosuyla ve
fonksiyon parametresiyle yazılır, sonra yeni bir indirim kuralı eklenip her üç sürümde
dokunulan satır sayısı sayılır.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.