İçeriğe geç
academia.sh

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.
  • sinir yeniden 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.

Aramak için yazmaya başlayın.

↑↓ Esc gezin · aç · kapat