Ders 13 / 19
Hollywood İlkesi
Çağıran ile çağrılan arasındaki yönün tersine çevrilmesi: adımların sırasını üç ayrı çağıranın yazdığı düzen ile adımların kendini bir iskelete kaydettiği düzenin karşılaştırılması, sırayı bilen dosya sayısının sayılması ve yeni bir adım eklendiğinde düzenlenen dosya ile dokunulan satır sayısının ölçülmesi.
İçindekiler
Bir önceki dersin soyut yerleşiminde bileşim kökü hangi fiyat listesinin kullanılacağına karar veriyordu, ama istemcileri hâlâ kendisi çağırıyordu. Adımların sırası ve sayısı çağıranın gövdesinde duruyor. Bu akışa yeni bir adım eklemek, çağıranın gövdesini her seferinde düzenlemeyi gerektirir.
Hollywood ilkesi yönü tersine çevirir: “bizi arama, biz seni ararız”. Parçalar üst düzey akışı çağırmaz; üst düzey akış parçalara çağrılma noktası verir. Eşzamansız JavaScript ve Çalışma Zamanı kursunda tanıtılan denetimin devri (inversion of control) bu ilkenin genel adıdır; burada ölçülen biçimi, adım sırasının hangi modülde yazılı olduğudur.
Sırayı Çağıranın Yazdığı Düzen
Ücret hesabı üç adımdan oluşuyor: taban ücret, bölge katsayısı, sözleşmeli müşteri indirimi. Üç ayrı çağıran bu sırayı kendi gövdesinde yazıyor.
mkdir -p cagiran iskelet
// kayit.mjs — iki duzenin de kullandigi ornek gonderiler export const KAYITLAR = [ { kod: "GN-1", agirlik: 0.8, adres: "34100", sozlesmeli: false }, { kod: "GN-2", agirlik: 3.0, adres: "06500", sozlesmeli: true }, { kod: "GN-3", agirlik: 12.0, adres: "65200", sozlesmeli: true }, ];
// cagiran/adimlar.mjs — adimlar; sirayi bilmezler, cagirani da bilmezler const KADEME = [[1, 3900], [5, 6400], [20, 11800]]; const BOLGE = { "34": 100, "06": 115, "65": 140 }; export const taban = (kurus, g) => KADEME.find(([ust]) => g.agirlik <= ust)?.[1] ?? 15000; export const bolge = (kurus, g) => Math.round((kurus * (BOLGE[g.adres.slice(0, 2)] ?? 160)) / 100); export const indirim = (kurus, g) => Math.round((kurus * (g.sozlesmeli ? 90 : 100)) / 100);
// cagiran/fatura.mjs — sirayi cagiran yaziyor import { taban, bolge, indirim } from "./adimlar.mjs"; export function fatura(gonderi) { let kurus = taban(0, gonderi); kurus = bolge(kurus, gonderi); kurus = indirim(kurus, gonderi); return `${gonderi.kod} ${kurus} kurus`; }
// cagiran/onizleme.mjs — ayni sirayi ikinci kez yaziyor import { taban, bolge, indirim } from "./adimlar.mjs"; export function onizleme(gonderi) { let kurus = taban(0, gonderi); kurus = bolge(kurus, gonderi); kurus = indirim(kurus, gonderi); return `~${kurus}`; }
// cagiran/toplu.mjs — ayni sirayi ucuncu kez yaziyor import { taban, bolge, indirim } from "./adimlar.mjs"; export function toplam(gonderiler) { let genel = 0; for (const g of gonderiler) { let kurus = taban(0, g); kurus = bolge(kurus, g); kurus = indirim(kurus, g); genel += kurus; } return `TOPLAM ${genel} kurus`; }
// cagiran/kur.mjs — bilesim koku import { KAYITLAR } from "../kayit.mjs"; import { fatura } from "./fatura.mjs"; import { onizleme } from "./onizleme.mjs"; import { toplam } from "./toplu.mjs"; console.log(fatura(KAYITLAR[1]), onizleme(KAYITLAR[1]), toplam(KAYITLAR));
Sırayı İskeletin Bildiği Düzen
İkinci düzende sıra bir iskelettedir. Adımlar iskeleti çağırmaz; iskelete kendilerini kaydeder ve sıraları geldiğinde çağrılırlar.
// iskelet/boru.mjs — iskelet: adimlari sirayla cagirir, adlarini onceden bilmez const ADIMLAR = []; export const adimEkle = (ad, islev) => ADIMLAR.push({ ad, islev }); export const adimAdlari = () => ADIMLAR.map((a) => a.ad); export function calistir(gonderi) { let kurus = 0; for (const a of ADIMLAR) kurus = a.islev(kurus, gonderi); return kurus; }
// iskelet/adimlar.mjs — adimlar kendini iskelete kaydeder; iskeleti cagirmazlar import { adimEkle } from "./boru.mjs"; const KADEME = [[1, 3900], [5, 6400], [20, 11800]]; const BOLGE = { "34": 100, "06": 115, "65": 140 }; adimEkle("taban", (kurus, g) => KADEME.find(([ust]) => g.agirlik <= ust)?.[1] ?? 15000); adimEkle("bolge", (kurus, g) => Math.round((kurus * (BOLGE[g.adres.slice(0, 2)] ?? 160)) / 100)); adimEkle("indirim", (kurus, g) => Math.round((kurus * (g.sozlesmeli ? 90 : 100)) / 100));
// iskelet/fatura.mjs — sirayi bilmez, iskeleti cagirir import { calistir } from "./boru.mjs"; export const fatura = (gonderi) => `${gonderi.kod} ${calistir(gonderi)} kurus`;
// iskelet/onizleme.mjs — sirayi bilmez import { calistir } from "./boru.mjs"; export const onizleme = (gonderi) => `~${calistir(gonderi)}`;
// iskelet/toplu.mjs — sirayi bilmez import { calistir } from "./boru.mjs"; export const toplam = (gonderiler) => `TOPLAM ${gonderiler.reduce((t, g) => t + calistir(g), 0)} kurus`;
// iskelet/kur.mjs — bilesim koku: adimlari yukler, sonra istemcileri cagirir import "./adimlar.mjs"; import { KAYITLAR } from "../kayit.mjs"; import { adimAdlari } from "./boru.mjs"; import { fatura } from "./fatura.mjs"; import { onizleme } from "./onizleme.mjs"; import { toplam } from "./toplu.mjs"; console.log(fatura(KAYITLAR[1]), onizleme(KAYITLAR[1]), toplam(KAYITLAR)); console.log("adimlar:", adimAdlari().join(" -> "));
node cagiran/kur.mjs node iskelet/kur.mjs
GN-2 6624 kurus ~6624 TOPLAM 25392 kurus GN-2 6624 kurus ~6624 TOPLAM 25392 kurus adimlar: taban -> bolge -> indirim
Aynı sonuç. Fark, adimAdlari() satırında görünen şeydir: ikinci düzende adım listesi çalışma
zamanında sorulabilen bir veridir, birincisinde üç ayrı gövdeye dağılmış bir denetim akışıdır.
Yeni Bir Adımın Maliyeti
Sigorta primi adımı ekleniyor: indirimden sonra tutar yüzde iki artıyor. Birinci düzende yeni
işlev yazılır ve üç çağıranın gövdesine eklenir; ikincisinde tek bir kayıt satırı yeter.
Yerinde düzenlemede yedek uzantısı verilmiştir; GNU ve BSD sed bu biçimde aynı davranır.
cp -r cagiran cagiran-yeni cp -r iskelet iskelet-yeni cat >> cagiran-yeni/adimlar.mjs <<'EOF' export const sigorta = (kurus) => Math.round((kurus * 102) / 100); EOF sed -i.y 's/taban, bolge, indirim/taban, bolge, indirim, sigorta/' cagiran-yeni/*.mjs sed -i.y 's/^ kurus = indirim(kurus, gonderi);/&\n kurus = sigorta(kurus);/' cagiran-yeni/*.mjs sed -i.y 's/^ kurus = indirim(kurus, g);/&\n kurus = sigorta(kurus);/' cagiran-yeni/*.mjs rm -f cagiran-yeni/*.y cat >> iskelet-yeni/adimlar.mjs <<'EOF' adimEkle("sigorta", (kurus) => Math.round((kurus * 102) / 100)); EOF node cagiran-yeni/kur.mjs node iskelet-yeni/kur.mjs for k in cagiran iskelet; do echo "$k: duzenlenen dosya = $(diff -rq $k $k-yeni | grep -c '^Files')" \ " dokunulan satir = $(diff -rU0 $k $k-yeni | grep -cE '^[+-][^+-]')" \ " sirayi bilen dosya = $(grep -l 'kurus = ' $k/*.mjs | wc -l | tr -d ' ')" done
GN-2 6756 kurus ~6756 TOPLAM 25899 kurus GN-2 6756 kurus ~6756 TOPLAM 25899 kurus adimlar: taban -> bolge -> indirim -> sigorta cagiran: duzenlenen dosya = 4 dokunulan satir = 10 sirayi bilen dosya = 3 iskelet: duzenlenen dosya = 1 dokunulan satir = 1 sirayi bilen dosya = 1
İki düzen de aynı yeni sonucu üretiyor. Maliyet dört dosya ve on satıra karşı bir dosya ve bir satır. Son sütun nedeni veriyor: birinci düzende sırayı bilen üç dosya vardı, ikincisinde bir.
Ölçü, açık–kapalı ilkesinin ölçtüğünden farklı bir şeyi söylüyor. Orada yeni bir tür eklemenin maliyeti sayılmıştı; burada sayılan, var olan bir akışa yeni bir adım eklemenin maliyeti. İkisi de aynı yöne çıkar: değişikliğin metne değil veriye düşmesi.
Yönün Tersine Çevrilmesi
İki düzen arasındaki fark ithal grafiğinde de görünür. Birinci düzende fatura.mjs adımları
ithal eder ve onları çağırır; denetim yukarıdan aşağı akar. İkinci düzende adimlar.mjs
iskeleti ithal eder ve ona kaydolur; çağrı yönü aşağıdan yukarı kurulur, çalıştırma yönü
yukarıdan aşağı kalır. Çağıran ile çağrılan arasındaki ilişki tersine döndüğü için ilke bu
adı taşır.
Bu tersine çevirme, bağımlılığın tersine çevrilmesiyle aynı şey değildir. Orada soru soyutlamanın hangi tarafta tanımlandığıydı; burada soru denetim akışının hangi tarafta yazıldığı. İki ilke birlikte kullanılır: iskelet adımların sözleşmesini tanımlar (bağımlılığın tersine çevrilmesi) ve adımları çağırma sırasını da o tutar (Hollywood ilkesi).
İskeletin Bedeli
Tersine çevirmenin bedeli okunabilirlikte ödenir. Birinci düzende akışı görmek için tek bir
gövdeyi okumak yeterlidir; ikincisinde sıra, kayıt çağrılarının çalışma anındaki düzenine
bağlıdır ve bu düzen bileşim kökünün ithal sırasından gelir. adimAdlari() gibi bir sorgunun
bulunması bu yüzden zorunludur: sıra bir veri hâline geldiğinde, o verinin okunabilir olması
gerekir.
İkinci bedel hata ayıklamadadır. Bir adım yanlış sonuç ürettiğinde birinci düzende çağrı zinciri yığıt izinde doğrudan görünür; ikincisinde iskeletin döngüsü araya girer. Ölçüt yine sayıdır: sırayı bilen dosya sayısı birden büyükse ve akışa adım ekleme isteği tekrarlıyorsa iskelet kazanır; akış sabitse ve tek çağıran varsa iskelet yalnız bir dolaylılık katmanıdır.
Özet
- Hollywood ilkesi çağıran ile çağrılan arasındaki yönü tersine çevirir: parçalar akışı çağırmaz, akış parçalara çağrılma noktası verir.
- Ölçü, adım sırasının kaç dosyada yazılı olduğudur: çağıran düzeninde 3, iskelet düzeninde 1 dosya sırayı biliyordu.
- Akışa yeni bir adım eklendiğinde çağıran düzeninde 4 dosya ve 10 satır, iskelet düzeninde 1 dosya ve 1 satır düzenlendi.
- İskelet düzeninde adım listesi çalışma zamanında sorulabilen bir veridir; çağıran düzeninde üç ayrı gövdeye dağılmış bir denetim akışıdır.
- Bedel okunabilirlik ve hata ayıklamadadır; tek çağıranlı ve sabit bir akışta iskelet yalnız bir dolaylılık katmanı ekler.
Sonraki Adım
Buraya kadarki bütün ölçüler modül çiftleri üzerindeydi: iki modül arasındaki bağın gücü, bir modülün içindeki adların ilgililiği, bir çağrının kaç nesne üzerinden geçtiği, bir değişikliğin dokunduğu dosya kümesi. Kod tabanı büyüdüğünde asıl soru biçim değiştirir: artık hangi modülün hangisine bağlanacağı değil, sınırın nereye çizileceği sorulur. Bu sorunun cevabı kararların ne kadar sık değiştiğinde saklıdır — hangi karar yıllarca duran kararlı bir politikadır, hangisi fiyat listesi her yenilendiğinde oynayan bir ayrıntı. Sonraki konu bu ayrımı kurar ve sınırın iki tarafını değişim sıklığıyla ölçer.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.