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.