Ders 06 / 19
Bağlaşım Türleri
İki modül arasındaki bağın gücünün sayılması: paylaşılan ad sayısı, geçilen parametre sayısı ve ortak değiştirilen durum ölçen bir betiğin yazılması, aynı fatura satırını üreten üç tasarımda ölçümlerin karşılaştırılması ve paylaşılan duruma yapılan tek satırlık bir dokunuşun yalnız bir tasarımı bozduğunun gösterilmesi.
İçindekiler
SOLID ilkeleri bir birimin kime bağlanacağını söyledi ama bağın kendisini ölçmedi. “Gevşek bağlı” ile “sıkı bağlı” arasındaki fark, bir tasarım yorumundan çıkarılmak yerine sayılabilir olmalıdır. Bu konu iki ölçü kurar; ilki modüller arasındaki bağa ait.
Bağlaşım (coupling) iki modül arasındaki bağın gücüdür. Gücün kaynağı üç yerdedir: bir modülün diğerinden aldığı ad sayısı, çağrılarda geçirilen parametre sayısı ve iki modülün ortak değiştirdiği durum. Bağlaşım türleri bu üç ölçünün birleşiminden çıkar ve klasik sıralaması en güçlüden en zayıfa doğrudur: içerik, ortak, denetim, damga, veri.
| Tür | İngilizce | Belirtisi |
|---|---|---|
| İçerik bağlaşımı | content coupling | Bir modül diğerinin iç adına erişir |
| Ortak bağlaşım | common coupling | İki modül aynı değiştirilebilir durumu paylaşır |
| Denetim bağlaşımı | control coupling | Çağrıya, çağrılanın hangi dalı seçeceğini söyleyen bayrak geçilir |
| Damga bağlaşımı | stamp coupling | Kullanılmayan alanları da olan bütün bir kayıt geçilir |
| Veri bağlaşımı | data coupling | Yalnız gereken sayı ya da dize geçilir |
Güçlü bağlaşım kötü olduğu için değil, değişikliği yayan yüzeyi büyüttüğü için sınırlanır. Aşağıdaki üç tasarım aynı fatura satırını üretir; fark yalnız bu yüzeyin genişliğindedir.
Aynı Çıktı, Üç Tasarım
Birinci tasarım paylaşılan bir ayar nesnesi üzerinden çalışır. Hesap modülü sonucu hem döndürür hem paylaşılan duruma yazar; fatura modülü sonucu oradan ve hesabın iç adından okur.
mkdir -p ortak damga veri
// ortak/ayar.mjs — butun modullerin okuyup yazdigi paylasilan durum export const AYAR = { bolge: 100, kdv: 20, _son: 0 };
// ortak/hesap.mjs — sonucu doner, ama once paylasilan duruma yazar import { AYAR } from "./ayar.mjs"; export const _KADEME = [[1, 3900], [5, 6400], [20, 11800]]; const BOLGE = { "34": 100, "06": 115, "65": 140 }; export function hesapla(gonderi) { AYAR.bolge = BOLGE[gonderi.adres.slice(0, 2)] ?? 160; const taban = _KADEME.find(([ust]) => gonderi.agirlik <= ust)?.[1] ?? 15000; AYAR._son = Math.round((taban * AYAR.bolge * (100 + AYAR.kdv)) / 10000); return AYAR._son; }
// ortak/fatura.mjs — sonucu paylasilan durumdan ve hesabin ic adindan okur import { AYAR } from "./ayar.mjs"; import { hesapla, _KADEME } from "./hesap.mjs"; export function satir(gonderi) { hesapla(gonderi); return `${AYAR._son} kurus (${_KADEME.length} kademe)`; }
İkinci tasarımda paylaşılan durum yoktur. Bunun yerine bütün gönderi kaydı ve hesabın hangi dalı seçeceğini söyleyen bir bayrak geçirilir.
// damga/hesap.mjs — paylasilan durum yok; butun kayit ve bir bayrak gecirilir const KADEME = [[1, 3900], [5, 6400], [20, 11800]]; const BOLGE = { "34": 100, "06": 115, "65": 140 }; export const kademeSayisi = () => KADEME.length; export function hesapla(gonderi, kdvEkle) { const bolge = BOLGE[gonderi.adres.slice(0, 2)] ?? 160; const taban = KADEME.find(([ust]) => gonderi.agirlik <= ust)?.[1] ?? 15000; const ham = (taban * bolge) / 100; return Math.round(kdvEkle ? (ham * 120) / 100 : ham); }
// damga/fatura.mjs — hesaba butun gonderiyi ve davranisi secen bayragi verir import { hesapla, kademeSayisi } from "./hesap.mjs"; export const satir = (gonderi) => `${hesapla(gonderi, true)} kurus (${kademeSayisi()} kademe)`;
Üçüncü tasarımda hesap adımları ayrı işlevlere bölünür ve her biri yalnız ihtiyaç duyduğu sayıyı alır. Bayrak, iki ayrı işlev adına dönüşür.
// veri/hesap.mjs — her adim yalniz ihtiyac duydugu sayilari alir const KADEME = [[1, 3900], [5, 6400], [20, 11800]]; export const kademeSayisi = () => KADEME.length; export const taban = (agirlik) => KADEME.find(([ust]) => agirlik <= ust)?.[1] ?? 15000; export const bolgeli = (kurus, katsayi) => (kurus * katsayi) / 100; export const kdvli = (kurus) => Math.round((kurus * 120) / 100);
// veri/fatura.mjs — hesaba yalniz sayi gecer, bayrak yok import { taban, bolgeli, kdvli, kademeSayisi } from "./hesap.mjs"; const BOLGE = { "34": 100, "06": 115, "65": 140 }; export function satir(gonderi) { const katsayi = BOLGE[gonderi.adres.slice(0, 2)] ?? 160; return `${kdvli(bolgeli(taban(gonderi.agirlik), katsayi))} kurus (${kademeSayisi()} kademe)`; }
Ölçer
Aşağıdaki betik bir dizindeki modülleri okur, her ithal deyimi için bir kenar üretir ve o kenarın üç ölçüsünü hesaplar: alınan ad sayısı, çağrılarda geçirilen en yüksek parametre sayısı ve ithal edilmiş bir ada yapılan yazma sayısı. Türü bu ölçülerden türetir; bir kenarda birden çok tür bulunursa en güçlüsü etiket olur.
// baglasim-olc.mjs — iki modul arasindaki bagin uc olcusu ve turu import { readdirSync, readFileSync } from "node:fs"; import { join } from "node:path"; const ITHAL = /import\s*\{([^}]*)\}\s*from\s*["']\.\/([^"']+)["']/g; // Butun kayit olarak gecirilen alan nesneleri. Bir cagrida bunlardan biri argumansa damga. const KAYITLAR = ["gonderi", "tarife", "musteri"]; function cagriArgumanlari(metin, ad) { const cagrilar = []; for (const m of metin.matchAll(new RegExp(`\\b${ad}\\s*\\(`, "g"))) { let i = m.index + m[0].length; let derinlik = 1; let parca = ""; const parcalar = []; while (i < metin.length && derinlik > 0) { const c = metin[i]; if ("([{".includes(c)) derinlik += 1; else if (")]}".includes(c)) derinlik -= 1; if (derinlik === 0) break; if (c === "," && derinlik === 1) { parcalar.push(parca.trim()); parca = ""; } else parca += c; i += 1; } if (parca.trim() !== "") parcalar.push(parca.trim()); cagrilar.push(parcalar); } return cagrilar; } const kok = process.argv[2]; const dosyalar = readdirSync(kok).filter((a) => a.endsWith(".mjs")).sort(); const metinler = new Map(dosyalar.map((d) => [d, readFileSync(join(kok, d), "utf8")])); // Once tum agacta hangi ithal edilmis adlara yazildigini topla: yazilan ad paylasilan durumdur. const yazilan = new Set(); for (const [, metin] of metinler) { for (const m of metin.matchAll(/\b([A-Za-z_]\w*)\.\w+\s*=[^=]/g)) yazilan.add(m[1]); } let toplamAd = 0; let toplamParam = 0; let toplamYazma = 0; for (const d of dosyalar) { const metin = metinler.get(d); for (const m of metin.matchAll(ITHAL)) { const adlar = m[1].split(",").map((s) => s.trim()).filter(Boolean); const hedef = m[2]; let param = 0; let yazma = 0; const turler = new Set(); for (const ad of adlar) { const cagrilar = cagriArgumanlari(metin, ad); const arg = cagrilar.flat(); param += cagrilar.length === 0 ? 0 : Math.max(...cagrilar.map((c) => c.length)); yazma += [...metin.matchAll(new RegExp(`\\b${ad}\\.\\w+\\s*=[^=]`, "g"))].length; if (ad.startsWith("_")) turler.add("icerik"); if (yazilan.has(ad)) turler.add("ortak"); if (arg.some((a) => a === "true" || a === "false")) turler.add("denetim"); if (arg.some((a) => KAYITLAR.includes(a))) turler.add("damga"); } if (turler.size === 0) turler.add("veri"); const sira = ["icerik", "ortak", "denetim", "damga", "veri"]; const enGuclu = sira.find((t) => turler.has(t)); toplamAd += adlar.length; toplamParam += param; toplamYazma += yazma; console.log(` ${d} -> ${hedef}`.padEnd(30) + `ad=${adlar.length} parametre=${param} yazma=${yazma} tur=${enGuclu}` + (turler.size > 1 ? ` (${[...sira.filter((t) => turler.has(t))].join(", ")})` : "")); } } console.log(`${kok.padEnd(6)} toplam: ad=${toplamAd} parametre=${toplamParam} yazma=${toplamYazma}`);
node baglasim-olc.mjs ortak node baglasim-olc.mjs damga node baglasim-olc.mjs veri
fatura.mjs -> ayar.mjs ad=1 parametre=0 yazma=0 tur=ortak fatura.mjs -> hesap.mjs ad=2 parametre=1 yazma=0 tur=icerik (icerik, damga) hesap.mjs -> ayar.mjs ad=1 parametre=0 yazma=2 tur=ortak ortak toplam: ad=4 parametre=1 yazma=2 fatura.mjs -> hesap.mjs ad=2 parametre=2 yazma=0 tur=denetim (denetim, damga) damga toplam: ad=2 parametre=2 yazma=0 fatura.mjs -> hesap.mjs ad=4 parametre=4 yazma=0 tur=veri veri toplam: ad=4 parametre=4 yazma=0
Üç tasarım da aynı satırı üretiyor, ölçüleri farklı. Birinci tasarımda üç kenar var, ikisi paylaşılan duruma bakıyor ve iki yazma sayıldı. İkinci tasarımda tek kenar kaldı; ad ve parametre sayısı düşük ama tür denetim bağlaşımı. Üçüncü tasarımda yazma sıfır, tür veri bağlaşımı, buna karşılık ad sayısı dörde ve parametre sayısı dörde çıktı.
Son satır ölçünün önemli bir yanını gösteriyor: tür ile büyüklük ayrı şeylerdir. Üçüncü tasarım en zayıf bağlaşım türüne sahip, ama en çok adı paylaşan tasarım da o. Bağlaşımı azaltmak “daha az ad geçir” demek değildir; geçen şeyin ne olduğunu değiştirmektir.
Değişikliğin Yayılması
Ölçünün karşılığı, ilgisiz bir yerden gelen bir dokunuşta görülür. Aşağıdaki betik üç tasarımın satırını yazar, sonra yalnız paylaşılan ayar nesnesinin bir alanına dokunur ve satırları yeniden yazar.
// uzaktan-dokunus.mjs — ilgisiz bir modulun tek satiri uc tasarimi nasil etkiliyor import { AYAR } from "./ortak/ayar.mjs"; import { satir as ortakSatir } from "./ortak/fatura.mjs"; import { satir as damgaSatir } from "./damga/fatura.mjs"; import { satir as veriSatir } from "./veri/fatura.mjs"; const G = { agirlik: 3.0, adres: "06500" }; const yaz = (etiket) => console.log(`${etiket.padEnd(8)} ortak=${ortakSatir(G)} damga=${damgaSatir(G)} veri=${veriSatir(G)}`); yaz("once"); AYAR.kdv = 0; yaz("sonra");
node uzaktan-dokunus.mjs
once ortak=8832 kurus (3 kademe) damga=8832 kurus (3 kademe) veri=8832 kurus (3 kademe) sonra ortak=7360 kurus (3 kademe) damga=8832 kurus (3 kademe) veri=8832 kurus (3 kademe)
Hesap modülüne, fatura modülüne, kademe tablosuna dokunulmadı. Değişen tek şey paylaşılan nesnenin bir alanı oldu ve birinci tasarımın çıktısı 8832’den 7360’a düştü. Ortak bağlaşımın ölçülebilir tanımı budur: bir modülün davranışı, onu hiç ithal etmemiş bir modül tarafından değiştirilebiliyor.
Türlerin Somut Belirtileri
Denetim bağlaşımının belirtisi bir bayrak parametresidir. hesapla(gonderi, true) çağrısını
okuyan biri true ne demek diye sormak zorundadır; cevabı çağrılanın gövdesindedir. Bayrak
kaldırıldığında bilgi çağrının kendisine geçer: kdvli(...) ile bolgeli(...) ayrı adlardır
ve hangisinin çağrıldığı çağrı yerinde okunur.
Damga bağlaşımının belirtisi, çağrılanın aldığı kaydın alanlarından yalnız bir kısmını
kullanmasıdır. hesapla(gonderi, true) gönderinin ağırlık ve adres alanlarını kullanır;
kaydın geri kalanı — değer, teslim adresi, hacim — çağrıya girer ve kullanılmaz. Bu, gönderi
kaydına eklenen her yeni alanın hesap modülünün girdi yüzeyini büyütmesi demektir.
İçerik bağlaşımının belirtisi ad kuralıdır: _KADEME adı altçizgiyle başlayarak dışarı
verilmemesi gerektiğini söylüyor, ama fatura modülü onu yine de ithal ediyor. Ölçer bunu
yakaladı; kademe tablosunun biçimi değiştiğinde fatura modülü de değişecek.
Özet
- Bağlaşım iki modül arasındaki bağın gücüdür ve üç ölçüyle sayılır: paylaşılan ad sayısı, geçirilen parametre sayısı ve ortak değiştirilen durum.
- Türler en güçlüden en zayıfa içerik, ortak, denetim, damga ve veri bağlaşımıdır; bir kenarda birden çoğu bulunabilir, etiket en güçlüsüdür.
- Aynı fatura satırını üreten üç tasarımda ölçüm sırasıyla içerik, denetim ve veri bağlaşımı verdi; yazma sayısı 2, 0 ve 0 bulundu.
- Tür ile büyüklük ayrı ölçülerdir: en zayıf türe sahip tasarım aynı zamanda en çok adı (4) ve en çok parametreyi (4) paylaşan tasarımdı.
- Paylaşılan nesnenin tek bir alanına yapılan dokunuş yalnız ortak bağlaşımlı tasarımın çıktısını 8832’den 7360’a değiştirdi; diğer ikisi etkilenmedi.
Sonraki Adım
Bağlaşım ölçüsü modüller arasındaki kenarları saydı ve modülün içine hiç bakmadı. Üçüncü tasarımın hesap modülü dört ad dışa veriyor; bu dört adın birbirine ait olup olmadığı sorusu ölçünün dışında kaldı. Aynı dosyada duran işlevler ortak bir veriye mi dokunuyor, yoksa yalnız aynı dosyada oldukları için mi yan yanalar? Sonraki ders modül içindeki işlevleri dokundukları veri kümesine göre kümeler, düşük ilgililikli bir modülü bölerek ölçümü tekrarlar ve bölmenin bağlaşım ölçüsünü nasıl etkilediğini sayar.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.