Ders 06 / 10
Conway Yasası
İletişim yapısının modül yapısına yansımasının ölçülmesi: aynı modül grafiğine iki ayrı ekip bölünmesinin bindirilmesi, modül başına ekip ve ekip sınırını geçen bağ sayımı, bu bağların iletişim mesafesine göre dağılımı, iletişim yolu olmayan ekipler arasında duran bağların bulunması ve aynı iş kalemi kümesinin iki bölünmedeki el değiştirme, bekleme ve yeniden iş sayıları.
İçindekiler
Önceki konu sürecin biçimini seçti, akışı sınırladı ve hata bulma gecikmesini kısalttı. Bütün o ölçümlerin ortak bir sessiz girdisi vardı: işin kimden kime geçtiği veri olarak alındı; o sınırın nereden geldiği hiç sorulmadı. Bu ders o soruyu sorar.
Conway yasası, bir sistemi üreten kuruluşun iletişim yapısı ile ürettiği sistemin yapısı arasında bir benzerlik olduğunu söyleyen terimdir. Terim tek başına ölçü vermez; ölçü iki grafik yan yana konarak kurulur — modül grafiği (hangi modül hangi modüle bağımlıdır) ve iletişim grafiği (hangi ekibin hangi ekiple düzenli bir kanalı vardır).
Aynı türden bir ithal grafiği daha önce başka amaçlarla okundu: katman kuralı denetlendi, sınır ihlali sayıldı. Buradaki sayım o değildir. Grafiğe bindirilen şey kural değil ekip sınırıdır. Through-line bölgesel kütüphane ağıdır ve kurgudur.
İki Grafik, Tek Kod Tabanı
ET1 — kod tabanı on yedi modülden ve kırk üç bağımlılık bağından oluşur. Gerekçe: örtüşme ancak bağ sayısının ekip sayısından çok büyük olduğu bir grafikte anlam taşır.
ET2 — bileşen odaklı bölünmede beş ekip vardır, her ekip bir teknik bileşenin sahibidir ve dört düzenli kanal bulunur. ET3 — akış odaklı bölünmede altı ekip vardır, her ekip bir hizmet akışının sahibidir ve dokuz kanal bulunur. Gerekçe: iki bölünme aynı on yedi modülü paylaştırır, yani modül grafiği iki koşumda da bit bit aynıdır; değişen tek şey sınırın yeridir.
ET4 — iş yükü on iki kalemdir; her kalem modülleri işin izlediği sırayla listeler ve ilk modül isteğin düştüğü yerdir. ET5 — bir modülün “değiştiren ekipleri” sahibi ile o modüle dokunan kalemleri başlatan ekiplerin birleşimidir. Gerekçe: kurumda kod herkese açıktır; kalemi başlatan ekip gerektiğinde başka ekibin modülünde değişiklik yazar ve sahibinden gözden geçirme ister.
Aşağıdaki blok bir modeldir; gerçek bir kod tabanından okunmuş değildir.
// kur.mjs — modul grafigi ile iletisim grafigi yan yana konur, ortusme olculur (model) import { writeFileSync } from "node:fs"; // ET1 — 17 modul; "a: b c" satiri a modulunun b ve c modullerini ithal ettigini soyler const ITHAL = { subeUcu: "oduncAkisi ucretAkisi rezervasyonAkisi ortakBicim", personelUcu: "uyelikAkisi oduncAkisi ucretAkisi ortakBicim", ceptenErisim: "oduncAkisi rezervasyonAkisi katalogKoprusu ortakBicim", oduncAkisi: "oduncKurali katalogKurali depoErisimi bildirimKuyrugu ortakBicim", ucretAkisi: "ucretKurali depoErisimi bildirimKuyrugu ortakBicim", uyelikAkisi: "uyelikKurali kimlikKoprusu depoErisimi ortakBicim", rezervasyonAkisi: "oduncKurali katalogKoprusu depoErisimi bildirimKuyrugu ortakBicim", oduncKurali: "ortakBicim", ucretKurali: "oduncKurali ortakBicim", katalogKurali: "ortakBicim", uyelikKurali: "ortakBicim", depoErisimi: "olayGunlugu ortakBicim", katalogKoprusu: "katalogKurali ortakBicim", kimlikKoprusu: "ortakBicim", bildirimKuyrugu: "olayGunlugu ortakBicim", olayGunlugu: "ortakBicim", ortakBicim: "", }; const M = Object.keys(ITHAL); const KENAR = Object.entries(ITHAL) .flatMap(([a, s]) => (s ? s.split(" ").map((b) => [a, b]) : [])); // ET2 — bilesen odakli bolunme: bes ekip, her ekip bir teknik bilesenin sahibi const A = { ad: "A (bilesen odakli)", bolunme: { arayuz: "subeUcu personelUcu ceptenErisim", akis: "oduncAkisi ucretAkisi uyelikAkisi rezervasyonAkisi", kural: "oduncKurali ucretKurali katalogKurali uyelikKurali", altyapi: "depoErisimi katalogKoprusu kimlikKoprusu bildirimKuyrugu olayGunlugu", ortak: "ortakBicim" }, kanal: "arayuz-akis akis-kural akis-altyapi kural-altyapi" }; // ET3 — akis odakli bolunme: alti ekip, her ekip bir hizmet akisinin sahibi const B = { ad: "B (akis odakli)", bolunme: { deneyim: "subeUcu personelUcu ceptenErisim", odunc: "oduncAkisi oduncKurali rezervasyonAkisi depoErisimi", ucret: "ucretAkisi ucretKurali", uyelik: "uyelikAkisi uyelikKurali kimlikKoprusu", katalog: "katalogKurali katalogKoprusu", platform: "ortakBicim olayGunlugu bildirimKuyrugu" }, kanal: "deneyim-odunc deneyim-ucret deneyim-uyelik odunc-ucret odunc-katalog " + "odunc-platform ucret-platform uyelik-platform katalog-platform" }; // ET4 — on iki is kalemi; moduller isin izledigi sirayla, ilki istegin dustugu yerdir const KALEM = [ ["gecikme ucreti orani", "ucretAkisi ucretKurali subeUcu ortakBicim"], ["rezervasyon iptali", "ceptenErisim rezervasyonAkisi oduncKurali bildirimKuyrugu"], ["uyelik hatirlatmasi", "uyelikAkisi uyelikKurali bildirimKuyrugu personelUcu"], ["katalog kaydinda alan", "katalogKoprusu katalogKurali ortakBicim ceptenErisim"], ["odunc suresi uzatma", "oduncAkisi oduncKurali subeUcu"], ["kimlikte ikinci adim", "kimlikKoprusu uyelikAkisi personelUcu ortakBicim"], ["ucret iadesi kaydi", "ucretAkisi depoErisimi olayGunlugu"], ["sube gecikme raporu", "personelUcu depoErisimi oduncAkisi"], ["bildirim metni bicimi", "bildirimKuyrugu ortakBicim"], ["rezervasyon sirasi", "ceptenErisim rezervasyonAkisi subeUcu"], ["odunc kurali istisnasi", "oduncKurali ucretKurali oduncAkisi ucretAkisi"], ["olay gunlugune alan", "olayGunlugu ortakBicim depoErisimi"], ]; function kur({ ad, bolunme, kanal }) { const sahip = {}, E = Object.keys(bolunme), kom = Object.fromEntries(E.map((e) => [e, []])); for (const [e, ms] of Object.entries(bolunme)) for (const m of ms.split(" ")) sahip[m] = e; for (const k of kanal.split(" ")) { const [x, y] = k.split("-"); kom[x].push(y); kom[y].push(x); } const d = {}; // iletisim grafiginde en kisa yol; yol yoksa -1 for (const s of E) { d[s] = Object.fromEntries(E.map((e) => [e, -1])); d[s][s] = 0; for (let q = [s]; q.length; ) { const u = q.shift(); for (const v of kom[u]) if (d[s][v] < 0) { d[s][v] = d[s][u] + 1; q.push(v); } } } // ET5 — kod herkese acik: kalemi baslatan ekip gerektiginde baska ekibin modulunu degistirir const deg = Object.fromEntries(M.map((m) => [m, new Set([sahip[m]])])); for (const [, ms] of KALEM) for (const m of ms.split(" ")) deg[m].add(sahip[ms.split(" ")[0]]); return { ad, bolunme, sahip, d, deg: Object.fromEntries(M.map((m) => [m, [...deg[m]]])) }; } const S = [kur(A), kur(B)]; writeFileSync("ag.json", JSON.stringify({ M, KENAR, KALEM, S })); const olc = (s) => { const dis = KENAR.filter(([a, b]) => s.sahip[a] !== s.sahip[b]); const uzak = (f) => dis.filter(([a, b]) => f(s.d[s.sahip[a]][s.sahip[b]])).length; const ek = (n) => M.filter((m) => s.deg[m].length >= n).length; return { "ekip sinirini gecen modul bagi": `${dis.length}/${KENAR.length}`, " iletisim mesafesi 1 olan ekipler arasi": uzak((u) => u === 1), " iletisim mesafesi 2 olan ekipler arasi": uzak((u) => u === 2), " ILETISIM YOLU OLMAYAN ekipler arasi": uzak((u) => u < 0), "birden cok ekibin degistirdigi modul": ek(2), " uc ve daha cok ekibin degistirdigi modul": ek(3), "modul basina ekip ortalamasi": (M.reduce((t, m) => t + s.deg[m].length, 0) / M.length).toFixed(2), }; }; const O = S.map(olc); console.log(`${M.length} modul, ${KENAR.length} bag, ${KALEM.length} is kalemi\n`); console.log(`${"olcu".padEnd(42)}${S.map((s) => s.ad.padStart(20)).join("")}`); for (const k of Object.keys(O[0])) console.log(`${k.padEnd(42)}${O.map((o) => String(o[k]).padStart(20)).join("")}`); for (const s of S) { const c = {}; for (const [a, b] of KENAR) if (s.d[s.sahip[a]][s.sahip[b]] < 0) { const k = `${s.sahip[a]}->${s.sahip[b]}`; c[k] = (c[k] ?? 0) + 1; } console.log(`\n${s.ad} — uc ve daha cok ekibin degistirdigi: ` + M.filter((m) => s.deg[m].length >= 3).map((m) => `${m}(${s.deg[m].length})`).join(", ")); console.log(` iletisim yolu olmayan ekipler arasinda duran bag: ` + (Object.keys(c).length ? Object.entries(c).map(([k, n]) => `${k} ${n}`).join(", ") : "yok")); }
17 modul, 43 bag, 12 is kalemi olcu A (bilesen odakli) B (akis odakli) ekip sinirini gecen modul bagi 40/43 32/43 iletisim mesafesi 1 olan ekipler arasi 23 27 iletisim mesafesi 2 olan ekipler arasi 1 5 ILETISIM YOLU OLMAYAN ekipler arasi 16 0 birden cok ekibin degistirdigi modul 15 12 uc ve daha cok ekibin degistirdigi modul 6 4 modul basina ekip ortalamasi 2.24 2.06 A (bilesen odakli) — uc ve daha cok ekibin degistirdigi: personelUcu(3), oduncAkisi(3), oduncKurali(3), depoErisimi(3), bildirimKuyrugu(3), ortakBicim(3) iletisim yolu olmayan ekipler arasinda duran bag: arayuz->ortak 3, akis->ortak 4, kural->ortak 4, altyapi->ortak 5 B (akis odakli) — uc ve daha cok ekibin degistirdigi: subeUcu(3), depoErisimi(4), bildirimKuyrugu(3), ortakBicim(4) iletisim yolu olmayan ekipler arasinda duran bag: yok
Örtüşme Nerede Bozulur
Tablonun en keskin satırı ortadadır. Bileşen odaklı bölünmede on altı modül bağı, iletişim grafiğinde birbirine hiç bağlı olmayan iki ekip arasında durur — kırk üç bağın üçte birinden fazlası; akış odaklı bölünmede sayı sıfırdır.
Son çıktı satırları bu on altı bağın nereden geldiğini adlandırır: hepsi ortak ekibi ile diğer
dört ekip arasındadır. ortak tek modülün — ortakBicim — sahibidir ve hiçbir iletişim kanalı
yoktur; istekler ona yazılı bir sıradan ulaşır. Modül grafiğinde ise ortakBicim on beş modül
tarafından ithal edilir. Kod tabanının en çok bağımlı olunan modülü, kimsenin konuşmadığı ekibin
elindedir. İki grafik örtüşmediğinde modül grafiği kazanır: bağımlılık kodda durmaya devam eder ve
eksik kanal kendini o bağdan geçen her kalemin beklemesiyle gösterir.
Akış odaklı bölünme bu sayıyı sıfıra indirir ama başkasını büyütür. Bölünme A’da hiçbir modül üç
ekibi aşmaz; B’de depoErisimi ve ortakBicim dört ekip tarafından değiştirilir, çünkü akış odaklı
ekipler kalemlerini uçtan uca bitirmek için ortak modüllere doğrudan girer. B’nin ortalaması daha
iyidir (2.06 karşı 2.24) ama en kötü modülü daha kötüdür.
Aynı İşin İki Bölünmedeki Akışı
Bedeli görmek için aynı on iki kalem iki bölünmede koşturulur.
ET6 — bir kalem dokunduğu her modül için bir tur çalışır; iki ekip arasındaki her geçiş, iletişim mesafesi 1 ise 1, mesafe 2 ise 3, mesafe 3 ise 5, yol yoksa 8 tur bekler. Gerekçe: doğrudan kanalı olan iki ekip aynı turda konuşur; aracıya düşen her adım bir kuyruk daha bekletir.
ET7 — üç yeniden iş nedeni sayılır: mesafesi 1’den büyük bir geçiş varsa kisit (geç öğrenilen
kısıt), kalem üç ve daha çok ekibin değiştirdiği bir modüle dokunuyorsa sinir (yanlış sınır),
kalem dört ve daha çok ekibe dağılıyorsa bilgi (eksik bilgi); her yeniden iş 2 tur çalışma ekler
ve en uzun beklemeyi bir kez daha ödetir. Gerekçe: üç neden üç ayrı yapısal kusuru işaret eder —
kanal yokluğu, sahiplik belirsizliği, kapsamın yayılması.
// akis.mjs — ayni on iki is kalemi iki bolunmede kosturulur: el degistirme, bekleme, yeniden is import { readFileSync } from "node:fs"; const { KALEM, S } = JSON.parse(readFileSync("ag.json", "utf8")); const BEKLE = [0, 1, 3, 5], YOK = 8; // ET6: iletisim mesafesine gore bekleme turu const kosum = (s) => KALEM.map(([ad, dizi]) => { const ms = dizi.split(" "); const sira = [...new Set(ms.map((m) => s.sahip[m]))]; // ekipler, isin izledigi sirayla let bek = 0, enUzun = 0, kopuk = 0; for (let j = 1; j < sira.length; j++) { const u = s.d[sira[j - 1]][sira[j]], b = u < 0 ? YOK : BEKLE[u]; bek += b; enUzun = Math.max(enUzun, b); if (u < 0 || u >= 2) kopuk += 1; } const neden = []; // ET7: uc yeniden is nedeni if (kopuk) neden.push("kisit"); // gec ogrenilen kisit if (ms.some((m) => s.deg[m].length >= 3)) neden.push("sinir"); // yanlis sinir if (sira.length >= 4) neden.push("bilgi"); // eksik bilgi const cal = ms.length + neden.length * 2; // her yeniden is 2 tur calisma ekler const bekT = bek + neden.length * enUzun; // ve en uzun beklemeyi bir kez daha odetir return { ad, ekip: sira.length, eld: sira.length - 1, cal, bek: bekT, akis: cal + bekT, neden }; }); const R = S.map((s) => ({ ad: s.ad, k: kosum(s) })); const BAS = ["ekip", "eld", "bek", "akis"]; const hucre = (r) => [r.ekip, r.eld, r.bek, r.akis].map((x) => String(x).padStart(6)).join("") + ` ${(r.neden.join("+") || "-").padEnd(17)}`; console.log(`${"is kalemi".padEnd(24)}${R.map((r) => r.ad.padEnd(43)).join("")}`.trimEnd()); console.log(`${"".padEnd(24)}${R.map(() => BAS.map((h) => h.padStart(6)).join("") + ` ${"neden".padEnd(17)}`).join("")}`.trimEnd()); for (let i = 0; i < KALEM.length; i++) console.log(`${R[0].k[i].ad.padEnd(24)}${R.map((r) => hucre(r.k[i])).join("")}`.trimEnd()); const ozet = (r) => { const T = (f) => r.k.reduce((t, x) => t + f(x), 0); const n = (c) => r.k.filter((x) => x.neden.includes(c)).length; return { "el degistirme": T((x) => x.eld), "bekleme turu": T((x) => x.bek), "akis suresi (tur)": T((x) => x.akis), "akis suresinin bekleme payi": `%${(100 * T((x) => x.bek) / T((x) => x.akis)).toFixed(1)}`, "yeniden is (kalem / toplam)": `${r.k.filter((x) => x.neden.length).length} / ${T((x) => x.neden.length)}`, " neden: gec ogrenilen kisit": n("kisit"), " neden: yanlis sinir": n("sinir"), " neden: eksik bilgi": n("bilgi"), }; }; const Z = R.map(ozet); console.log(`\n${"toplam".padEnd(40)}${R.map((r) => r.ad.padStart(20)).join("")}`); for (const k of Object.keys(Z[0])) console.log(`${k.padEnd(40)}${Z.map((z) => String(z[k]).padStart(20)).join("")}`);
is kalemi A (bilesen odakli) B (akis odakli)
ekip eld bek akis neden ekip eld bek akis neden
gecikme ucreti orani 4 3 36 46 kisit+sinir+bilgi 3 2 10 18 kisit+sinir
rezervasyon iptali 4 3 5 13 sinir+bilgi 3 2 3 9 sinir
uyelik hatirlatmasi 4 3 14 24 kisit+sinir+bilgi 3 2 10 18 kisit+sinir
katalog kaydinda alan 4 3 41 51 kisit+sinir+bilgi 3 2 10 18 kisit+sinir
odunc suresi uzatma 3 2 10 17 kisit+sinir 2 1 2 7 sinir
kimlikte ikinci adim 4 3 34 44 kisit+sinir+bilgi 3 2 10 18 kisit+sinir
ucret iadesi kaydi 2 1 2 7 sinir 3 2 3 8 sinir
sube gecikme raporu 3 2 10 17 kisit+sinir 2 1 2 7 sinir
bildirim metni bicimi 2 1 24 30 kisit+sinir 1 0 0 4 sinir
rezervasyon sirasi 2 1 1 4 - 2 1 2 7 sinir
odunc kurali istisnasi 2 1 2 8 sinir 2 1 1 5 -
olay gunlugune alan 2 1 24 31 kisit+sinir 2 1 2 7 sinir
toplam A (bilesen odakli) B (akis odakli)
el degistirme 24 17
bekleme turu 203 55
akis suresi (tur) 292 126
akis suresinin bekleme payi %69.5 %43.7
yeniden is (kalem / toplam) 11 / 24 11 / 15
neden: gec ogrenilen kisit 8 4
neden: yanlis sinir 11 11
neden: eksik bilgi 5 0
Bölünme Değiştiğinde Ne Değişmedi
Toplam tabloda üç sayı belirgin biçimde düşer ve bir sayı hiç kıpırdamaz.
Düşenler: el değiştirme 24’ten 17’ye, bekleme 203 turdan 55 tura, akış süresi 292’den 126’ya iner;
bekleme payı %69.5’ten %43.7’ye düşer. Önemli olan orantısızlıktır: el değiştirme yalnız %29
azalırken bekleme %73 azalır. Bedelin çoğu el değiştirmenin sayısında değil, el değiştirilen
ekibin uzaklığındadır. Bölünme A’da “bildirim metni biçimi” yalnız iki ekibe dokunur ve tek el
değiştirme içerir, ama o el değiştirme ortak ekibine yapıldığı için kalem otuz tura ulaşır —
dört ekibe dağılan “rezervasyon iptali” kaleminin iki katından fazla.
Kıpırdamayan sayı sinir nedenidir: iki bölünmede de on bir kalem bu nedenle yeniden işe girer.
A’da altı modül üç ekip tarafından değiştirilir; B’de bu sayı dörde iner ama iki modül dört ekibe
çıkar ve o ikisi (depoErisimi, ortakBicim) kalemlerin çoğunda görünür. Ekip
sınırını taşımak paylaşılan modülü ortadan kaldırmaz; yalnız kimin beklediğini değiştirir. Buna
karşılık bilgi nedeni 5’ten 0’a iner ve kisit 8’den 4’e düşer ama sıfırlanmaz; kalan dördü
platform ile deneyim arasındaki iki adımlık mesafeden gelir.
İki gözlem birlikte dersin sonucudur. İletişim yapısını değiştirmek modül grafiğine hiç dokunmadan akış süresini iki kattan fazla kısaltabilir; ama ortak bağımlılığı yalnız iletişim yapısıyla çözmek mümkün değildir. Örtüşmeyi kurmanın iki yönü vardır ve bir bölünme kararı yalnız birini kullanır.
Özet
- Aynı 17 modüllü ve 43 bağlı grafiğe iki bölünme bindirildi: bileşen odaklı 5 ekip (4 kanal) ve akış odaklı 6 ekip (9 kanal); modül grafiği iki koşumda da değişmedi.
- Bileşen odaklı bölünmede 40/43 bağ ekip sınırını geçer ve bunların 16’sı iletişim grafiğinde hiç yolu olmayan ekipler arasında durur; akış odaklıda sınırı geçen bağ 32’ye iner ve yolu olmayan ekip çifti kalmaz.
- On altı kopuk bağın hepsi tek modülün etrafındadır: 15 modülün ithal ettiği
ortakBicim, hiçbir kanalı olmayan tek modüllük bir ekibin elindedir. - Aynı 12 iş kalemi iki bölünmede koşturulduğunda el değiştirme 24’ten 17’ye, bekleme 203 turdan 55 tura, akış süresi 292’den 126’ya iner; bekleme payı %69.5’ten %43.7’ye düşer.
- Bekleme %73, el değiştirme yalnız %29 azalır: bedel el değiştirmenin sayısında değil, karşı ekibin iletişim grafiğindeki uzaklığındadır.
siniryeniden iş nedeni iki bölünmede de 11 kalemde görülür; akış odaklı bölünme en çok paylaşılan modülü 3 ekipten 4 ekibe çıkarır. Paylaşılan modüldeki çekişme ekip sınırı taşınarak yok edilemez.
Sonraki Adım
Bu dersin ikinci bölünmesi “akış odaklı” adıyla anıldı ama yalnız tek bir özelliğiyle kullanıldı: her ekip bir hizmet akışının sahibidir. Sayılar bu özelliğin akış süresini iki kattan fazla kısalttığını gösterdi — ve bir şeyi hiç ölçmedi: o ekiplerin ne kadar yük taşıdığını. Sonraki ders bu yükü sayar: aynı iş kalemi kümesi üç ayrı ekip düzeninde koşturulur ve el değiştirme, bekleme, yeniden iş sayılarının yanına ekip başına bilişsel yük eklenir — sahip olunan modül ve bağımlı olunan dış arayüz sayısı. Akış odaklı düzenin kazandığı yer bu dersin tablosunda görüldü; kaybettiği yer orada görülecek.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.