İçeriğe geç
academia.sh

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.

Aramak için yazmaya başlayın.

↑↓ Esc gezin · aç · kapat