Ders 08 / 19
Demeter Yasası
Zincirleme erişimin sınırlanması: erişim zinciri derinliğini sayan bir taramanın yazılması, aynı beş çıktıyı üreten iki tasarımda bulgu sayısının karşılaştırılması ve adres kaydı yeniden yapılandırıldığında düzenlenen dosya sayısının ölçülmesi.
İçindekiler
Uyum ölçüsü bir modülün kendi adlarına dokunmasına baktı. Modüller birbirine nesne geçirmeye başladığında yeni bir soru çıkar: bir işlev, aldığı nesnenin içinden aldığı ikinci bir nesnenin üçüncü bir alanına eriştiğinde kaç ayrı yapıyı bilmek zorunda kalır? Bu bağımlılık ne ithal grafiğinde ne de uyum ölçüsünde görünür, çünkü ortada yeni bir ithal ya da yeni bir modül düzeyi ad yoktur.
Demeter yasası (Law of Demeter) bu erişimi sınırlar. Bir işlev yalnız şunların
yöntemlerini çağırmalıdır: kendi nesnesinin, aldığı parametrelerin, kendi kurduğu nesnelerin
ve doğrudan parçalarının. “Komşunla konuş, komşunun komşusuyla değil” biçiminde özetlenir.
Ölçülebilir karşılığı erişim zincirinin derinliğidir: gonderi.kod bir adım, gonderi.alici.adres.il
üç adımdır ve aradaki iki yapının biçimine bağımlılık üretir.
Zincirli Tasarım
Kayıt üç düzey derinliğinde ve beş ayrı istemci onun içine giriyor.
mkdir -p zincirli kisa
// zincirli/kayit.mjs — ic ice gecmis gonderi kaydi export const KAYIT = { kod: "GN-000001", alici: { ad: "A. Yilmaz", adres: { il: "Ankara", ilce: "Cankaya", posta: "06500" } }, tarife: { ad: "agirlik", kademe: { ust: 5, ucret: 6400 } }, };
// zincirli/etiket.mjs — alici kaydinin icine iki adim giriyor export const etiket = (gonderi) => `${gonderi.alici.ad} / ${gonderi.alici.adres.il} ${gonderi.alici.adres.posta}`;
// zincirli/ucret.mjs — tarifenin icindeki kademeye ve adresin icine uzaniyor const BOLGE = { "34": 100, "06": 115, "65": 140 }; export const ucret = (gonderi) => Math.round((gonderi.tarife.kademe.ucret * (BOLGE[gonderi.alici.adres.posta.slice(0, 2)] ?? 160)) / 100);
// zincirli/rapor.mjs — il ve ilceyi ayri ayri okuyor export const rapor = (gonderi) => `${gonderi.kod} ${gonderi.alici.adres.il}/${gonderi.alici.adres.ilce} ${gonderi.tarife.ad}`;
// zincirli/bildirim.mjs — bildirim metni de ayni zinciri yuruyor export const bildirim = (gonderi) => `${gonderi.kod} ${gonderi.alici.adres.il} icin yola cikti`;
// zincirli/sube.mjs — teslim subesi secimi export const sube = (gonderi) => `${gonderi.alici.adres.il}-${gonderi.alici.adres.ilce} subesi`;
// zincirli/kur.mjs — bes istemcinin ciktisi import { KAYIT } from "./kayit.mjs"; import { etiket } from "./etiket.mjs"; import { ucret } from "./ucret.mjs"; import { rapor } from "./rapor.mjs"; import { bildirim } from "./bildirim.mjs"; import { sube } from "./sube.mjs"; for (const f of [etiket, ucret, rapor, bildirim, sube]) console.log(f(KAYIT));
node zincirli/kur.mjs
A. Yilmaz / Ankara 06500 7360 GN-000001 Ankara/Cankaya agirlik GN-000001 Ankara icin yola cikti Ankara-Cankaya subesi
Zincir Taraması
Betik her dosyadaki erişim zincirlerini çıkarır, derinliklerini sayar ve iki adımdan uzun
olanları bulgu olarak bildirir. Dil ve çalışma zamanı nesneleri (Math, JSON, console
gibi) alan zinciri sayılmaz; bileşim kökü olan kur.mjs de taramanın dışındadır.
// zincir-tara.mjs — erisim zinciri derinligini sayar; iki adimdan uzun her zincir bir bulgu import { readdirSync, readFileSync } from "node:fs"; import { join } from "node:path"; const ZINCIR = /\b[a-zA-Z_]\w*(?:\.\w+)+/g; // Dil ve calisma zamani nesneleri alan zinciri sayilmaz. const DIS = ["Math", "Object", "String", "Number", "JSON", "console", "process", "Array"]; const kok = process.argv[2]; let toplamBulgu = 0; let enDerin = 0; for (const d of readdirSync(kok).filter((a) => a.endsWith(".mjs")).sort()) { if (d === "kur.mjs") continue; const metin = readFileSync(join(kok, d), "utf8").replace(/^\/\/.*$/gm, ""); const zincirler = [...metin.matchAll(ZINCIR)] .map((m) => m[0]) .filter((z) => !DIS.includes(z.split(".")[0])); const derinlikler = zincirler.map((z) => z.split(".").length - 1); const bulgular = zincirler.filter((z) => z.split(".").length - 1 >= 2); const yerelEnDerin = derinlikler.length === 0 ? 0 : Math.max(...derinlikler); toplamBulgu += bulgular.length; enDerin = Math.max(enDerin, yerelEnDerin); console.log(` ${d.padEnd(14)} zincir=${zincirler.length} en derin=${yerelEnDerin}` + ` bulgu=${bulgular.length}${bulgular.length > 0 ? " " + bulgular.join(" ") : ""}`); } console.log(`${kok.padEnd(9)} toplam bulgu=${toplamBulgu} en derin zincir=${enDerin}`);
node zincir-tara.mjs zincirli
bildirim.mjs zincir=2 en derin=3 bulgu=1 gonderi.alici.adres.il etiket.mjs zincir=3 en derin=3 bulgu=3 gonderi.alici.ad gonderi.alici.adres.il gonderi.alici.adres.posta kayit.mjs zincir=0 en derin=0 bulgu=0 rapor.mjs zincir=4 en derin=3 bulgu=3 gonderi.alici.adres.il gonderi.alici.adres.ilce gonderi.tarife.ad sube.mjs zincir=2 en derin=3 bulgu=2 gonderi.alici.adres.il gonderi.alici.adres.ilce ucret.mjs zincir=2 en derin=4 bulgu=2 gonderi.tarife.kademe.ucret gonderi.alici.adres.posta.slice zincirli toplam bulgu=11 en derin zincir=4
On bir bulgu, en derin zincir dört adım. Beş istemcinin beşi de adres kaydının alan adlarını biliyor.
Zincirin Kısaltılması
Zinciri kısaltmanın yolu ara nesnelere davranış vermektir. Adres kaydının biçimini yalnız bir modül bilir; kalanlar ondan sonuç ister.
// kisa/kayit.mjs — ayni ic ice gecmis kayit export const KAYIT = { kod: "GN-000001", alici: { ad: "A. Yilmaz", adres: { il: "Ankara", ilce: "Cankaya", posta: "06500" } }, tarife: { ad: "agirlik", kademe: { ust: 5, ucret: 6400 } }, };
// kisa/adres.mjs — adres kaydinin bicimini yalniz burasi bilir export const adres = (kayit) => ({ yaz: () => `${kayit.il} ${kayit.posta}`, bolgeKodu: () => kayit.posta.slice(0, 2), ilIlce: () => `${kayit.il}/${kayit.ilce}`, il: () => kayit.il, });
// kisa/gonderi.mjs — gonderi, komsusuna sorup sonucu kendi adiyla verir import { adres } from "./adres.mjs"; export const gonderi = (kayit) => { const a = adres(kayit.alici.adres); return { kod: kayit.kod, etiketSatiri: () => `${kayit.alici.ad} / ${a.yaz()}`, bolgeKodu: () => a.bolgeKodu(), ilIlce: () => a.ilIlce(), il: () => a.il(), tarifeAdi: () => kayit.tarife.ad, kademeUcreti: () => kayit.tarife.kademe.ucret, }; };
// kisa/etiket.mjs — tek adim export const etiket = (gonderi) => gonderi.etiketSatiri();
// kisa/ucret.mjs — tek adim const BOLGE = { "34": 100, "06": 115, "65": 140 }; export const ucret = (gonderi) => Math.round((gonderi.kademeUcreti() * (BOLGE[gonderi.bolgeKodu()] ?? 160)) / 100);
// kisa/rapor.mjs — tek adim export const rapor = (gonderi) => `${gonderi.kod} ${gonderi.ilIlce()} ${gonderi.tarifeAdi()}`;
// kisa/bildirim.mjs — tek adim export const bildirim = (gonderi) => `${gonderi.kod} ${gonderi.il()} icin yola cikti`;
// kisa/sube.mjs — tek adim export const sube = (gonderi) => `${gonderi.ilIlce().replace("/", "-")} subesi`;
// kisa/kur.mjs — ayni bes cikti import { KAYIT } from "./kayit.mjs"; import { gonderi } from "./gonderi.mjs"; import { etiket } from "./etiket.mjs"; import { ucret } from "./ucret.mjs"; import { rapor } from "./rapor.mjs"; import { bildirim } from "./bildirim.mjs"; import { sube } from "./sube.mjs"; const g = gonderi(KAYIT); for (const f of [etiket, ucret, rapor, bildirim, sube]) console.log(f(g));
node kisa/kur.mjs
node zincir-tara.mjs kisa
A. Yilmaz / Ankara 06500 7360 GN-000001 Ankara/Cankaya agirlik GN-000001 Ankara icin yola cikti Ankara-Cankaya subesi adres.mjs zincir=6 en derin=2 bulgu=1 kayit.posta.slice bildirim.mjs zincir=2 en derin=1 bulgu=0 etiket.mjs zincir=1 en derin=1 bulgu=0 gonderi.mjs zincir=10 en derin=3 bulgu=4 kayit.alici.adres kayit.alici.ad kayit.tarife.ad kayit.tarife.kademe.ucret kayit.mjs zincir=0 en derin=0 bulgu=0 rapor.mjs zincir=3 en derin=1 bulgu=0 sube.mjs zincir=1 en derin=1 bulgu=0 ucret.mjs zincir=2 en derin=1 bulgu=0 kisa toplam bulgu=5 en derin zincir=3
Aynı beş çıktı, on bir bulgu yerine beş bulgu. Daha önemlisi bulguların dağılımı: beş istemci modülün beşi de sıfır bulguya indi, kalan bulgular yapıyı çözen iki modülde toplandı. Demeter yasası yapının bilinmesini ortadan kaldırmaz, bilen modül sayısını düşürür.
Değişikliğin Dokunduğu Dosyalar
Ölçünün karşılığı bir yapı değişikliğinde çıkar. Adres kaydı yeniden düzenleniyor: il ve
ilce alanları konum adında bir alt kayda taşınıyor. posta yerinde kalıyor. Yerinde
düzenlemede yedek uzantısı verilmiştir; GNU ve BSD sed bu biçimde aynı davranır.
cp -r zincirli zincirli-yeni cp -r kisa kisa-yeni YENI='{ konum: { il: "Ankara", ilce: "Cankaya" }, posta: "06500" }' ESKI='{ il: "Ankara", ilce: "Cankaya", posta: "06500" }' sed -i.y "s#$ESKI#$YENI#" zincirli-yeni/kayit.mjs kisa-yeni/kayit.mjs sed -i.y 's/\.adres\.il/.adres.konum.il/g' zincirli-yeni/*.mjs sed -i.y 's/kayit\.il/kayit.konum.il/g' kisa-yeni/adres.mjs rm -f zincirli-yeni/*.y kisa-yeni/*.y node zincirli-yeni/kur.mjs node kisa-yeni/kur.mjs for k in zincirli kisa; do echo "$k: duzenlenen dosya = $(diff -rq $k $k-yeni | grep -c '^Files') ->" \ "$(diff -rq $k $k-yeni | sed 's#.*/\([a-z]*\.mjs\) .*#\1#' | tr '\n' ' ')" done
A. Yilmaz / Ankara 06500 7360 GN-000001 Ankara/Cankaya agirlik GN-000001 Ankara icin yola cikti Ankara-Cankaya subesi A. Yilmaz / Ankara 06500 7360 GN-000001 Ankara/Cankaya agirlik GN-000001 Ankara icin yola cikti Ankara-Cankaya subesi zincirli: duzenlenen dosya = 5 -> bildirim.mjs etiket.mjs kayit.mjs rapor.mjs sube.mjs kisa: duzenlenen dosya = 2 -> adres.mjs kayit.mjs
İki sürüm de aynı beş satırı üretmeye devam ediyor. Fark maliyette: aynı yapı değişikliği
zincirli tasarımda beş dosyayı, kısa tasarımda iki dosyayı düzenletti. Zincirli sürümde ücret
modülü dokunulmayan tek modül oldu, çünkü yalnız posta alanını kullanıyordu — yani hangi
modülün etkileneceği, o modülün hangi alan adına eriştiğine bağlıydı.
Yasanın Yanlış Uygulanışı
Zinciri kısaltmanın yanlış yolu, her ara alan için bir aktarıcı yöntem yazmaktır.
gonderi.aliciAdi(), gonderi.aliciIl(), gonderi.aliciIlce(), gonderi.aliciPosta()
biçiminde büyüyen bir arayüz zinciri kısaltır ama aynı bilgiyi bu kez yöntem adlarına
kopyalar; alan eklendikçe arayüz büyür ve bir önceki derste ölçülen arayüz fazlalığı geri
gelir. Yukarıdaki kısa sürümde adres nesnesi ham alan döndürmüyor, sorulan soruya cevap
veriyor: yaz, bolgeKodu, ilIlce. Bunlar alan adlarının değil, istemcilerin
sorularının adlarıdır.
Yasanın istisnası da vardır: veri taşımak için var olan kayıtlarda — sıralı bir yapılandırma nesnesinde ya da bir sınır noktasında ayrıştırılmış gövdede — zincir kaçınılmazdır ve maliyeti düşüktür, çünkü o kayıt zaten dış bir sözleşmedir. Ölçü, zincirin varlığı değil, kaç modülün aynı yapıyı ayrı ayrı bilmesidir.
Özet
- Demeter yasası bir işlevin erişebileceği nesneleri kendi parçaları, parametreleri ve kurduğu nesnelerle sınırlar; ölçüsü erişim zincirinin adım sayısıdır.
- Beş istemcinin kayda doğrudan girdiği tasarımda 11 bulgu ve en derin 4 adımlık zincir sayıldı.
- Ara nesnelere davranış verilince bulgu sayısı 5’e indi ve beş istemci modülün beşi de sıfır bulguya düştü; kalan bulgular yapıyı çözen iki modülde toplandı.
- Adres kaydı yeniden yapılandırıldığında zincirli tasarımda 5, kısa tasarımda 2 dosya düzenlendi; iki sürüm de aynı beş satırı üretmeye devam etti.
- Zinciri her alan için bir aktarıcı yöntemle kısaltmak ölçüyü düzeltmez; ara nesne alan adlarını değil, istemcilerin sorularını karşılamalıdır.
Sonraki Adım
Kısa sürümde adres nesnesi hâlâ değer döndürüyor: ilIlce() bir dize verir, kararı çağıran
verir. Şube seçimi bu dizeyi alıp biçimlendiriyor, bildirim başka bir parçasını kullanıyor.
Aynı kural birden çok çağrı yerinde tekrarlandığında soru değişir: veriyi dışarı verip kararı
istemciye bırakmak mı, yoksa kararı nesneye söylemek mi? Sonraki ders aynı kuralın kaç ayrı
yerde tekrarlandığını sayar ve kararı veriye taşıyan 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.