İçeriğe geç
academia.sh

Ders 05 / 10

Sürdürülebilirlik Ölçütleri

Değişim maliyetinin doğrudan gözlenmesi: aynı on iki değişikliğin iki yapıda koşturulup değişiklik başına dokunulan dosya, dokunulan modül ve yayılma derinliğinin sayılması, altı çeyreklik bozulmada ölçütlerin bozulmayı gösterip göstermediğinin sınanması ve eşik kuralının gerçek maliyet kaydında yakaladığı, kaçırdığı, boşa öttüğü durumlar.

İçindekiler

Bu konudaki her ölçü bir değişiklik önerdi. Nitelik ağacı eksik denetimleri gösterdi, gerilim matrisi hangi kararın neyi bastırdığını, güven sınırı grafiği hangi geçişin denetimsiz olduğunu, ölçek birimi neyin kopyalanacağını. Hepsinin ortak varsayımı, sistemin değiştirilebilir olduğuydu ve bu varsayım hiç ölçülmedi. Sürdürülebilirlik ağaçta bir kök nitelikti, gerilim matrisinde en çok bastırılan nitelikti; ama iki yerde de yaprak değerlerinden dolaylı olarak okundu. Bu ders onu doğrudan ölçer: aynı değişiklik kümesi iki ayrı yapı üzerinde koşturulur.

Teknik borcun faizi Mimari Karar ve Dokümantasyon kursunda, karar kaydı konusunda ölçüldü ve burada tekrarlanmaz. Değişim maliyeti, o faizin gözlenen yüzüdür — faiz bir hesap kalemi olarak değil, her değişiklikte fazladan dokunulan dosya olarak görünür. Ölçülecek üç büyüklük var: değişiklik başına dokunulan dosya, dokunulan modül ve değişikliğin kaç adım öteye yayıldığı.

İki Yapıda Aynı Değişiklik

Aşağıdaki blok iki yapıyı modelliyor. Modül kümesi ikisinde de aynı; ayrılan iki şey var — bağımlılık grafiği ve kavramların modüllere yerleşimi. Aynı on iki değişiklik isteği iki yapıda da koşturuluyor. Yüzeyi değiştiren bir istek, dokunulan modüle bağımlı modülleri de dokundurur.

// yapi.mjs — ayni degisiklik kumesini iki yapida kosturur ve degisim maliyetini olcer
import { mkdirSync, writeFileSync } from "node:fs";

// KN25: kurgusal bolgesel kutuphane aginin ayni islevi tasiyan iki yapisi. Modul kumesi ayni,
// bagimlilik grafigi ve kavramlarin yerlesimi farkli. Model.
const MODUL = ["odunc", "ucret", "uyelik", "katalog", "bildirim", "rapor", "sube", "ortak"];
const BAGIMLILIK = {
  A: [["odunc", "ortak"], ["odunc", "ucret"], ["odunc", "uyelik"], ["ucret", "ortak"],
    ["ucret", "uyelik"], ["uyelik", "ortak"], ["katalog", "ortak"], ["bildirim", "odunc"],
    ["bildirim", "uyelik"], ["rapor", "odunc"], ["rapor", "ucret"], ["rapor", "uyelik"],
    ["sube", "odunc"], ["sube", "ucret"], ["sube", "katalog"]],
  B: [["odunc", "ortak"], ["ucret", "ortak"], ["uyelik", "ortak"], ["katalog", "ortak"],
    ["bildirim", "ortak"], ["rapor", "ortak"], ["sube", "ortak"], ["rapor", "odunc"],
    ["sube", "odunc"]],
};

// KN26: on iki degisiklik istegi. Her istek bir kavrama dokunur; kavramin hangi modulde kac
// dosyada gectigi yapiya gore degisir. yuzey = degisiklik modulun disa acik yuzeyini
// degistiriyor mu (ayni istek icin iki yapida da ayni). Model.
const DEGISIKLIK = [
  ["ucret tarifesi", true, { ucret: 2, odunc: 1, sube: 1, rapor: 1 }, { ucret: 2 }],
  ["gecikme esigi", false, { ucret: 1, bildirim: 1, odunc: 1 }, { ucret: 1 }],
  ["uye alani", true, { uyelik: 2, odunc: 1, rapor: 1, sube: 1 }, { uyelik: 2 }],
  ["odunc suresi", false, { odunc: 2, ucret: 1, bildirim: 1 }, { odunc: 2 }],
  ["rezervasyon kurali", true, { odunc: 2, sube: 1 }, { odunc: 2 }],
  ["katalog alani", true, { katalog: 2, sube: 1, rapor: 1 }, { katalog: 2 }],
  ["bildirim metni", false, { bildirim: 2 }, { bildirim: 2 }],
  ["sube takvimi", false, { sube: 2, odunc: 1 }, { sube: 2 }],
  ["rapor sutunu", false, { rapor: 2, ucret: 1 }, { rapor: 2 }],
  ["para birimi", true, { ortak: 1, ucret: 2, rapor: 1, sube: 1 }, { ortak: 2 }],
  ["kimlik alani", true, { uyelik: 2, ortak: 1, odunc: 1 }, { uyelik: 2 }],
  ["indirim kurali", false, { ucret: 2, odunc: 1, rapor: 1 }, { ucret: 2 }],
];

// Yayilma: yuzeyi degistiren bir degisiklik, dokunulan module bagimli modulleri de
// dokundurur; her dolayli modulde bir cagri yeri duzeltilir. Derinlik, dogrudan dokunulan
// modulden en uzak dolayli module kadar olan adim sayisidir.
const olc = (yapi, yuzey, yerlesim) => {
  const dogrudan = Object.keys(yerlesim);
  let dosya = Object.values(yerlesim).reduce((s, n) => s + n, 0);
  const gorulen = new Map(dogrudan.map((m) => [m, 0]));
  if (yuzey) {
    let sira = [...dogrudan], derinlik = 0;
    while (sira.length) {
      derinlik += 1;
      const sonraki = [];
      for (const m of sira)
        for (const [a, b] of BAGIMLILIK[yapi])
          if (b === m && !gorulen.has(a)) { gorulen.set(a, derinlik); sonraki.push(a); dosya += 1; }
      sira = sonraki;
    }
  }
  return { dosya, modul: gorulen.size, derinlik: Math.max(...gorulen.values()) };
};

mkdirSync("model", { recursive: true });
writeFileSync("model/model.mjs",
  `export const MODUL = ${JSON.stringify(MODUL)};\n` +
  `export const BAGIMLILIK = ${JSON.stringify(BAGIMLILIK)};\n` +
  `export const DEGISIKLIK = ${JSON.stringify(DEGISIKLIK)};\n` +
  `export const olc = ${olc};\n`);

console.log(`${MODUL.length} modul; bagimlilik A ${BAGIMLILIK.A.length}, B ${BAGIMLILIK.B.length}`);
console.log(`\n${"degisiklik".padEnd(20)}${"yuzey".padEnd(7)}` +
  `${"A dosya".padStart(8)}${"A modul".padStart(8)}${"A der".padStart(6)}` +
  `${"B dosya".padStart(9)}${"B modul".padStart(8)}${"B der".padStart(6)}`);
const olcum = DEGISIKLIK.map(([ad, yuzey, a, b]) =>
  [ad, yuzey, olc("A", yuzey, a), olc("B", yuzey, b)]);
for (const [ad, yuzey, a, b] of olcum)
  console.log(`${ad.padEnd(20)}${(yuzey ? "evet" : "hayir").padEnd(7)}` +
    `${String(a.dosya).padStart(8)}${String(a.modul).padStart(8)}${String(a.derinlik).padStart(6)}` +
    `${String(b.dosya).padStart(9)}${String(b.modul).padStart(8)}${String(b.derinlik).padStart(6)}`);

const ort = (yapi, alan) =>
  (olcum.reduce((s, o) => s + o[yapi][alan], 0) / olcum.length).toFixed(2);
console.log(`\ndegisiklik basina ortalama — A: dosya ${ort(2, "dosya")}, modul ${ort(2, "modul")}, ` +
  `derinlik ${ort(2, "derinlik")}`);
console.log(`degisiklik basina ortalama — B: dosya ${ort(3, "dosya")}, modul ${ort(3, "modul")}, ` +
  `derinlik ${ort(3, "derinlik")}`);
const enKotu = olcum.slice().sort((a, b) => b[3].modul - a[3].modul)[0];
console.log(`iki yapinin da en pahali degisikligi: ${enKotu[0]} ` +
  `(A ${enKotu[2].modul} modul, B ${enKotu[3].modul} modul)`);
8 modul; bagimlilik A 15, B 9

degisiklik          yuzey   A dosya A modul A der  B dosya B modul B der
ucret tarifesi      evet          6       5     1        2       1     0
gecikme esigi       hayir         3       3     0        1       1     0
uye alani           evet          7       6     1        2       1     0
odunc suresi        hayir         4       3     0        2       1     0
rezervasyon kurali  evet          5       4     1        4       3     1
katalog alani       evet          4       3     0        2       1     0
bildirim metni      hayir         2       1     0        2       1     0
sube takvimi        hayir         3       2     0        2       1     0
rapor sutunu        hayir         3       2     0        2       1     0
para birimi         evet          9       8     2        9       8     1
kimlik alani        evet          9       8     1        2       1     0
indirim kurali      hayir         4       3     0        2       1     0

degisiklik basina ortalama — A: dosya 4.92, modul 4.00, derinlik 0.50
degisiklik basina ortalama — B: dosya 2.67, modul 1.75, derinlik 0.17
iki yapinin da en pahali degisikligi: para birimi (A 8 modul, B 8 modul)

Üç ölçüt de aynı yönü gösteriyor ama farklı büyüklükte ayrışıyor. Dosyada oran 1,8; modülde 2,3; derinlikte 3. En zayıf ayrım dosyada, çünkü doğrudan dokunulan dosya sayısının büyük bölümü işin kendisinden gelir — bir tarife değişikliği hangi yapıda olursa olsun tarifenin yazıldığı iki dosyayı ister. Yapı farkı dosyada değil, dosyaların hangi modüllere dağıldığında görünüyor.

Farkın tamamı yüzeyi değiştiren altı değişiklikten geliyor. Yüzeyi değiştirmeyen altı istekte yayılma yok, derinlik sıfır ve dağınık yapıda bile iki ile dört dosya arasında kalınıyor. Yapının bedeli her değişiklikte değil, yalnız dışa açık yüzeye dokunan değişiklikte ödeniyor; ölçüm bu iki kümeyi ayırmazsa ortalama, farkı olduğundan küçük gösterir.

Bir satır iki yapıda da aynı: para birimi değişikliği ikisinde de sekiz modüle dokunuyor. Derli yapıda kavram tek modülde toplanmış, ama o modül ortak modül ve ona herkes bağlı. Toplamak, kavram zaten herkesin bağlı olduğu yerdeyse bir şey kazandırmaz. Modüler yapının kazancı seçici: kavramı bir kenara toplayabildiği yerde büyük, merkeze toplamak zorunda kaldığı yerde sıfır.

Ölçütler Bozulmayı Gösteriyor mu

Bir ölçütün asıl sınavı tek bir ölçümde değil, zaman içinde verdiği eğridedir. Aşağıdaki blok derli yapının altı çeyrekte bozulmasını modelliyor — her çeyrekte bir kavram fazladan bir modüle sızıyor, iki çeyrekte ayrıca yeni bir bağımlılık kenarı açılıyor — ve üç ölçütün bu bozulmayı gösterip göstermediğini sınıyor. Sonra bir eşik kuralı yazıp altıncı çeyrekteki gerçek maliyet kaydında koşturuyor.

// bozulma.mjs — olcutlerin zaman icinde bozulmayi gosterip gostermedigini sinar ve esik
// kuralini bilinen maliyet kaydinda kosturur
import { BAGIMLILIK, DEGISIKLIK, olc } from "./model/model.mjs";

// KN27: B yapisi alti ceyrekte bozuluyor. Her ceyrekte bir kavram fazladan bir module siziyor
// [kavram, modul]; iki ceyrekte ayrica yeni bir bagimlilik kenari aciliyor. Model.
const SIZINTI = [
  ["ucret tarifesi", "sube", null],
  ["uye alani", "odunc", null],
  ["odunc suresi", "sube", ["rapor", "ucret"]],
  ["ucret tarifesi", "rapor", null],
  ["katalog alani", "rapor", ["bildirim", "uyelik"]],
  ["uye alani", "rapor", null],
];

const yerlesim = DEGISIKLIK.map(([ad, yuzey, , b]) => [ad, yuzey, { ...b }]);
const olcumler = [];
const cek = () => {
  const o = yerlesim.map(([ad, yuzey, y]) => [ad, olc("B", yuzey, y)]);
  const ort = (alan) => o.reduce((s, [, v]) => s + v[alan], 0) / o.length;
  return { o, dosya: ort("dosya"), modul: ort("modul"), derinlik: ort("derinlik"),
    ihlal: o.filter(([, v]) => v.modul > 2).length };
};
olcumler.push(cek());
for (const [kavram, modul, kenar] of SIZINTI) {
  const y = yerlesim.find(([ad]) => ad === kavram)[2];
  y[modul] = (y[modul] || 0) + 1;
  if (kenar) BAGIMLILIK.B.push(kenar);
  olcumler.push(cek());
}

console.log(`${"ceyrek".padEnd(8)}${"dosya/degisiklik".padStart(17)}${"modul/degisiklik".padStart(17)}` +
  `${"derinlik".padStart(10)}${"esigi asan degisiklik".padStart(23)}  (Q0 = bozulmadan once)`);
olcumler.forEach((m, i) =>
  console.log(`${`Q${i}`.padEnd(8)}${m.dosya.toFixed(2).padStart(17)}` +
    `${m.modul.toFixed(2).padStart(17)}${m.derinlik.toFixed(2).padStart(10)}` +
    `${String(m.ihlal).padStart(23)}`));

const ilkHareket = (alan) => olcumler.findIndex((m, i) => i > 0 && m[alan] > olcumler[0][alan]);
const artan = (alan) => olcumler.every((m, i) => i === 0 || m[alan] >= olcumler[i - 1][alan]);
for (const alan of ["dosya", "modul", "derinlik", "ihlal"])
  console.log(`${alan.padEnd(9)} ilk hareket Q${ilkHareket(alan)}, ` +
    `bastan sona artis ${(olcumler.at(-1)[alan] - olcumler[0][alan]).toFixed(2)}, ` +
    `hic geri gitmiyor mu: ${artan(alan) ? "evet" : "hayir"}`);

// KN28: alti ceyrek sonunda on iki degisikligin gercek maliyeti ekip kaydindan okunuyor;
// dordu pahali gecmis. Kayit bir gozlemdir, olcutten cikmaz. Model.
const PAHALI = ["ucret tarifesi", "uye alani", "rezervasyon kurali", "katalog alani"];
// Kural: bir degisiklik en fazla iki module dokunmali.
const ESIK = 2;
const son = olcumler.at(-1).o;
let yak = 0, kac = 0, yanlis = 0, sessiz = 0;
const ayrik = [];
for (const [ad, v] of son) {
  const oter = v.modul > ESIK, pahali = PAHALI.includes(ad);
  if (oter && pahali) yak += 1; else if (oter) yanlis += 1;
  else if (pahali) kac += 1; else sessiz += 1;
  if (oter !== pahali) ayrik.push(`  ${oter ? "yanlis alarm" : "kacan"}: ${ad} ` +
    `(${v.modul} modul, kayitta ${pahali ? "pahali" : "ucuz"})`);
}
console.log(`\nesik ${ESIK} modul, ${son.length} degisiklik, ${PAHALI.length} pahali: ` +
  `yakalanan ${yak}, kacan ${kac}, yanlis alarm ${yanlis}, dogru sessizlik ${sessiz}`);
for (const s of ayrik) console.log(s);

// KN29: cevrilemeyen kisim. Olcut genisligi sayar, zorlugu sayamaz; bir kavramin dogru
// modulde olup olmadigi da bir yargidir. Yerine ceyrekte bir yerlesim gozden gecirmesi
// konur: 12 degisiklik x 1 kisi-saat.
const SAAT = 1;
console.log(`\ncevrilemeyen: degisikligin zorlugu ve kavramin dogru modulde olmasi (yargi); ` +
  `yanlis alarm ${yanlis}, kacan ${kac} tam olarak bu kapsamda`);
console.log(`yerine konan: ceyrekte bir yerlesim gozden gecirmesi, ` +
  `${son.length * SAAT} kisi-saat/ceyrek, alti ceyrekte ${son.length * SAAT * 6}`);
const kayit = DEGISIKLIK.length * 3 + BAGIMLILIK.B.length;
console.log(`olcut maliyeti: ${kayit} kayit (12 degisiklik x 3 alan + ` +
  `${BAGIMLILIK.B.length} bagimlilik kenari); kosum ${olcumler.length * son.length} olcum; ` +
  `degisim maliyeti, borc faizi olarak olculen seyin gozlenen yuzudur`);
ceyrek   dosya/degisiklik modul/degisiklik  derinlik  esigi asan degisiklik  (Q0 = bozulmadan once)
Q0                   2.67             1.75      0.17                      2
Q1                   2.75             1.83      0.17                      2
Q2                   3.00             2.08      0.25                      3
Q3                   3.17             2.25      0.33                      4
Q4                   3.17             2.25      0.25                      4
Q5                   3.42             2.50      0.33                      4
Q6                   3.42             2.50      0.33                      4
dosya     ilk hareket Q1, bastan sona artis 0.75, hic geri gitmiyor mu: evet
modul     ilk hareket Q1, bastan sona artis 0.75, hic geri gitmiyor mu: evet
derinlik  ilk hareket Q2, bastan sona artis 0.17, hic geri gitmiyor mu: hayir
ihlal     ilk hareket Q2, bastan sona artis 2.00, hic geri gitmiyor mu: evet

esik 2 modul, 12 degisiklik, 4 pahali: yakalanan 3, kacan 1, yanlis alarm 1, dogru sessizlik 7
  kacan: katalog alani (2 modul, kayitta pahali)
  yanlis alarm: para birimi (8 modul, kayitta ucuz)

cevrilemeyen: degisikligin zorlugu ve kavramin dogru modulde olmasi (yargi); yanlis alarm 1, kacan 1 tam olarak bu kapsamda
yerine konan: ceyrekte bir yerlesim gozden gecirmesi, 12 kisi-saat/ceyrek, alti ceyrekte 72
olcut maliyeti: 47 kayit (12 degisiklik x 3 alan + 11 bagimlilik kenari); kosum 84 olcum; degisim maliyeti, borc faizi olarak olculen seyin gozlenen yuzudur

Dosya ve modül ölçütleri ilk çeyrekte hareket ediyor ve altı çeyrek boyunca hiç geri gitmiyor. Derinlik ölçütü ikisini de yapmıyor: ikinci çeyrekte hareket ediyor, dördüncü çeyrekte geri gidiyor. Yapı yalnız kötüleşirken ölçüt bir çeyrek iyileşmiş görünüyor. Nedeni ölçütün tanımında: sızıntı, bir kavramı daha önce dolaylı dokunulan modüle doğrudan yazıyor, o modül artık yayılma değil başlangıç sayılıyor ve derinlik düşüyor. Bozulmanın kendisi ölçütün girdisini değiştiriyorsa ölçüt tek yönlü olmaz — ve bu, ölçütü kullanışsız kılmaz, yalnız tek başına okunamaz kılar.

Eşiği aşan değişiklik sayısı basamak basamak ilerliyor: iki, üç, dört, sonra üç çeyrek sabit. Kaba sayım geç hareket ediyor ve son üç çeyrekteki bozulmayı hiç göstermiyor; ortalama ise aynı üç çeyrekte 2,25’ten 2,50’ye çıkıyor. İkisi birbirinin yerine geçmiyor: ortalama erken uyarı verir, eşik sayımı karar noktası olur.

Kuralın kendisi altıncı çeyrekte on iki değişikliğin dördü pahalı geçmişken üç yakalama, bir kaçan, bir yanlış alarm ve yedi doğru sessizlik üretiyor. Kaçan, iki modüle dokunduğu halde pahalı geçen katalog alanı değişikliği; yanlış alarm, sekiz modüle dokunduğu halde ucuz geçen para birimi değişikliği — sekiz yerde aynı sabitin adı düzeltilmiş. İki hata da tek kaynaktan geliyor: ölçüt genişliği sayıyor, zorluğu değil. Çevrilemeyen kısım tam olarak budur; değişikliğin zorluğu ve kavramın doğru modülde olup olmadığı bir yargıdır. Yerine çeyrekte bir yerleşim gözden geçirmesi konuyor: on iki değişiklik için 12 kişi-saat, altı çeyrekte 72.

Ölçütün kendi maliyeti küçük görünüyor — 47 kayıt ve 84 ölçüm. Ama kayıtların hepsi elle tutuluyor: her değişiklik isteğinin hangi kavrama dokunduğu, yüzeyi değiştirip değiştirmediği ve hangi modülde kaç dosyada geçtiği. Bu kayıt bırakılırsa ölçüt susmaz, yanlış konuşur; bozulmayı gösteren eğri, kaydın eskime eğrisi olur.

Özet

  • Aynı on iki değişiklik iki yapıda koşturulduğunda dağınık yapı değişiklik başına 4,92 dosya, 4,00 modül ve 0,50 derinlik; derli yapı 2,67 dosya, 1,75 modül ve 0,17 derinlik istiyor.
  • Ayrım en zayıf dosyada (1,8 kat), en keskin derinlikte (3 kat); farkın tamamı yüzeyi değiştiren altı değişiklikten geliyor, ötekilerde iki yapı ayrışmıyor.
  • Ortak modüle dokunan değişiklik iki yapıda da sekiz modüle yayılıyor: toplamak, kavram zaten herkesin bağlı olduğu yerdeyse kazandırmıyor.
  • Altı çeyreklik bozulmada dosya ve modül ölçütleri ilk çeyrekte hareket edip hiç geri gitmiyor; derinlik dördüncü çeyrekte geri gidiyor, çünkü sızıntı dolaylı dokunuşu doğrudana çeviriyor.
  • Eşik kuralı 3 yakalanan, 1 kaçan, 1 yanlış alarm, 7 doğru sessizlik veriyor; iki hata da ölçütün genişliği sayıp zorluğu saymamasından geliyor.
  • Çevrilemeyen kısmın yerine çeyrekte 12 kişi-saatlik yerleşim gözden geçirmesi konuyor; ölçütün koşumu 84 ölçüm, bakımı 47 elle tutulan kayıt.

Sonraki Adım

Bu konu nitelikleri ölçüye bağladı. Ağaçta otuz yaprak sayıldı ve hangisinin denetime çevrilebildiği ayrıldı; gerilim matrisi hangi kararın hangi niteliği bastırdığını gösterdi; güven sınırları ve denetimsiz geçişler sayıldı; ölçek biriminin üç adayı kopyalanan, paylaşılan ve kopyalanamayan üzerinden karşılaştırıldı; değişim maliyeti iki yapıda doğrudan ölçüldü. Artık hangi kararın hangi niteliği koruduğu yazılabiliyor.

Ama bu ölçülerin hepsi bir kez alındı. Eşikler bir günün eşiği, grafik bir günün grafiği, yerleşim bir günün yerleşimi. Altı ay sonra hâlâ geçerli olup olmadığını söyleyen bir şey yok — bu dersin bozulma tablosu bile elle kurulmuş bir modeldi, kendiliğinden koşan bir düzen değil. Ölçümü tekrarlayan, sapmayı bildiren ve her ölçünün kime ait olduğunu belirleyen bir mekanizma gerekiyor; onsuz bu konudaki bütün sayılar, alındıkları günün fotoğrafı olarak kalır.

İ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