İçeriğe geç
academia.sh

Ders 07 / 21

Hat Kodu Olarak Yapılandırma

Aynı hat iki biçimde tanımlanır — arayüzden elle ve depoda dosya olarak: şema ders içinde tanımlanıp altı hatalı değişiklik gerçek bir doğrulayıcıdan geçirilir, izlenebilirlik, gözden geçirmeden geçen değişiklik oranı, geri alma adımı ve beş servis için tanımın çoğaltılması sayılır.

İçindekiler

Önceki iki ders hattın süresini kısalttı ve nerede koşacağına karar verdi. İkisi de aynı nesneye dokundu ama o nesnenin kendisi hiç sorgulanmadı: sekiz aşama, aralarındaki bağımlılıklar, hangi aşamanın kapı olduğu ve kaç yuva kullanıldığı bir yerde yazılıdır. Yuva sayısını ikiden dörde çıkarmak da, bir kapıyı kaldırmak da o yazılı şeye dokunmaktır.

Nerede yazılı olduğu iki seçenektir. Hat bir arayüzden elle yapılandırılabilir: alanlar doldurulur, kaydedilir, o an yürürlüğe girer. Ya da depoda bir dosya olarak durur: değişiklik bir işleme olarak gelir, gözden geçirilir, sürümlenir. Bu ders iki biçimi dört sayıda karşılaştırır — izlenebilirlik, gözden geçirmeden geçen değişiklik oranı, geri alma adımı ve tanımın beş servise çoğaltılması.

HT37. Şema, doğrulayıcı ve dosya biçimi bu derste tanımlanmıştır; hat, yuva, asama, komut, onceki, kapi alan adları gerçek bir aracın alanları değildir. HT38. Yirmi hat değişikliği kurgu ölçüm ağının bir çeyreğini temsil eder; altısı hatalıdır. HT39. Arayüz biçiminin seçim listesi yapı gereği üç hata türünü engeller — bilinmeyen alan yazılamaz, olmayan bir aşamaya bağlanılamaz, sayı alanına metin girilemez; buna karşılık arayüzde gözden geçirme adımı yoktur. HT40. Depo biçiminde 3, 11 ve 17 numaralı değişiklikler acil gerekçesiyle gözden geçirmeden geçmiştir. HT41. Gözden geçirme beklemesi 42 dakika, şema doğrulaması 4 saniyedir; geri alma adımları sayılır, dakikaları kurgudur. HT42. Satır sayıları gerçekten üretilen metinden sayılır.

Şemanın Kendisi

Bir hat tanımı dosya olarak durduğunda kazanılan ilk şey şudur: tanım artık veridir ve veri okunabilir. Şema alanları ve türlerini bildirir; doğrulayıcı şemayı okuyup tanımı denetler.

// olcum-agi/sema.mjs — hat tanimi semasi, dogrulayici ve yazici (alan adlari bu derste tanimlandi)
export const SEMA = { hat: "metin", yuva: "sayi", asamalar: "dizi" };
const ASAMA = { ad: "metin", komut: "metin", onceki: "dizi", kapi: "eh" };
const tip = { metin: (d) => typeof d === "string", sayi: (d) => Number.isInteger(d),
  dizi: (d) => Array.isArray(d), eh: (d) => d === "evet" || d === "hayir" };
const alanDenetle = (nesne, sema, on, h) => {
  for (const a of Object.keys(nesne)) if (!sema[a]) h.push(`bilinmeyen alan: ${on}${a}`);
  for (const [a, tp] of Object.entries(sema))
    if (!(a in nesne)) h.push(`eksik alan: ${on}${a}`);
    else if (!tip[tp](nesne[a])) h.push(`tur hatasi: ${on}${a}, ${tp} bekleniyor`);
};

export const dogrula = (t) => {
  const h = [];
  alanDenetle(t, SEMA, "", h);
  if (!Array.isArray(t.asamalar)) return h;
  const adlar = t.asamalar.map((s) => s.ad);
  for (const s of t.asamalar) {
    alanDenetle(s, ASAMA, `${s.ad}.`, h);
    for (const o of s.onceki || []) if (!adlar.includes(o)) h.push(`bilinmeyen onceki: ${s.ad} -> ${o}`);
  }
  const bak = (ad, iz) => (iz.includes(ad) ? true
    : (t.asamalar.find((s) => s.ad === ad)?.onceki || []).some((o) => bak(o, [...iz, ad])));
  for (const s of t.asamalar) if (bak(s.ad, [])) { h.push(`dongu: ${s.ad}`); break; }
  return h;
};

export const TABAN = { hat: "okuma-toplayici", yuva: 3, asamalar: [
  ["bagimlilik-cozumu", "coz", [], "hayir"], ["derleme", "derle", ["bagimlilik-cozumu"], "hayir"],
  ["statik-cozumleme", "coz-statik", ["bagimlilik-cozumu"], "evet"], ["birim-sinamasi", "sina-birim", ["derleme"], "evet"],
  ["tumlesim-sinamasi", "sina-tumlesim", ["derleme"], "evet"], ["paketleme", "paketle", ["derleme"], "hayir"],
  ["sozlesme-sinamasi", "sina-sozlesme", ["paketleme"], "evet"], ["imaj-uretimi", "uret-imaj", ["paketleme"], "hayir"],
].map(([ad, komut, onceki, kapi]) => ({ ad, komut, onceki, kapi })) };

// Tanimin dosya bicimi: her asama uc ya da dort satir.
export const yaz = (t) => [`hat ${t.hat}`, `yuva ${t.yuva}`, ...t.asamalar.flatMap((s) => [`asama ${s.ad}`,
  `  komut ${s.komut}`, ...(s.onceki.length ? [`  onceki ${s.onceki.join(" ")}`] : []), `  kapi ${s.kapi}`])];

// Kurgu olcum aginin bes servisi: taban hat + servise ozgu farklar.
export const SERVISLER = [
  { ad: "okuma-toplayici", fark: [] }, { ad: "dogrulayici", fark: [["yuva", 2]] },
  { ad: "faturalama", fark: [["sil", "sozlesme-sinamasi"]] },
  { ad: "is-emri", fark: [["ekle", { ad: "gecikme-sinamasi", komut: "sina-gecikme", onceki: ["paketleme"], kapi: "evet" }]] },
  { ad: "gecelik-toplu-is", fark: [["yuva", 4], ["sil", "statik-cozumleme"]] },
];
export const uygula = (s) => {
  const t = { ...TABAN, hat: s.ad, asamalar: TABAN.asamalar.map((a) => ({ ...a })) };
  for (const [tur, d] of s.fark) {
    if (tur === "yuva") t.yuva = d;
    if (tur === "sil") t.asamalar = t.asamalar.filter((a) => a.ad !== d);
    if (tur === "ekle") t.asamalar.push(d);
  }
  return t;
};

Doğrulayıcı dört şey denetler: bilinmeyen ve eksik alan, tür uyuşmazlığı, olmayan bir aşamaya yapılan onceki atfı ve bağımlılıklarda döngü. Dördü de tanımın metninden kararlaştırılır; hattı koşturmak gerekmez.

İki Biçim

Altı hatalı değişiklik gerçekten kurulur, gerçekten doğrulayıcıdan geçirilir ve her biri için iki biçimin tuttuğu ayrı ayrı yazılır.

// olcum-agi/bicimler.mjs — ayni hat iki bicimde: izlenebilirlik, gozden gecirme, kapi, geri alma
import { dogrula, TABAN } from "./sema.mjs";

const kopya = () => ({ ...TABAN, asamalar: TABAN.asamalar.map((a) => ({ ...a })) });
const N = 20, GECEN = [3, 11, 17]; // gozden gecirmeden gecen uc hat degisikligi
const IZ = ["kim", "ne zaman", "neden", "ne degisti"];

// Alti hatali hat degisikligi. no: kacinci degisiklikte geldigi. arayuz: bicim geregi arayuzde
// yazilamayan hata (secim listesi engeller). gozden: yalniz insanin gorebilecegi hata.
const HATALAR = [
  ["H1 bilinmeyen alan", 2, 1, 0, (t) => { t.asama = t.asamalar; delete t.asamalar; }],
  ["H2 dongu", 5, 0, 1, (t) => { t.asamalar[1].onceki = ["paketleme"]; }],
  ["H3 bilinmeyen onceki", 8, 1, 0, (t) => { t.asamalar[3].onceki = ["derleme-eski"]; }],
  ["H4 tur hatasi (yuva)", 11, 1, 0, (t) => { t.yuva = "uc"; }],
  ["H5 kapi kaldirildi", 14, 0, 1, (t) => { t.asamalar.splice(2, 1); }],
  ["H6 kapi devre disi", 17, 0, 1, (t) => { t.asamalar[4].kapi = "hayir"; }],
];

const s = { arayuz: 0, depo: 0, sema: 0, yalniz: 0 };
console.log("hata                  no  sema dogrulayicisinin dedigi                arayuz  depo");
for (const [ad, no, arayuz, gozden, boz] of HATALAR) {
  const t = kopya(); boz(t);
  const bulgu = dogrula(t), sema = bulgu.length > 0;
  const depo = sema || (gozden && !GECEN.includes(no)); // gecen degisiklige insan bakmaz
  s.sema += sema; s.arayuz += arayuz; s.depo += depo; s.yalniz += depo && !sema;
  console.log(ad.padEnd(22) + String(no).padStart(2) + "  " + (bulgu[0] || "— (sema gecti)").slice(0, 42).padEnd(44) +
    (arayuz ? "tutar" : "kacar").padEnd(8) + (depo ? "tutar" : "kacar"));
}

const BICIM = {
  arayuz: { iz: IZ.slice(0, 2), gecen: N, dakika: 35,
    geri: ["degisikligi bul", "eski degeri hatirla", "elle geri yaz", "kaydet", "elle tetikle", "dogrula"] },
  depo: { iz: IZ, gecen: GECEN.length, dakika: 4, geri: ["onceki surume don", "hat kendini kossun"] },
};
console.log(`\nizlenebilirlik (${N} degisiklik x ${IZ.length} alan):`);
for (const [ad, b] of Object.entries(BICIM))
  console.log(`  ${ad.padEnd(8)}${b.iz.length * N}/${IZ.length * N} alan — ${b.iz.join(", ")}`);
console.log(`gozden gecirmeden gecen: arayuz ${N}/${N} (%100), depo ${GECEN.length}/${N} (%${(GECEN.length * 100) / N})`);
console.log(`kapi hesabi (6 hata): arayuz ${s.arayuz}/6, depo ${s.depo}/6 (sema ${s.sema}, yalniz gozden gecirme ${s.yalniz})`);
console.log("\nhat kirildiginda geri alma:");
for (const [ad, b] of Object.entries(BICIM))
  console.log(`  ${ad.padEnd(8)}${b.geri.length} adim, ${b.dakika} dk — ${b.geri.join(" / ")}`);
console.log(`\nkapinin ekledigi sure: sema 4 s x ${N} = ${4 * N} s; gozden gecirme 42 dk x ${N - GECEN.length} = ${42 * (N - GECEN.length)} dk`);
hata                  no  sema dogrulayicisinin dedigi                arayuz  depo
H1 bilinmeyen alan     2  bilinmeyen alan: asama                      tutar   tutar
H2 dongu               5  dongu: derleme                              kacar   tutar
H3 bilinmeyen onceki   8  bilinmeyen onceki: birim-sinamasi -> derle  tutar   tutar
H4 tur hatasi (yuva)  11  tur hatasi: yuva, sayi bekleniyor           tutar   tutar
H5 kapi kaldirildi    14  — (sema gecti)                              kacar   tutar
H6 kapi devre disi    17  — (sema gecti)                              kacar   kacar

izlenebilirlik (20 degisiklik x 4 alan):
  arayuz  40/80 alan — kim, ne zaman
  depo    80/80 alan — kim, ne zaman, neden, ne degisti
gozden gecirmeden gecen: arayuz 20/20 (%100), depo 3/20 (%15)
kapi hesabi (6 hata): arayuz 3/6, depo 5/6 (sema 4, yalniz gozden gecirme 1)

hat kirildiginda geri alma:
  arayuz  6 adim, 35 dk — degisikligi bul / eski degeri hatirla / elle geri yaz / kaydet / elle tetikle / dogrula
  depo    2 adim, 4 dk — onceki surume don / hat kendini kossun

kapinin ekledigi sure: sema 4 s x 20 = 80 s; gozden gecirme 42 dk x 17 = 714 dk

Kapının Hesabı

Tablo beklenmedik bir yerden başlar: arayüz biçimi üç hatayı yapı gereği tutar. Bilinmeyen bir alan yazılamaz çünkü alan listesi sabittir; olmayan bir aşamaya bağlanılamaz çünkü seçim listeden yapılır; sayı alanına metin girilemez. Depo biçiminde bu üç hata yazılabilir ve ancak şema doğrulayıcısı koşarsa yakalanır. Doğrulayıcı olmadan depo biçimi bu üç hatada arayüzden kötüdür.

Doğrulayıcı koştuğunda tablo döner. Şema dört hatayı tutar; dördüncüsü döngüdür ve arayüzün göremediği hatadır: derleme aşaması paketlemeye bağlanınca çizge kendine kapanır, ama her alan tek tek geçerlidir. Bir arayüz alan denetler, çizge denetlemez.

Geriye iki hata kalır ve ikisi de kapının kendisine dokunur: H5 statik çözümleme aşamasını siler, H6 tümleşim sınamasının kapi alanını hayir yapar. İkisi de şemaya göre kusursuz tanımlardır. Yalnız bir insan “bu değişiklik neden bir kapıyı kaldırıyor” diye sorabilir. Depo biçimi H5’i tutar; H6 kaçar, çünkü 17 numaralı değişiklik gözden geçirmeden geçen üçten biridir.

Toplam arayüz 3/6, depo 5/6. Kapının marjinal değeri ise ayrı okunur: şema dört hata tutar ve 20 değişiklikte toplam 80 saniye ekler. Gözden geçirme yalnız bir hatayı tek başına tutar ve 17 değişiklikte 714 dakika bekletir. Bir kusur için on iki saat — bu kursun ölçüsüne göre savunulması en zor kapı budur. Savunması tek bir yerdedir: tuttuğu hata, hattın bir kapısını kaldıran değişikliktir; bu kursun bütün ölçümlerinin dayandığı şeyi sessizce silen türden. Buna karşılık aynı hatayı çok daha ucuza tutmanın yolu görünüyor — şemaya “kapı sayısı azalamaz” kuralını eklemek. Bu ders o kuralı ölçmez; ama ölçseydi gözden geçirmenin marjinal değeri sıfıra inerdi ve 714 dakikalık kapı kaldırılabilir hâle gelirdi.

İzlenebilirlik iki alanda ayrılır. Arayüz 80 alanın 40’ını kaydeder — kim ve ne zaman. Eksik olan ikisi “neden” ve “ne değişti”dir, yani hat gece bozulduğunda sorulacak tam olarak iki soru. Geri alma tablosu aynı boşluğun ikinci faturasıdır: arayüzde geri almanın ilk adımı “değişikliği bul”dur ve cevabı hiçbir yerde yazılı değildir — altı adım, 35 dakika. Depoda önceki sürüme dönmek iki adım ve 4 dakikadır, çünkü “ne değişti” zaten kayıtlıdır.

Beş Servis

Kurgu ölçüm ağının beş servisi neredeyse aynı hattı kullanır. Tanım her servise kopyalanabilir ya da ortak parça ayrılıp farklar bildirilebilir. İki biçimin satırları gerçekten üretilip sayılır.

// olcum-agi/cogaltma.mjs — bes servis: tam kopya ile ortak parca ayrilmis bicim
import { SERVISLER, uygula, yaz, TABAN } from "./sema.mjs";

const tam = SERVISLER.map((s) => ({ ad: s.ad, satir: yaz(uygula(s)) }));
const toplamTam = tam.reduce((t, s) => t + s.satir.length, 0);

// Ortak parca: hat adi disindaki her sey. Servis dosyasi: kalitim + ad + yalniz farklar.
const ortak = yaz(TABAN).filter((l) => !l.startsWith("hat "));
const ayrik = SERVISLER.map((s) => ({
  ad: s.ad,
  satir: ["kalitim ortak", `hat ${s.ad}`, ...s.fark.flatMap(([tur, d]) => tur === "yuva" ? [`yuva ${d}`]
    : tur === "sil" ? [`asama-sil ${d}`]
      : [`asama ${d.ad}`, `  komut ${d.komut}`, `  onceki ${d.onceki.join(" ")}`, `  kapi ${d.kapi}`])],
}));
const toplamAyrik = ortak.length + ayrik.reduce((t, s) => t + s.satir.length, 0);

// Bes dosyanin hepsinde birebir gecen satirlar (tekrar sayisiyla birlikte).
const say = (a) => a.reduce((m, l) => m.set(l, (m.get(l) || 0) + 1), new Map());
const sayimlar = tam.map((s) => say(s.satir));
let hepsinde = 0;
for (const [l, c] of sayimlar[0]) hepsinde += Math.min(c, ...sayimlar.slice(1).map((m) => m.get(l) || 0));

console.log("servis              tam kopya  ortak parca ile");
tam.forEach((s, i) => console.log(s.ad.padEnd(20) + String(s.satir.length).padStart(6) +
  String(ayrik[i].satir.length).padStart(12)));
console.log("ortak dosya".padEnd(20) + "     —" + String(ortak.length).padStart(12));
console.log("toplam".padEnd(20) + String(toplamTam).padStart(6) + String(toplamAyrik).padStart(12) +
  `   (%${(((toplamTam - toplamAyrik) * 100) / toplamTam).toFixed(1)} az satir)`);
console.log(`\nbes dosyanin hepsinde birebir gecen satir: ${hepsinde} — tam kopyada ` +
  `${hepsinde * 5} satirin ${hepsinde * 4}'u tekrardir`);

console.log("\nokunabilirlik bedeli");
console.log(`  bir servisin hattini okumak: tam kopya 1 dosya ${tam[1].satir.length} satir; ` +
  `ortak parca ile 2 dosya ${ortak.length + ayrik[1].satir.length} satir`);
console.log(`  ortak dosyada tek satir degisince etkilenen servis: ${SERVISLER.length}/${SERVISLER.length}`);
console.log(`  bes servise yeni bir kapi eklemek: tam kopya ${5 * 4} satir / 5 ayri degisiklik; ` +
  `ortak parca ile 4 satir / 1 degisiklik`);
servis              tam kopya  ortak parca ile
okuma-toplayici         33           2
dogrulayici             33           3
faturalama              29           3
is-emri                 37           6
gecelik-toplu-is        29           4
ortak dosya              —          32
toplam                 161          50   (%68.9 az satir)

bes dosyanin hepsinde birebir gecen satir: 24 — tam kopyada 120 satirin 96'u tekrardir

okunabilirlik bedeli
  bir servisin hattini okumak: tam kopya 1 dosya 33 satir; ortak parca ile 2 dosya 35 satir
  ortak dosyada tek satir degisince etkilenen servis: 5/5
  bes servise yeni bir kapi eklemek: tam kopya 20 satir / 5 ayri degisiklik; ortak parca ile 4 satir / 1 degisiklik

Kazanç büyüktür: 161 satır 50 satıra iner, yüzde 68,9 azalma. Kaynağı da açıktır — beş dosyanın hepsinde birebir aynı geçen 24 satır vardır ve tam kopyada bu satırlar 120 kez yazılmıştır, yani 96 satır tekrardır.

Bedel satır sayısında görünmez. Bir servisin hattını okumak tam kopyada 33 satırlık tek dosya, ortak parça ayrıldığında 35 satır ve iki dosya demektir. Fark iki satırdır ama asama-sil sozlesme-sinamasi satırı tek başına hiçbir şey anlatmaz; silinen şeyin ne olduğu ancak öteki dosyada görünür ve etkin tanım hiçbir yerde bütün hâlde durmaz.

Asıl bedel etki alanıdır. Ortak dosyada değiştirilen tek satır beş servisin beşini birden etkiler — ki bu aynı zamanda kazançtır: beş servise yeni bir kapı eklemek tam kopyada 20 satır ve beş ayrı değişiklik, ortak parça ile 4 satır ve tek değişikliktir. Çoğaltma dağınıktır ama bir servisi tek başına değiştirmeye izin verir; ortaklaştırma her değişikliği beş servisin ortak kararı yapar. Beş hat gerçekten aynı kalmalıysa ortaklaştırma doğrudur; ayrışmaları bekleniyorsa 96 satırlık tekrar, ödenmiş bir bağımsızlık bedelidir.

Özet

  • Tanım dosya olduğunda veri olur: şema alanları ve türleri bildirir, doğrulayıcı bilinmeyen alanı, tür hatasını, olmayan aşamaya atfı ve bağımlılık döngüsünü hattı koşturmadan yakalar.
  • Arayüz biçimi üç hatayı yapı gereği tutar (alan listesi sabit, seçim listeden yapılır) ama çizgeyi denetlemez ve döngüyü kaçırır. Toplam: arayüz 3/6, depo 5/6.
  • Kapının marjinal değeri: şema dört hata tutar ve 20 değişiklikte 80 saniye ekler; gözden geçirme yalnız bir hatayı tek başına tutar ve 17 değişiklikte 714 dakika bekletir. Tuttuğu hata bir kapıyı kaldıran değişikliktir; şemaya “kapı sayısı azalamaz” kuralı eklenirse bu kapı savunulamaz olur.
  • İzlenebilirlik 40/80 alana karşı 80/80’dir. Arayüzde eksik olan iki alan “neden” ve “ne değişti”dir; geri almanın ilk adımı da tam olarak odur — 6 adım ve 35 dakikaya karşı 2 adım ve 4 dakika.
  • Beş servis için tam kopya 161, ortak parça ayrılmış biçim 50 satırdır (yüzde 68,9 az); beş dosyada birebir aynı geçen 24 satırın 96 tekrarı silinir. Bedeli okunabilirlik ve etki alanıdır: bir servisi okumak 2 dosya 35 satıra çıkar, ortak dosyadaki tek satır beş servisi birden etkiler.

Sonraki Adım

Hat artık tanımlı, doğrulanmış, sürümlenmiş ve geri alınabilir. Bir soru hâlâ dışarıda: bu hat ne zaman koşuyor? Önceki derste günde 10 ile 80 arasında koşum sayısı verildi ve eşik o sayıdan hesaplandı, ama sayının nereden geldiği hiç söylenmedi. Koşumu başlatan şey bir tetikleyicidir ve tetikleyici de bir kapıdır: koşumun olup olmayacağına karar verir. Üç tür vardır — bir olaya bağlanan, saate bağlanan ve elle çekilen — ve üçü aynı takvimde çok farklı sayılar üretir: kaç koşum, kaçı hiçbir şey değişmemişken, değişiklikle koşum arasında ne kadar gecikme, kaç koşum birbirinin üstüne biniyor. Sonraki ders üçünü aynı iki haftalık kurgu takvimde koşturur, sonra yol tabanlı bir filtre ekleyip gereksiz koşumun düşüşünü ve filtrenin kaçırdığı gerçek değişikliği birlikte sayar.

İ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