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.