İçeriğe geç
academia.sh

Ders 07 / 19

Uyum

Modül içindeki parçaların birbirine ait olup olmadığının sayılması: işlevlerin dokundukları modül düzeyi adlara göre kümelenmesi, bileşen sayısı ile paylaşmayan işlev çifti sayısının hesaplanması, düşük uyumlu modülün bölünüp ölçümün tekrarlanması ve bölmenin yeni bağlaşım kenarı üretmediğinin gösterilmesi.

İçindekiler

Bağlaşım ölçüsü modüller arasındaki kenarları saydı ve modülün içine hiç bakmadı. Bir modül dört ad dışa veriyor olabilir; bu dört adın birbirine ait olup olmadığı sorusu ölçünün dışındadır. Aynı dosyada duran işlevler ortak bir veriye mi dokunuyor, yoksa yalnız aynı dosyada oldukları için mi yan yanalar?

Uyum (cohesion) bir modül içindeki parçaların birbirine ait olma derecesidir ve bağlaşımın modül içi karşılığıdır. Ölçülebilir tanımı şudur: bir modülün işlevleri, dokundukları modül düzeyi adlara göre kümelendiğinde kaç ayrı kümeye ayrılıyor? Tek kümeye ayrılıyorsa modül tek bir şeyle ilgilidir; birden çok kümeye ayrılıyorsa modül birkaç ayrı şeyi aynı dosyada tutuyordur.

Ölçülecek Modül

Aşağıdaki modül gönderiyle ilgili üç işi bir arada tutuyor: ücret hesabı, takip numarası üretimi ve adres biçimleme. Üçü de “gönderi” sözcüğüyle anılabildiği için aynı dosyada durmaları makul görünür.

mkdir -p birlesik bolunmus
// birlesik/gonderi-servisi.mjs — gonderiyle ilgili her sey tek modulde
const KADEME = [[1, 3900], [5, 6400], [20, 11800]];
const BOLGE = { "34": 100, "06": 115, "65": 140 };
const IL_ADLARI = { "34": "Istanbul", "06": "Ankara", "65": "Van" };
const ONEK = "GN";
let SAYAC = 0;

export function taban(agirlik) {
  return KADEME.find(([ust]) => agirlik <= ust)?.[1] ?? 15000;
}

export function bolgeKatsayisi(adres) {
  return BOLGE[adres.slice(0, 2)] ?? 160;
}

export function ucret(gonderi) {
  return Math.round((taban(gonderi.agirlik) * bolgeKatsayisi(gonderi.adres)) / 100);
}

export function takipNo() {
  SAYAC += 1;
  return `${ONEK}-${String(SAYAC).padStart(6, "0")}`;
}

export function takipGecerli(no) {
  return new RegExp(`^${ONEK}-\\d{6}$`).test(no);
}

export function adresYaz(adres) {
  return `${IL_ADLARI[adres.slice(0, 2)] ?? "bilinmeyen"} ${adres}`;
}

Kümeleme Ölçeri

Betik modülün üst düzey adlarını çıkarır, her işlevin gövdesinde hangi adların geçtiğini bulur ve iki işlevi aynı kümeye koyar: ya ortak bir ada dokunuyorlarsa ya da biri diğerini çağırıyorsa. Sonuçta bileşen sayısı ile ortak ada dokunan ve dokunmayan işlev çifti sayısı raporlanır.

// uyum-olc.mjs — modul icindeki islevleri dokunduklari adlara gore kumeler
import { readFileSync } from "node:fs";

function govde(metin, acilis) {
  let i = acilis;
  let derinlik = 0;
  do {
    if (metin[i] === "{") derinlik += 1;
    else if (metin[i] === "}") derinlik -= 1;
    i += 1;
  } while (i < metin.length && derinlik > 0);
  return metin.slice(acilis, i);
}

function coz(yol) {
  const metin = readFileSync(yol, "utf8");
  const veri = [...metin.matchAll(/^(?:export\s+)?(?:const|let)\s+(\w+)/gm)].map((m) => m[1]);
  const islevAdlari = [...metin.matchAll(/^(?:export\s+)?function\s+(\w+)/gm)].map((m) => m[1]);
  const tumu = [...veri, ...islevAdlari];
  const islevler = [];
  for (const m of metin.matchAll(/^(?:export\s+)?function\s+(\w+)\s*\(/gm)) {
    const g = govde(metin, metin.indexOf("{", m.index + m[0].length));
    const ref = tumu.filter((a) => a !== m[1] && new RegExp(`\\b${a}\\b`).test(g));
    islevler.push({ ad: m[1], ref: new Set(ref) });
  }
  return { veri, islevler };
}

const kesisiyor = (a, b) =>
  [...a.ref].some((x) => b.ref.has(x)) || a.ref.has(b.ad) || b.ref.has(a.ad);

for (const yol of process.argv.slice(2)) {
  const { veri, islevler } = coz(yol);
  const bilesen = new Map(islevler.map((f, i) => [f.ad, i]));
  let degisti = true;
  while (degisti) {
    degisti = false;
    for (const a of islevler) {
      for (const b of islevler) {
        if (a.ad !== b.ad && kesisiyor(a, b) && bilesen.get(a.ad) !== bilesen.get(b.ad)) {
          const enKucuk = Math.min(bilesen.get(a.ad), bilesen.get(b.ad));
          bilesen.set(a.ad, enKucuk);
          bilesen.set(b.ad, enKucuk);
          degisti = true;
        }
      }
    }
  }
  const kumeler = new Map();
  for (const [ad, k] of bilesen) kumeler.set(k, [...(kumeler.get(k) ?? []), ad]);

  let paylasan = 0;
  let paylasmayan = 0;
  for (let i = 0; i < islevler.length; i += 1) {
    for (let j = i + 1; j < islevler.length; j += 1) {
      if (kesisiyor(islevler[i], islevler[j])) paylasan += 1; else paylasmayan += 1;
    }
  }
  console.log(`${yol}  islev=${islevler.length} veri=${veri.length} bilesen=${kumeler.size}`);
  let n = 0;
  for (const [, adlar] of [...kumeler].sort((a, b) => a[0] - b[0])) {
    n += 1;
    console.log(`  bilesen ${n}: ${adlar.join(", ")}`);
  }
  console.log(`  paylasan cift=${paylasan} paylasmayan cift=${paylasmayan}` +
    ` uyumsuzluk=${Math.max(0, paylasmayan - paylasan)}`);
}
node uyum-olc.mjs birlesik/gonderi-servisi.mjs
birlesik/gonderi-servisi.mjs  islev=6 veri=5 bilesen=3
  bilesen 1: taban, bolgeKatsayisi, ucret
  bilesen 2: takipNo, takipGecerli
  bilesen 3: adresYaz
  paylasan cift=3 paylasmayan cift=12 uyumsuzluk=9

Altı işlev üç ayrı kümeye ayrıldı. On beş işlev çiftinden yalnız üçü ortak bir ada dokunuyor; kalan on ikisinin hiçbir ortak noktası yok. Uyumsuzluk sayısı bu iki değerin farkıdır ve dokuz çıktı. Modülün adı “gönderi servisi” olduğu için parçaların ilgili göründüğüne dikkat: ölçüm ada değil, koda bakıyor.

Bölme ve Yeniden Ölçüm

Bileşenler bölmenin sınırını doğrudan veriyor. Üç bileşen üç modüle çıkarılır; işlev gövdeleri değişmez, yalnız hangi dosyada durdukları değişir.

// bolunmus/ucret.mjs — birinci bilesen: ucret hesabi
const KADEME = [[1, 3900], [5, 6400], [20, 11800]];
const BOLGE = { "34": 100, "06": 115, "65": 140 };

export function taban(agirlik) {
  return KADEME.find(([ust]) => agirlik <= ust)?.[1] ?? 15000;
}

export function bolgeKatsayisi(adres) {
  return BOLGE[adres.slice(0, 2)] ?? 160;
}

export function ucret(gonderi) {
  return Math.round((taban(gonderi.agirlik) * bolgeKatsayisi(gonderi.adres)) / 100);
}
// bolunmus/takip.mjs — ikinci bilesen: takip numarasi
const ONEK = "GN";
let SAYAC = 0;

export function takipNo() {
  SAYAC += 1;
  return `${ONEK}-${String(SAYAC).padStart(6, "0")}`;
}

export function takipGecerli(no) {
  return new RegExp(`^${ONEK}-\\d{6}$`).test(no);
}
// bolunmus/adres.mjs — ucuncu bilesen: adres bicimleme
const IL_ADLARI = { "34": "Istanbul", "06": "Ankara", "65": "Van" };

export function adresYaz(adres) {
  return `${IL_ADLARI[adres.slice(0, 2)] ?? "bilinmeyen"} ${adres}`;
}
node uyum-olc.mjs bolunmus/ucret.mjs bolunmus/takip.mjs bolunmus/adres.mjs
grep -c "^import" bolunmus/ucret.mjs bolunmus/takip.mjs bolunmus/adres.mjs || true
bolunmus/ucret.mjs  islev=3 veri=2 bilesen=1
  bilesen 1: taban, bolgeKatsayisi, ucret
  paylasan cift=2 paylasmayan cift=1 uyumsuzluk=0
bolunmus/takip.mjs  islev=2 veri=2 bilesen=1
  bilesen 1: takipNo, takipGecerli
  paylasan cift=1 paylasmayan cift=0 uyumsuzluk=0
bolunmus/adres.mjs  islev=1 veri=1 bilesen=1
  bilesen 1: adresYaz
  paylasan cift=0 paylasmayan cift=0 uyumsuzluk=0
bolunmus/ucret.mjs:0
bolunmus/takip.mjs:0
bolunmus/adres.mjs:0

Üç modülün üçünde de bileşen sayısı bir, uyumsuzluk sıfır. Son üç satır bölmenin bedelini sayıyor: yeni modüller birbirini hiç ithal etmiyor. Uyumsuz bir modülü bölmek bağlaşım üretmedi, çünkü bölünen parçalar zaten birbirine dokunmuyordu. Bölmenin bağlaşım maliyeti ancak gerçekten ortak veriye dokunan işlevler ayrıldığında doğar.

Davranış da değişmedi.

// ayni-davranis.mjs — iki yerlesim ayni sonuclari uretir
const G = { agirlik: 3.0, adres: "06500" };
const b = await import("./birlesik/gonderi-servisi.mjs");
const u = await import("./bolunmus/ucret.mjs");
const t = await import("./bolunmus/takip.mjs");
const a = await import("./bolunmus/adres.mjs");

console.log("birlesik ->", b.ucret(G), b.takipNo(), b.adresYaz(G.adres), b.takipGecerli("GN-000001"));
console.log("bolunmus ->", u.ucret(G), t.takipNo(), a.adresYaz(G.adres), t.takipGecerli("GN-000001"));
node ayni-davranis.mjs
birlesik -> 7360 GN-000001 Ankara 06500 true
bolunmus -> 7360 GN-000001 Ankara 06500 true

Uyumun Pratik Karşılığı

Bileşen sayısı soyut kalmasın diye ikinci bir ölçü alınır: bir verinin değişmesi hâlinde okunması gereken dosyanın büyüklüğü. Takip numarası öneki ile bölge katsayıları farklı kişilerin kararıdır; ikisinin de nerede durduğu ölçülebilir.

// etki-alani.mjs — bir veri adi degistiginde okunmasi gereken dosyanin buyuklugu
import { readFileSync } from "node:fs";

const [ad, ...dosyalar] = process.argv.slice(2);

for (const yol of dosyalar) {
  const metin = readFileSync(yol, "utf8");
  if (!new RegExp(`^(?:const|let)\\s+${ad}\\b`, "m").test(metin)) continue;
  const islev = [...metin.matchAll(/^(?:export\s+)?function\s+\w+/gm)].length;
  const satir = metin.trim().split("\n").length;
  console.log(`${ad.padEnd(10)} ${yol.padEnd(30)} islev=${islev} satir=${satir}`);
}
for ad in ONEK BOLGE; do
  node etki-alani.mjs $ad birlesik/gonderi-servisi.mjs \
    bolunmus/ucret.mjs bolunmus/takip.mjs bolunmus/adres.mjs
done
ONEK       birlesik/gonderi-servisi.mjs   islev=6 satir=31
ONEK       bolunmus/takip.mjs             islev=2 satir=12
BOLGE      birlesik/gonderi-servisi.mjs   islev=6 satir=31
BOLGE      bolunmus/ucret.mjs             islev=3 satir=15

Takip önekini değiştirmek birleşik yerleşimde 6 işlevlik, 31 satırlık bir dosyayı açmayı gerektiriyor; bölünmüş yerleşimde 2 işlevlik, 12 satırlık bir dosyayı. Uyumun karşılığı budur: değişikliğin okuma yüzeyi, değişikliğin kendisiyle orantılı kalır.

Yanıltıcı Uyum Ölçütleri

Adın benzerliği uyum değildir. gonderi-servisi adı altındaki altı işlev de gönderiyle ilgiliydi; ölçüm yine üç bileşen buldu. Aynı biçimde “hepsi yardımcı işlev” ya da “hepsi doğrulama yapıyor” gibi gerekçeler de ölçülemez; bu gruplamalarda işlevler ortak veri paylaşmaz, yalnız benzer iş yaparlar.

Ters yönde bir yanılgı da vardır: bileşen sayısını bire indirmek her zaman doğru değildir. Bir modülün bütün işlevlerini tek bir paylaşılan nesneye dokundurmak bileşen sayısını bire düşürür ama bu, bir önceki derste ölçülen ortak bağlaşımı üretir. İki ölçü birlikte okunmalıdır: modül içi bileşen sayısı bir, modüller arası yazma sayısı sıfır.

Özet

  • Uyum, bir modül içindeki işlevlerin dokundukları adlara göre kaç kümeye ayrıldığıdır; bağlaşımın modül içi karşılığıdır.
  • Birleşik modülde 6 işlev 3 bileşene ayrıldı; 15 işlev çiftinden 12’si hiçbir ortak ada dokunmuyordu, uyumsuzluk 9 ölçüldü.
  • Bileşenler bölmenin sınırını doğrudan verir: üç bileşen üç modüle çıkarıldığında her modülde bileşen sayısı 1, uyumsuzluk 0 oldu.
  • Bölmenin bağlaşım maliyeti sıfır çıktı: yeni modüller birbirini hiç ithal etmiyor, çünkü ayrılan parçalar zaten ortak veriye dokunmuyordu.
  • Bir verinin değişmesinde okunacak dosya 6 işlev ve 31 satırdan 2 işlev ve 12 satıra indi.

Sonraki Adım

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, o işlev kaç ayrı tipin yapısını bilmek zorunda kalır? gonderi.alici.adres.il gibi bir zincir, aradaki her adımın biçimine bağımlılık üretir ve bu bağımlılık ne ithal grafiğinde ne de uyum ölçüsünde görünür. Sonraki ders zincir derinliğini sayan bir tarama yazar ve zincir kısaldığında bir alan değişikliğinin dokunduğu dosya sayısının nasıl düştüğünü ö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