İçeriğe geç
academia.sh

Ders 11 / 19

Değişeni Kapsülle

Değişim eksenlerinin yalıtılması: fiyat listesi ve çıktı biçimi eksenlerinden gelen iki değişikliğin dokunduğu dosya kümelerinin çıkarılması, kesişimlerinin sayılması ve her ekseni kendi modülüne çeken yerleşimde kesişimin sıfıra inmesi.

İçindekiler

Bir önceki dersin ayrık tasarımında ağırlık sınırı sabiti ile sevk kuralı aynı dosyadaydı. İkisi aynı nedenle değişmiyor: sınır fiyat listesi yenilendiğinde, kural kurum politikası değiştiğinde oynar. Bu farkın adı değişim eksenidir: bir modüle değişiklik isteği gönderen ayrı kaynakların her biri bir eksendir.

Değişeni kapsülle (encapsulate what varies) ilkesi, her ekseni kendi modülünde toplamayı söyler. Tek sorumluluk ilkesi aynı fikri değişim nedeni sayısıyla ölçmüştü; buradaki ölçü farklı ve daha doğrudan: iki ayrı eksenden gelen iki değişikliğin dokunduğu dosya kümelerinin kesişimi. Kesişim boş değilse, iki bağımsız değişiklik aynı metni paylaşıyor demektir.

İki Eksen, Dağıtık Yerleşim

Ölçüm, ücretlendirme kitaplığının en sık değişen iki ekseniyle yapılır: fiyat listesi ve çıktı biçimi. Birincisini fiyatlandırma birimi, ikincisini muhasebe raporlaması ister; ikisi aynı anda gelmez. Kitaplığın üç istemcisi var: HTTP ucu, toplu işleme ve rapor. Birinci yerleşimde üçü de ücreti kendi içinde hesaplıyor ve satırı kendi içinde biçimliyor.

mkdir -p dagitik yalitilmis
// kayit.mjs — iki yerlesimin de kullandigi ornek gonderiler
export const KAYITLAR = [
  { kod: "GN-1", agirlik: 0.8, adres: "34100" },
  { kod: "GN-2", agirlik: 3.0, adres: "06500" },
  { kod: "GN-3", agirlik: 12.0, adres: "65200" },
];
// dagitik/web.mjs — fiyat tablosu da cikti bicimi de burada
const KADEME = [[1, 3900], [5, 6400], [20, 11800]];
const BOLGE = { "34": 100, "06": 115, "65": 140 };

export function satir(gonderi) {
  const taban = KADEME.find(([ust]) => gonderi.agirlik <= ust)?.[1] ?? 15000;
  const kurus = Math.round((taban * (BOLGE[gonderi.adres.slice(0, 2)] ?? 160)) / 100);
  return `${gonderi.kod} ${kurus} kurus`;
}
// dagitik/toplu.mjs — ayni fiyat tablosu, ayni bicim, ikinci kopya
const KADEME = [[1, 3900], [5, 6400], [20, 11800]];
const BOLGE = { "34": 100, "06": 115, "65": 140 };

export function satirlar(gonderiler) {
  return gonderiler.map((g) => {
    const taban = KADEME.find(([ust]) => g.agirlik <= ust)?.[1] ?? 15000;
    const kurus = Math.round((taban * (BOLGE[g.adres.slice(0, 2)] ?? 160)) / 100);
    return `${g.kod} ${kurus} kurus`;
  });
}
// dagitik/rapor.mjs — ucuncu kopya
const KADEME = [[1, 3900], [5, 6400], [20, 11800]];
const BOLGE = { "34": 100, "06": 115, "65": 140 };

export function toplamSatiri(gonderiler) {
  let toplam = 0;
  for (const g of gonderiler) {
    const taban = KADEME.find(([ust]) => g.agirlik <= ust)?.[1] ?? 15000;
    toplam += Math.round((taban * (BOLGE[g.adres.slice(0, 2)] ?? 160)) / 100);
  }
  const kurus = toplam;
  return `TOPLAM ${kurus} kurus`;
}

Yalıtılmış Yerleşim

İkinci yerleşimde her eksen kendi modülünde durur: fiyat listesi bir modülde, çıktı biçimi bir modülde. İstemciler ikisini de dışarıdan alır.

// yalitilmis/fiyat.mjs — birinci eksen: fiyat listesi yalniz burada
const KADEME = [[1, 3900], [5, 6400], [20, 11800]];
const BOLGE = { "34": 100, "06": 115, "65": 140 };

export function kurusHesabi(gonderi) {
  const taban = KADEME.find(([ust]) => gonderi.agirlik <= ust)?.[1] ?? 15000;
  return Math.round((taban * (BOLGE[gonderi.adres.slice(0, 2)] ?? 160)) / 100);
}
// yalitilmis/bicim.mjs — ikinci eksen: cikti bicimi yalniz burada
export const tutar = (kurus) => `${kurus} kurus`;
// yalitilmis/web.mjs — iki ekseni de disaridan alir
import { kurusHesabi } from "./fiyat.mjs";
import { tutar } from "./bicim.mjs";

export const satir = (gonderi) => `${gonderi.kod} ${tutar(kurusHesabi(gonderi))}`;
// yalitilmis/toplu.mjs — iki ekseni de disaridan alir
import { kurusHesabi } from "./fiyat.mjs";
import { tutar } from "./bicim.mjs";

export const satirlar = (gonderiler) =>
  gonderiler.map((g) => `${g.kod} ${tutar(kurusHesabi(g))}`);
// yalitilmis/rapor.mjs — iki ekseni de disaridan alir
import { kurusHesabi } from "./fiyat.mjs";
import { tutar } from "./bicim.mjs";

export const toplamSatiri = (gonderiler) =>
  `TOPLAM ${tutar(gonderiler.reduce((t, g) => t + kurusHesabi(g), 0))}`;
// kur.mjs — iki yerlesimin ayni ciktiyi urettigini gosterir
import { KAYITLAR } from "./kayit.mjs";

const kok = process.argv[2];
const { satir } = await import(`./${kok}/web.mjs`);
const { satirlar } = await import(`./${kok}/toplu.mjs`);
const { toplamSatiri } = await import(`./${kok}/rapor.mjs`);

console.log(`${kok}: ${satir(KAYITLAR[0])} | ${satirlar(KAYITLAR).join(" ")} | ${toplamSatiri(KAYITLAR)}`);
node kur.mjs dagitik
node kur.mjs yalitilmis
dagitik: GN-1 3900 kurus | GN-1 3900 kurus GN-2 7360 kurus GN-3 16520 kurus | TOPLAM 27780 kurus
yalitilmis: GN-1 3900 kurus | GN-1 3900 kurus GN-2 7360 kurus GN-3 16520 kurus | TOPLAM 27780 kurus

İki Değişiklik, İki Küme

Eksen A fiyat listesidir: orta kademe ücreti 6400’den 7100’e, Ankara bölge katsayısı 115’ten 120’ye çıkıyor. Eksen B çıktı biçimidir: tutarlar kuruş yerine iki ondalıklı lira olarak yazılacak. İki değişiklik ayrı kopyalara ayrı ayrı uygulanır ve her birinin dokunduğu dosya kümesi çıkarılır. Yerinde düzenlemede yedek uzantısı verilmiştir; GNU ve BSD sed bu biçimde aynı davranır.

for k in dagitik yalitilmis; do
  cp -r $k $k-a
  cp -r $k $k-b
  sed -i.y 's/\[5, 6400\]/[5, 7100]/; s/"06": 115/"06": 120/' $k-a/*.mjs
  sed -i.y 's/\${kurus} kurus/${(kurus \/ 100).toFixed(2)} TL/' $k-b/*.mjs
  rm -f $k-a/*.y $k-b/*.y
  node kur.mjs $k-a
  node kur.mjs $k-b
done

degisen() { diff -rq "$1" "$2" | sed 's#^Files [^/]*/\([^ ]*\) and .*#\1#' | sort; }
for k in dagitik yalitilmis; do
  degisen $k $k-a > eksen-a.txt
  degisen $k $k-b > eksen-b.txt
  echo "$k eksen A -> $(tr '\n' ' ' < eksen-a.txt)"
  echo "$k eksen B -> $(tr '\n' ' ' < eksen-b.txt)"
  echo "$k kesisim = $(comm -12 eksen-a.txt eksen-b.txt | wc -l | tr -d ' ') $(comm -12 eksen-a.txt eksen-b.txt | tr '\n' ' ')"
  echo "$k bolge katsayisini bilen dosya = $(grep -l BOLGE $k/*.mjs | wc -l | tr -d ' ')"
done
dagitik-a: GN-1 3900 kurus | GN-1 3900 kurus GN-2 8520 kurus GN-3 16520 kurus | TOPLAM 28940 kurus
dagitik-b: GN-1 39.00 TL | GN-1 39.00 TL GN-2 73.60 TL GN-3 165.20 TL | TOPLAM 277.80 TL
yalitilmis-a: GN-1 3900 kurus | GN-1 3900 kurus GN-2 8520 kurus GN-3 16520 kurus | TOPLAM 28940 kurus
yalitilmis-b: GN-1 39.00 TL | GN-1 39.00 TL GN-2 73.60 TL GN-3 165.20 TL | TOPLAM 277.80 TL
dagitik eksen A -> rapor.mjs toplu.mjs web.mjs
dagitik eksen B -> rapor.mjs toplu.mjs web.mjs
dagitik kesisim = 3 rapor.mjs toplu.mjs web.mjs
dagitik bolge katsayisini bilen dosya = 3
yalitilmis eksen A -> fiyat.mjs
yalitilmis eksen B -> bicim.mjs
yalitilmis kesisim = 0
yalitilmis bolge katsayisini bilen dosya = 1

İki yerleşim de her iki değişiklikten sonra aynı sonucu üretiyor; fark maliyette. Dağıtık yerleşimde her iki eksen de aynı üç dosyaya dokundu ve kesişim üç. Yalıtılmış yerleşimde kümeler ayrık ve kesişim sıfır.

Son satır kesişimin nedenini gösteriyor: dağıtık yerleşimde bölge katsayısı üç dosyada tanımlıydı, yalıtılmışta bir dosyada. Kesişimin kaynağı bilgiyi kopyalamaktır.

Kesişimin Anlamı

Kesişim sayısı üç ayrı riskin ortak ölçüsüdür. Birincisi, iki bağımsız değişikliğin aynı anda gelmesi durumunda aynı dosyaların düzenlenmesi gerekir; ikisi de aynı satırlara dokunuyorsa birleştirme el işine döner. İkincisi, fiyat değişikliğini yapan kişi biçim kodunu da okumak zorundadır — okuma yüzeyi, değişikliğin kendisiyle orantısız büyür. Üçüncüsü ve en pahalısı, bir eksende yapılan değişikliğin diğer eksenin sınamalarını kırma olasılığıdır; kesişim sıfır olduğunda bu olasılık yapısal olarak ortadan kalkar.

Ölçü tersinden de okunur. Kesişim sıfırsa ve bir eksende gelen değişiklik yine de iki dosyayı düzenlettiyse, o eksen henüz tam yalıtılmamıştır. Yalıtımın hedefi dosya sayısını azaltmak değil, kümeleri ayrık tutmaktır.

Eksenin Doğru Seçilmesi

Yanlış eksen seçmek maliyeti azaltmaz, yerini değiştirir. Yukarıdaki bölme “hesap” ve “biçim” olarak yapıldı; bunun yerine “web tarafı” ve “toplu taraf” biçiminde bir bölme yapılsaydı iki eksenden gelen değişiklikler yine aynı dosyalara düşerdi, çünkü fiyat listesi de biçim de her iki tarafta kullanılıyor.

Ekseni bulmanın yolu, tek bir kod tabanına bakmak değil, gelen değişiklik isteklerine bakmaktır. İki istek hiç aynı anda gelmiyorsa ve farklı kişilerden geliyorsa iki ayrı eksendir. Aynı kişiden, aynı gerekçeyle ve birlikte geliyorsa tek eksendir ve bölünmeleri yalnız dosya sayısını artırır. Fiyat listesiyle çıktı biçimi bu ölçüte göre ayrıdır: birini fiyatlandırma, diğerini muhasebe raporlaması ister.

Özet

  • Değişim ekseni, bir modüle değişiklik isteği gönderen ayrı kaynaklardır; ilke her ekseni kendi modülünde toplamayı söyler.
  • Ölçü, iki ayrı eksenden gelen iki değişikliğin dokunduğu dosya kümelerinin kesişimidir.
  • Dağıtık yerleşimde iki eksen de aynı üç dosyaya dokundu ve kesişim 3 çıktı; yalıtılmış yerleşimde kümeler ayrıktı ve kesişim 0 oldu.
  • Kesişimin kaynağı bilgiyi kopyalamaktır: bölge katsayısı dağıtık yerleşimde 3 dosyada, yalıtılmışta 1 dosyada tanımlıydı.
  • Eksen, kod yapısına değil gelen değişiklik isteklerinin kaynağına göre seçilir; yanlış eksen kesişimi azaltmaz.

Sonraki Adım

Yalıtılmış yerleşimde üç istemci de fiyat.mjs modülünü adıyla ithal ediyor ve kurusHesabi işlevini doğrudan çağırıyor. Fiyat hesabının ikinci bir gerçekleştirimi gerektiğinde — sözleşmeli müşteriye özel bir liste, ya da sınama için sabit bir liste — istemcilerin ithal satırı değişmek zorunda. Somut bir modüle bağlanmanın bedeli, gerçekleştirim değiştiğinde dokunulan satır sayısıyla ölçülebilir. Sonraki ders bu bedeli sayar ve istemcinin somut tip yerine bir soyutlamaya bağlandığı sürümle karşılaştırır.

İ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