Ders 07 / 11
Basitleştirmek
Karmaşıklığın bilinçli azaltılması: arızi ile özsel karmaşıklığın ayrılması, bir çözümün modül grafiğindeki toplam karmaşıklığın gereksinim grafiğinden okunan özsel tabana göre iki bileşene bölünmesi, sadeleştirmenin kaldırdığı arızi payın ve kaldıramadığı payın sayılması ve aynı değişiklik kümesi iki grafikte koşturularak sadeleştirmenin karşılığında büyüyen şeyin ölçülmesi.
İçindekiler
Önceki ders kararın hangi alternatife gittiğini ölçtü. Seçilen alternatif ortaya bir parça kümesi çıkarır: bir önbellek katmanı, bir eşitleme yolu, bir geçersizleştirme kuralı. Zamanla bu parçalar birikir ve sistem “karmaşık” diye anılmaya başlar. Karmaşıklık bir sıfat olarak kullanıldığı sürece üzerine karar verilemez, çünkü azaltma isteği ile azaltma imkânı arasındaki farkı göstermez.
Bu dersin sorusu şudur: bir çözümdeki karmaşıklığın ne kadarı kaldırılabilir, ne kadarı işin kendisinden gelir? Ayrım yapılmadan girişilen sadeleştirme iki biçimde başarısız olur — ya gereken bir şeyi siler, ya da hiçbir şeyi silmez ve yalnızca yeniden düzenler. Bağlaşım ve bütünlük ölçüleri önkoşul tasarım kurslarında kuruldu; burada tekrarlanmaz. Ölçülecek olan, karmaşıklığın iki bileşene bölünmesidir.
Arızi ve Özsel Karmaşıklık
Özsel karmaşıklık (essential complexity), çözülen işin kendisinden gelen ve hiçbir gerçekleştirimde ortadan kalkmayan paydır. Ödünç verilen bir kitabın iade tarihinin olması, gecikmenin bir bedele dönüşmesi, uzatmanın hem ödünç kaydına hem bedele bağlı olması — bunlar kütüphane ağının kurallarından gelir. Kod nasıl yazılırsa yazılsın bu bağlar durur.
Arızi karmaşıklık (accidental complexity), çözümün kurulma biçiminden gelen paydır. Aynı işi karşılayan ikinci bir modül, hiçbir davranışı karşılamadığı halde araya girmiş bir sarmalayıcı, tek bir ayarı okumak için kurulmuş bir katman. Bunlar işin değil, çözümün özellikleridir.
Ayrım kulağa açık gelir ama pratikte tartışmaya açıktır, çünkü herkes kendi eklediği katmanı özsel sayar. Tartışmayı kapatan tek şey ayrımı bir ölçüye bağlamaktır: özsel pay çözümden değil, gereksinimden okunur. Aynı davranış kümesini karşılayan herhangi bir çözümün inebileceği en alt sınır, gereksinimlerin kendi grafiğidir.
İki Grafik: Çözüm ve Gereksinim
Model iki grafik tutar. Birincisi kütüphane ağı kurallarının dayattığı davranışlar ve aralarındaki zorunlu bağlardır. İkincisi içeride yazılmış ödünç servislerinin bugünkü modül grafiğidir. Kurgu bir modeldir; gerçek bir kurum ya da ürün anlatılmaz.
// sade/grafik.mjs — odunc servislerinin modul grafigi ve gereksinim grafigi; kurgu bir modeldir // GEREKSINIM: kutuphane agi kurallarinin dayattigi davranislar ve aralarindaki zorunlu bag (MP4) export const GEREKSINIM = { r1: { ad: "odunc-ver", baglar: ["r7"] }, r2: { ad: "iade-al", baglar: ["r4"] }, r3: { ad: "uzatma", baglar: ["r1", "r4"] }, r4: { ad: "gecikme-bedeli", baglar: [] }, r5: { ad: "ayirtma", baglar: [] }, r6: { ad: "subeler-arasi-istek", baglar: ["r5"] }, r7: { ad: "uyelik-dogrulama", baglar: [] }, r8: { ad: "ceza-affi", baglar: ["r4"] }, r9: { ad: "raf-durumu-bildirimi", baglar: [] }, }; // MODUL: cozumun bugunku hali. ayarOkur = sube basina degisen ayari dogrudan okuyan modul (MP5) export const MODUL = [ { ad: "odunc-akisi", ihtiyac: ["r1"], ayarOkur: false }, { ad: "iade-akisi", ihtiyac: ["r2"], ayarOkur: false }, { ad: "uzatma-akisi", ihtiyac: ["r3"], ayarOkur: false }, { ad: "bedel-hesabi", ihtiyac: ["r4"], ayarOkur: false }, { ad: "ayirtma-akisi", ihtiyac: ["r5"], ayarOkur: false }, { ad: "subeler-arasi", ihtiyac: ["r6"], ayarOkur: false }, { ad: "uyelik-kapisi", ihtiyac: ["r7"], ayarOkur: false }, { ad: "af-akisi", ihtiyac: ["r8"], ayarOkur: false }, { ad: "raf-bildirimi", ihtiyac: ["r9"], ayarOkur: false }, { ad: "odunc-yardimcisi", ihtiyac: ["r1"], ayarOkur: false }, { ad: "iade-yardimcisi", ihtiyac: ["r2"], ayarOkur: false }, { ad: "kayit-koprusu", ihtiyac: [], ayarOkur: false }, { ad: "olay-koprusu", ihtiyac: [], ayarOkur: false }, { ad: "ayar-sarmalayici", ihtiyac: [], ayarOkur: true }, ]; export const BAG = [ ["odunc-akisi", "odunc-yardimcisi"], ["odunc-akisi", "uyelik-kapisi"], ["odunc-akisi", "kayit-koprusu"], ["odunc-akisi", "ayar-sarmalayici"], ["iade-akisi", "iade-yardimcisi"], ["iade-akisi", "bedel-hesabi"], ["iade-akisi", "kayit-koprusu"], ["iade-akisi", "ayar-sarmalayici"], ["uzatma-akisi", "odunc-yardimcisi"], ["uzatma-akisi", "bedel-hesabi"], ["uzatma-akisi", "ayar-sarmalayici"], ["bedel-hesabi", "kayit-koprusu"], ["bedel-hesabi", "ayar-sarmalayici"], ["ayirtma-akisi", "kayit-koprusu"], ["ayirtma-akisi", "olay-koprusu"], ["ayirtma-akisi", "ayar-sarmalayici"], ["subeler-arasi", "ayirtma-akisi"], ["subeler-arasi", "olay-koprusu"], ["subeler-arasi", "ayar-sarmalayici"], ["af-akisi", "bedel-hesabi"], ["af-akisi", "kayit-koprusu"], ["raf-bildirimi", "olay-koprusu"], ["uyelik-kapisi", "kayit-koprusu"], ]; export const kopya = () => ({ modul: MODUL.map((m) => ({ ...m, ihtiyac: [...m.ihtiyac] })), bag: BAG.map((b) => [...b]), }); export const olcu = (g) => g.modul.length + g.bag.length;
Karmaşıklık ölçüsü kasten kaba tutuldu: modül sayısı artı bağ sayısı. Ölçünün inceliği burada önemli değildir, çünkü ölçülen şey mutlak bir büyüklük değil, iki grafik arasındaki farktır.
Sadeleştirme iki adımdan oluşur ve ikisi de tek bir kurala dayanır: bir modül ancak hiçbir davranışı ayırt etmiyorsa kaldırılabilir.
// sade/sadelestir.mjs — iki sadelestirme adimi: ayni gereksinimi karsilayan modulu birlestir, // hicbir gereksinimi karsilamayan sarmalayiciyi kaldir (isi cagiranlarina gecer) function birlestir(g, at, kalan) { g.bag = g.bag.map(([a, b]) => [a === at ? kalan : a, b === at ? kalan : b]) .filter(([a, b]) => a !== b); const k = g.modul.find((m) => m.ad === kalan); k.ayarOkur ||= g.modul.find((m) => m.ad === at).ayarOkur; g.modul = g.modul.filter((m) => m.ad !== at); } function kaldir(g, at) { const onceki = g.bag.filter(([, b]) => b === at).map(([a]) => a); const sonraki = g.bag.filter(([a]) => a === at).map(([, b]) => b); const okur = g.modul.find((m) => m.ad === at).ayarOkur; g.bag = g.bag.filter(([a, b]) => a !== at && b !== at); for (const o of onceki) { if (okur) g.modul.find((m) => m.ad === o).ayarOkur = true; for (const s of sonraki) if (o !== s && !g.bag.some(([a, b]) => a === o && b === s)) g.bag.push([o, s]); } g.modul = g.modul.filter((m) => m.ad !== at); } export function sadelestir(g) { birlestir(g, "odunc-yardimcisi", "odunc-akisi"); birlestir(g, "iade-yardimcisi", "iade-akisi"); kaldir(g, "ayar-sarmalayici"); return g; }
kaldir işlevindeki tek satır dersin yarısını taşıyor: kaldırılan modül ayarOkur ise, bu
nitelik onu çağıran modüllere geçer. Sarmalayıcı kaldırıldığında yaptığı iş yok olmaz, dağılır.
Ayrımın Ölçülmesi
// sade/ayir.mjs — toplam karmasikligi ozsel ve arizi bilesene ayirir, sonra sadelestirmeyi kosturur import { GEREKSINIM, kopya, olcu } from "./grafik.mjs"; import { sadelestir } from "./sadelestir.mjs"; const g = kopya(); const toplam = olcu(g); const ozselModul = Object.keys(GEREKSINIM).length; const ozselBag = Object.values(GEREKSINIM).reduce((t, r) => t + r.baglar.length, 0); const ozsel = ozselModul + ozselBag; console.log(`modul grafigi : ${g.modul.length} modul + ${g.bag.length} bag = ${toplam}`); console.log(`gereksinim grafigi: ${ozselModul} davranis + ${ozselBag} zorunlu bag = ${ozsel}`); console.log(`ozsel = ${ozsel}, arizi = ${toplam - ozsel} (%${(100 * (toplam - ozsel) / toplam).toFixed(1)})`); sadelestir(g); const sade = olcu(g); console.log(`\nsadelestirmeden sonra: ${g.modul.length} modul + ${g.bag.length} bag = ${sade}`); console.log(`kaldirilan arizi pay : ${toplam - sade}`); console.log(`kalan arizi pay : ${sade - ozsel}`); console.log(`inilemeyen taban : ${ozsel} (ozsel)`); console.log(`ayari dogrudan okuyan modul: once 1, sonra ${g.modul.filter((m) => m.ayarOkur).length}`);
modul grafigi : 14 modul + 23 bag = 37 gereksinim grafigi: 9 davranis + 6 zorunlu bag = 15 ozsel = 15, arizi = 22 (%59.5) sadelestirmeden sonra: 11 modul + 15 bag = 26 kaldirilan arizi pay : 11 kalan arizi pay : 11 inilemeyen taban : 15 (ozsel) ayari dogrudan okuyan modul: once 1, sonra 6
Toplam karmaşıklık 37; bunun 15’i özsel, 22’si arızidir. Yani çözümün yarısından fazlası gereksinimden değil, çözümün kurulma biçiminden geliyor. Bu oran tek başına bir eylem çağrısı değildir; ayrımın ne işe yaradığını gösteren şey sonraki satırlardır.
Sadeleştirme 37’yi 26’ya indirdi, yani 22’lik arızi paydan 11’ini kaldırdı. Kalan 11 de arızidir
ama kaldırılamadı: kayit-koprusu ve olay-koprusu hiçbir davranışı karşılamıyor, buna karşılık
gerçek iş yapıyorlar — biri kayıt yazımını, öteki olay yayımını tek yerde topluyor. Onları
kaldırmak işi ortadan kaldırmaz, altı ayrı modüle kopyalar. Arızi olmak kaldırılabilir olmakla
aynı şey değildir.
Son satır ise sadeleştirmenin bedelidir. Şube başına değişen ayarı önce tek bir modül okuyordu; sarmalayıcı kaldırıldıktan sonra altı modül okuyor. Karmaşıklık ölçüsü 11 azaldı, ama bir niteliğin dağıldığı yer altı katına çıktı. Ölçü bunu görmez; bir sonraki koşum görür.
Sadeleştirmenin Karşılığı
Karşılaştırma aynı değişiklik kümesini iki grafikte koşturur. Değişiklikler önümüzdeki on iki ayda beklenenlerdir (MP6) ve iki türdür: bir davranışın kuralını değiştirenler, ve bir kuralı şube başına farklılaştıran yayılan değişiklik.
// sade/degisiklik.mjs — ayni degisiklik kumesi iki grafikte: dokunulan modul sayisi import { kopya } from "./grafik.mjs"; import { sadelestir } from "./sadelestir.mjs"; // MP6: on iki ayda beklenen degisiklikler. "yayilan" = sube basina farklilasan kural. const DEGISIKLIK = [ { ad: "gecikme bedeli orani", tur: "gereksinim", hedef: "r4" }, { ad: "uzatma hakki sayisi", tur: "gereksinim", hedef: "r3" }, { ad: "ayirtma suresi", tur: "gereksinim", hedef: "r5" }, { ad: "odunc verme kurali", tur: "gereksinim", hedef: "r1" }, { ad: "iade akisina yeni adim", tur: "gereksinim", hedef: "r2" }, { ad: "sube basina gecikme bedeli", tur: "yayilan", hedef: null }, ]; function dokunulan(g, d) { if (d.tur === "yayilan") return g.modul.filter((m) => m.ayarOkur).map((m) => m.ad); const tasiyan = g.modul.filter((m) => m.ihtiyac.includes(d.hedef)).map((m) => m.ad); const cagiran = g.bag.filter(([, b]) => tasiyan.includes(b)).map(([a]) => a); return [...new Set([...tasiyan, ...cagiran])]; } const grafik = { ozgun: kopya(), sade: sadelestir(kopya()) }; console.log("degisiklik | ozgun | sade | fark"); console.log("----------------------------|-------|------|-----"); const toplam = { ozgun: 0, sade: 0 }; for (const d of DEGISIKLIK) { const o = dokunulan(grafik.ozgun, d).length, s = dokunulan(grafik.sade, d).length; toplam.ozgun += o; toplam.sade += s; const fark = s - o; console.log(`${d.ad.padEnd(27)} | ${String(o).padStart(5)} | ${String(s).padStart(4)} | ` + `${fark > 0 ? "+" : ""}${fark}`); } console.log("----------------------------|-------|------|-----"); console.log(`${"toplam dokunulan modul".padEnd(27)} | ${String(toplam.ozgun).padStart(5)} | ` + `${String(toplam.sade).padStart(4)} | ${toplam.sade - toplam.ozgun}`); const y = DEGISIKLIK.filter((d) => d.tur === "yayilan"); console.log(`bilinen ${DEGISIKLIK.length - y.length} degisiklikte sade grafik ` + `${toplam.ozgun - dokunulan(grafik.ozgun, y[0]).length - (toplam.sade - dokunulan(grafik.sade, y[0]).length)} modul daha az dokundu`); console.log(`yayilan 1 degisiklikte sade grafik ` + `${dokunulan(grafik.sade, y[0]).length - dokunulan(grafik.ozgun, y[0]).length} modul daha fazla dokundu`);
degisiklik | ozgun | sade | fark ----------------------------|-------|------|----- gecikme bedeli orani | 4 | 4 | 0 uzatma hakki sayisi | 1 | 1 | 0 ayirtma suresi | 2 | 2 | 0 odunc verme kurali | 3 | 2 | -1 iade akisina yeni adim | 2 | 1 | -1 sube basina gecikme bedeli | 1 | 6 | +5 ----------------------------|-------|------|----- toplam dokunulan modul | 13 | 16 | 3 bilinen 5 degisiklikte sade grafik 2 modul daha az dokundu yayilan 1 degisiklikte sade grafik 5 modul daha fazla dokundu
Sadeleştirme bilinen beş değişiklikte 2 modül kazandırdı; ikinci modülü kaldırılan iki davranışta değişiklik tek yere düştü, geri kalan üçünde hiçbir şey değişmedi. Yayılan tek değişiklikte ise 5 modül kaybettirdi. Toplamda dokunulan modül sayısı 13’ten 16’ya çıktı.
Bu, sadeleştirmenin yanlış olduğu anlamına gelmez; sadeleştirmenin bir fiyatı olduğu anlamına gelir. Kaldırılan sarmalayıcı gerçekten arızi bir katmandı — hiçbir davranışı ayırt etmiyordu — ama bir esneklik noktası tutuyordu. O nokta silindiğinde esneklik yok olmadı, altı modüle dağıldı. Sadeleştirmenin karşılığında büyüyen şey budur: bir değişikliğin geçmesi gereken yer sayısı.
Karar bu yüzden ölçüye değil, ölçülerin karşılaştırılmasına dayanır. Yayılan değişikliğin gerçekten geleceğine inanılıyorsa sarmalayıcı arızi olmasına rağmen tutulur; gelmeyeceğine inanılıyorsa kaldırılır ve geldiği gün 6 modüle dokunma maliyeti kabul edilmiş olur. İkisi de savunulabilir; savunulamayan tek şey bu sayıyı hiç hesaplamadan katman eklemek ya da silmektir.
Özet
- Özsel karmaşıklık işin kendisinden gelir ve gereksinim grafiğinden okunur; arızi karmaşıklık çözümün kurulma biçiminden gelir ve toplamdan özsel payın çıkarılmasıyla bulunur.
- Modelde toplam karmaşıklık 37 (14 modül + 23 bağ), özsel taban 15 (9 davranış + 6 zorunlu bağ), arızi pay 22 — yani toplamın yüzde 59,5’i.
- Sadeleştirme arızi paydan 11’ini kaldırdı ve toplamı 26’ya indirdi; kalan 11 de arızidir ama kaldırılamaz, çünkü iki köprü modülü hiçbir davranışı karşılamadan gerçek iş yapıyor.
- Arızi olmak kaldırılabilir olmakla aynı şey değildir; 15’lik özsel taban hiçbir sadeleştirmenin altına inemeyeceği sınırdır.
- Aynı altı değişiklik iki grafikte koşturulunca sade grafik bilinen beş değişiklikte 2 modül kazandırdı, yayılan tek değişiklikte 5 modül kaybettirdi; toplam 13’ten 16’ya çıktı.
- Sadeleştirmenin karşılığında büyüyen şey, bir değişikliğin geçmesi gereken yer sayısıdır.
Sonraki Adım
Sadeleştirme kararının fiyatı hesaplandı, ama bir şey hesaplanmadı: sarmalayıcı kaldırıldıktan sonra geri getirilebilir mi? Altı modüle dağılmış ayar okumasını tek noktaya toplamak, sarmalayıcıyı kaldırmakla aynı büyüklükte bir iş değildir. Kararların bir kısmı böyledir — verildikten sonra geri alınması, verilmesinden kat kat pahalıdır; bir kısmı ise ertesi gün geri alınabilir. İki sınıf aynı yöntemle ele alındığında iki hata birden yapılır: geri alınabilir kararlar için haftalar harcanır, geri alınamaz kararlar bir toplantıda verilir. Sonraki ders aynı karar kümesinde geri alma maliyetini hesaplar, kararları iki sınıfa ayırır ve iki karar yöntemini her iki sınıfta koşturarak bekleme süresi ile yanlış karar maliyetini karşılaştırır.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.