İçeriğe geç
academia.sh

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.

Aramak için yazmaya başlayın.

↑↓ Esc gezin · aç · kapat