Ders 07 / 12
Yazılım Malzeme Listesi
Bileşen envanteri bir soru kümesiyle ölçülür: on iki sorunun kaçı envanterin alanlarıyla cevaplanıyor, kaçı hiç cevaplanmıyor, cevaplananların kaçı eksik alanla veriliyor ve envanter ne sıklıkta üretilirse sorgu anında kaç kayıtta yanılıyor.
İçindekiler
Önceki konu her değişiklikte ne tarandığını ve neyin kaçtığını saydı. Bütün o denetimlerin ortak bir sınırı vardı: hepsi ekibin yazdığı koda bakıyordu. Üretime giden şeyin çoğu ise başka yerde üretilmiş bileşenlerden oluşur ve o bileşenlerin hiçbiri bir gözden geçirmeden, bir kural setinden ya da bir desen taramasından geçmemiştir.
Bu ders o katmanı bir envantere çevirir. Kurs boyunca ölçü kanıttır ve bu derste kanıtın birimi sorudur: envanterin cevaplayabildiği soru sayısı. Sürekli Tümleştirme ve Teslimat kursunda envanter bir hat çıktısı olarak ölçülmüştü — kapsamı ve üretim maliyeti sayılmıştı; o ölçüm tekrarlanmaz. Buradaki nesne envanterin yapısıdır: hangi alanı taşıyor, o alanlarla hangi soru cevaplanabiliyor, hangisi cevaplanamıyor.
TZ1. Kurgu bölgesel ölçüm ağı üç depodur — abone portalı, sayaç alım servisi ve ikisinin
paylaştığı ortak kitaplık; bağımlılık ağacı node ile üretilir ve bir modeldir. TZ2.
Envanter beş alan taşır: bileşen adı, sürüm, hangi depoya girdiği, lisans ve bağımlılık ilişkisi
(bu bileşeni hangi bileşenin getirdiği). Envanter biçiminin adı geçmez; ölçülen şey bu beş alanın
kendisidir. TZ3. Çözümleyici lisansı kayıtların yaklaşık altıda birinde, getiren kenarını
sekizde birinde kaydetmez; alan vardır, değer yoktur. Üretecin tohumu 20260731 görünürdür.
Envanteri Üretmek
// olcum-agi/envanter.mjs — uc deponun bilesen envanteri uretilir (model) import { mkdirSync, writeFileSync } from "node:fs"; import { join } from "node:path"; const KOK = join(process.env.TMPDIR ?? "/tmp", "olcum-agi-tz"); mkdirSync(KOK, { recursive: true }); const uretec = (tohum) => () => (tohum = (tohum * 1103515245 + 12345) % 2147483648) / 2147483648; const r = uretec(20260731); // tohum gorunur const sec = (d) => d[Math.floor(r() * d.length)]; // Kurgu bolgesel olcum agi: abone portali ve sayac alim servisi ortak kitapligi paylasir. const DEPO = [["abone-portali", 11], ["sayac-alim", 9], ["ortak-kitaplik", 6]]; const HAVUZ = Array.from({ length: 70 }, (_, i) => `bilesen-${String(i + 1).padStart(2, "0")}`); const SURUM = ["1.2", "1.9", "2.0", "2.4", "3.0", "3.6", "4.1"]; const LISANS = ["izin-veren", "izin-veren", "izin-veren", "zayif-karsilikli", "karsilikli"]; const kayit = []; for (const [depo, dogrudanSayi] of DEPO) { const alinan = new Set(); const yeni = () => { let a; do { a = sec(HAVUZ); } while (alinan.has(a)); alinan.add(a); return a; }; const ekle = (ad, getiren) => { // Cozumleyici lisansi %18, getiren kenarini %12 oraninda kaydetmiyor: alan var, deger yok. kayit.push({ ad, surum: sec(SURUM), depo, lisans: r() < 0.18 ? "" : sec(LISANS), getiren: getiren === null ? depo : (r() < 0.12 ? "" : getiren) }); }; for (let i = 0; i < dogrudanSayi; i++) { const kok = yeni(); ekle(kok, null); for (let k = 0, n = Math.floor(r() * 4); k < n; k++) { // gecisli cocuklar const cocuk = yeni(); ekle(cocuk, kok); if (r() < 0.35) ekle(yeni(), cocuk); // ikinci duzey } } } writeFileSync(join(KOK, "envanter.json"), JSON.stringify(kayit)); const ALAN = ["ad", "surum", "depo", "lisans", "getiren"]; const dolu = (a) => kayit.filter((k) => k[a] !== "").length; const dogrudan = kayit.filter((k) => DEPO.some(([d]) => d === k.getiren)).length; console.log(`tohum 20260731; depo ${DEPO.length}, havuz ${HAVUZ.length} bilesen`); console.log(`envanter kaydi ${kayit.length} -> dogrudan ${dogrudan}, ` + `gecisli ${kayit.length - dogrudan}; benzersiz bilesen ${new Set(kayit.map((k) => k.ad)).size}`); console.log(`envanter alani ${ALAN.length}: ${ALAN.join(", ")}`); for (const a of ALAN) console.log(` ${a.padEnd(8)} dolu ${String(dolu(a)).padStart(3)}/${kayit.length}` + ` (%${(100 * dolu(a) / kayit.length).toFixed(0)})`);
tohum 20260731; depo 3, havuz 70 bilesen envanter kaydi 73 -> dogrudan 26, gecisli 47; benzersiz bilesen 48 envanter alani 5: ad, surum, depo, lisans, getiren ad dolu 73/73 (%100) surum dolu 73/73 (%100) depo dolu 73/73 (%100) lisans dolu 64/73 (%88) getiren dolu 67/73 (%92)
Üç depo 26 doğrudan bağımlılık bildirir; envanter 73 kayıt üretir. Aradaki 47 kayıt hiçbir ekibin seçmediği geçişli bileşenlerdir. Benzersiz bileşen sayısı 48’dir: aynı bileşen birden çok depoda geçer, her geçişi ayrı bir kayıttır.
Alanların doluluk oranı envanterin asıl ölçüsüdür. İlk üç alan her kayıtta doludur, çünkü çözümleyici onları bilmeden kaydı hiç üretemez. Son iki alan böyle değil: lisans 9 kayıtta, getiren 6 kayıtta boştur. Boş bir alan yokluk anlamına gelmez — bileşenin lisansı vardır, envanterde yazmıyordur. Bu ayrım bir sonraki bölümün tamamını belirler.
On İki Soru
Envanterin değeri üretildiği anda değil, sorulduğu anda ölçülür. Ölçünün birimi de sorudur: bir soru envanterin alanlarıyla cevaplanabiliyorsa envanter o soruyu kapsar, cevaplanamıyorsa soru envanterin dışındadır.
TZ4. Soru kümesi kurgudur ve on iki sorudan oluşur; her soru gereksindiği alanları bildirir. Bir soru, gereksindiği bütün alanlar envanterde varsa cevaplanır; alanların herhangi biri boş kayıt taşıyorsa cevap eksik sayılır.
// olcum-agi/sorular.mjs — envantere on iki soru sorulur: kaci cevaplaniyor import { readFileSync } from "node:fs"; import { join } from "node:path"; const KOK = join(process.env.TMPDIR ?? "/tmp", "olcum-agi-tz"); const kayit = JSON.parse(readFileSync(join(KOK, "envanter.json"))); const ALAN = new Set(["ad", "surum", "depo", "lisans", "getiren"]); const DEPO = ["abone-portali", "sayac-alim", "ortak-kitaplik"]; const H = "bilesen-19"; // orneklerde sorulan bilesen const adlar = [...new Set(kayit.map((k) => k.ad))]; const farkli = (ad, alan) => new Set(kayit.filter((k) => k.ad === ad).map((k) => k[alan])); const SORU = [ ["bu bilesen bizde var mi", ["ad"], () => (adlar.includes(H) ? "evet" : "hayir")], ["hangi surumde", ["ad", "surum"], () => [...farkli(H, "surum")].sort().join(", ")], ["hangi urune girdi", ["ad", "depo"], () => farkli(H, "depo").size + " depo"], ["kim getirdi", ["ad", "getiren"], () => [...farkli(H, "getiren")].join(", ")], ["kisitli lisansli bilesen var mi", ["lisans"], () => kayit.filter((k) => k.lisans === "karsilikli").length + " kayit"], ["kac bilesen iki surumle kosuyor", ["ad", "surum"], () => adlar.filter((a) => farkli(a, "surum").size > 1).length + " bilesen"], ["kac bilesen iki depoda ortak", ["ad", "depo"], () => adlar.filter((a) => farkli(a, "depo").size > 1).length + " bilesen"], ["dogrudan mi gecisli mi", ["getiren"], () => kayit.filter((k) => DEPO.includes(k.getiren)).length + " dogrudan"], ["bu bilesen kosum aninda cagriliyor mu", ["cagrilir"], null], ["bu surum envantere ne zaman girdi", ["giris"], null], ["son yayini ne kadar eski, kac bakimcisi var", ["bakim"], null], ["bu envanter hangi yapi ciktisini tarif ediyor", ["yapi"], null], ]; const bosSayisi = (alan) => kayit.filter((k) => k[alan] === "").length; const S = [46, 13, 12, 24, 0]; const satir = (s) => console.log(s.map((v, i) => String(v).padEnd(S[i])).join("")); satir(["soru", "gereken alan", "envanterde", "yanit", "tam mi"]); let cevaplanan = 0, tamCevap = 0; for (const [ad, gereken, calistir] of SORU) { const alanVar = gereken.every((a) => ALAN.has(a)); const bos = alanVar ? Math.max(...gereken.map(bosSayisi)) : 0; if (alanVar) { cevaplanan++; if (bos === 0) tamCevap++; } satir([ad, gereken.join("+"), alanVar ? "var" : "YOK", alanVar ? calistir() : "-", alanVar ? (bos ? `${bos} kayit bos` : "tam") : "-"]); } console.log(""); console.log(`on iki sorunun ${cevaplanan}'i envanterin alanlariyla cevaplaniyor, ` + `${SORU.length - cevaplanan}'u alan olmadigi icin hic cevaplanmiyor`); console.log(`cevaplanan ${cevaplanan} sorunun ${tamCevap}'i tam, ${cevaplanan - tamCevap}'i ` + `eksik alanla (lisans ${bosSayisi("lisans")} kayitta bos, getiren ${bosSayisi("getiren")} kayitta bos)`);
soru gereken alan envanterde yanit tam mi bu bilesen bizde var mi ad var evet tam hangi surumde ad+surum var 1.2, 3.6 tam hangi urune girdi ad+depo var 2 depo tam kim getirdi ad+getiren var bilesen-01, bilesen-23 6 kayit bos kisitli lisansli bilesen var mi lisans var 10 kayit 9 kayit bos kac bilesen iki surumle kosuyor ad+surum var 17 bilesen tam kac bilesen iki depoda ortak ad+depo var 20 bilesen tam dogrudan mi gecisli mi getiren var 26 dogrudan 6 kayit bos bu bilesen kosum aninda cagriliyor mu cagrilir YOK - - bu surum envantere ne zaman girdi giris YOK - - son yayini ne kadar eski, kac bakimcisi var bakim YOK - - bu envanter hangi yapi ciktisini tarif ediyor yapi YOK - - on iki sorunun 8'i envanterin alanlariyla cevaplaniyor, 4'u alan olmadigi icin hic cevaplanmiyor cevaplanan 8 sorunun 5'i tam, 3'i eksik alanla (lisans 9 kayitta bos, getiren 6 kayitta bos)
İlk dört soru envanteri hakkında konuşulurken en sık sayılan dörttür ve dördü de cevaplanır: bileşen bizde var, iki sürümü koşuyor, iki depoya girmiş, iki ayrı bileşen getirmiş. Beş alan sekiz soruyu kapsar; envanterin kapsama oranı 8/12’dir.
Cevaplanmayan dördü aynı sınıftandır: hiçbiri envanterin yanlış cevapladığı soru değildir, dördü de envanterin sorulamayacağı sorudur. Bileşenin koşum anında çağrılıp çağrılmadığı bir çağrı çözümlemesi ister, sürümün envantere ne zaman girdiği bir tarih alanı, paketin bakım durumu bir dış kayıt, envanterin hangi yapı çıktısını tarif ettiği ise bir bağ. Bunlar eklenebilir alanlardır ve eklenmedikçe soru sorulamaz. Bir envanterin sınırı, taşıdığı alan kümesidir.
Asıl tehlikeli sütun sonuncusudur. Cevaplanan sekiz sorunun üçü eksik alanla cevaplanır ve eksiklik cevabın biçiminde görünmez. “Kısıtlı lisanslı bileşen var mı” sorusunun yanıtı 10 kayıttır; ama lisans alanı 9 kayıtta boştur, yani gerçek sayı 10 ile 19 arasındadır. Envanter “10” der, bir aralık değil. Aynı biçimde “kim getirdi” sorusu iki ad döndürür, oysa altı kayıtta getiren kenarı hiç yazılmamıştır — o altı bileşen envanterde durur ve zincirleri kopuktur. Boş alan bir eksiklik olarak değil, küçük bir sayı olarak görünür.
Envanter Ne Zaman Üretildi
Dördüncü cevaplanmayan soru bir sonraki ölçüyü açar: envanter hangi yapı çıktısını tarif ediyor? Bağ yoksa envanterin doğruluğu tarife değil, tazeliğe kalır.
TZ5. Yirmi yapılık dizi kurgudur; her yapı iki ile beş arasında kaydı değiştirir, dörtte biri
bileşeni düşürür, kalanı sürüm yükseltir. Üretim başına 41 saniyelik hat süresi veri olarak
verilmiştir, ölçülmüş değildir; tohum 551903 görünürdür.
// olcum-agi/tazelik.mjs — envanter ne siklikta uretilirse sorgu ani kac kayitta yaniliyor import { readFileSync } from "node:fs"; import { join } from "node:path"; const KOK = join(process.env.TMPDIR ?? "/tmp", "olcum-agi-tz"); const baslangic = JSON.parse(readFileSync(join(KOK, "envanter.json"))); const uretec = (tohum) => () => (tohum = (tohum * 1103515245 + 12345) % 2147483648) / 2147483648; const r = uretec(551903); // tohum gorunur const SURUM = ["1.2", "1.9", "2.0", "2.4", "3.0", "3.6", "4.1"]; const anahtar = (k) => `${k.depo}|${k.ad}`; // Kurgu yapi dizisi: yirmi yapi. Her yapi birkac kaydi degistirir; degisim de kurgudur. const YAPI = 20, URETIM_SANIYE = 41; // sure veridir, olculmus degil const durum = new Map(baslangic.map((k) => [anahtar(k), { ...k }])); const gecmis = []; for (let y = 1; y <= YAPI; y++) { const kayitlar = [...durum.values()]; for (let i = 0, n = 2 + Math.floor(r() * 4); i < n; i++) { const k = kayitlar[Math.floor(r() * kayitlar.length)]; if (r() < 0.25) durum.delete(anahtar(k)); // bilesen dusuruldu else k.surum = SURUM[Math.floor(r() * SURUM.length)]; } gecmis.push(new Map([...durum].map(([a, k]) => [a, { ...k }]))); } // Uc uretim sikligi. Sorgu her yapidan sonra gelir; envanter en son uretilen surumdur. const SIKLIK = [["her yapida", 1], ["bes yapida bir", 5], ["yirmi yapida bir", 20]]; const S = [20, 12, 16, 18, 0]; const satir = (s) => console.log(s.map((v, i) => String(v).padEnd(S[i])).join("")); satir(["uretim sikligi", "uretim", "hat suresi", "yanlis kayit", "en kotu an"]); for (const [ad, adim] of SIKLIK) { let toplamYanlis = 0, enKotu = 0, uretim = 0; for (let y = 0; y < YAPI; y++) { if (y % adim === 0) uretim++; const envanter = gecmis[Math.floor(y / adim) * adim]; // elde duran envanter const simdi = gecmis[y]; let yanlis = 0; for (const [a, k] of simdi) if (!envanter.has(a) || envanter.get(a).surum !== k.surum) yanlis++; for (const a of envanter.keys()) if (!simdi.has(a)) yanlis++; // artik olmayan kayit toplamYanlis += yanlis; enKotu = Math.max(enKotu, yanlis); } satir([ad, uretim, `${uretim * URETIM_SANIYE} s`, `ortalama ${(toplamYanlis / YAPI).toFixed(1)}`, `${enKotu} kayit`]); } console.log(""); console.log(`yirmi yapida kayit ${baslangic.length} -> ${gecmis[YAPI - 1].size}; ` + `uretim basina ${URETIM_SANIYE} s veri olarak verildi, tohum 551903`);
uretim sikligi uretim hat suresi yanlis kayit en kotu an her yapida 20 820 s ortalama 0.0 0 kayit bes yapida bir 4 164 s ortalama 6.2 17 kayit yirmi yapida bir 1 41 s ortalama 22.1 41 kayit yirmi yapida kayit 73 -> 56; uretim basina 41 s veri olarak verildi, tohum 551903
Her yapıda üretilen envanter hiçbir sorguda yanılmaz ve hattan 820 saniye alır. Beş yapıda bir üretilen envanter 164 saniye alır ve ortalama 6,2 kayıtta yanılır; en kötü anda 17 kayıt yanlıştır. Yirmi yapıda bir üretilen envanter 41 saniyeye iner ve ortalama 22,1 kayıtta yanılır — elli altı kayıtlık bir kümede.
Üç satır aynı kapının üç ayarıdır ve hattan alınan süreyle yanlış cevap sayısı ters orantılı değil, basamaklıdır: sıklık beşte bire indiğinde süre beşte bire iner, yanlış kayıt ise sıfırdan 6,2’ye çıkar. İlk basamak en pahalı olanıdır, çünkü sıfırdan farklı her sayı envanterin verdiği cevabı bir kestirime çevirir.
Son satır ölçünün kendi sınırını da yazar: yirmi yapıda kayıt sayısı 73’ten 56’ya iner. Eski bir envanter yalnız eksik değildir, fazladır da — artık kullanılmayan on yedi bileşeni koşuyormuş gibi gösterir. Bu iki hata birbirini götürmez; ikisi de aynı sütunda sayılır ve ikisi de aynı sonucu verir: envanterin verdiği sayı, tarif ettiğini iddia ettiği şeyi tarif etmez.
Özet
- Üç depo 26 doğrudan bağımlılık bildirir, envanter 73 kayıt ve 48 benzersiz bileşen üretir; 47 kayıt hiçbir ekibin seçmediği geçişli bileşendir.
- Envanterin beş alanından üçü her kayıtta doludur; lisans 9, getiren 6 kayıtta boştur. Boş alan yokluk değil, kaydedilmemiş değerdir.
- On iki sorunun 8’i envanterin alanlarıyla cevaplanır, 4’ü hiç cevaplanmaz: çağrılma, envantere giriş tarihi, paketin bakım durumu ve envanterin hangi yapı çıktısına ait olduğu. Bir envanterin sınırı, taşıdığı alan kümesidir.
- Cevaplanan 8 sorunun 3’ü eksik alanla cevaplanır ve eksiklik cevabın biçiminde görünmez: “10 kayıt” yanıtının gerçeği 10 ile 19 arasındadır.
- Her yapıda üretim 820 saniye alır ve hiç yanılmaz; beş yapıda bir üretim 164 saniye alır, ortalama 6,2 kayıtta yanılır; yirmi yapıda bir üretim 41 saniyeye iner, ortalama 22,1 kayıtta yanılır. Eski envanter yalnız eksik değil, düşürülen on yedi bileşen kadar da fazladır.
Sonraki Adım
Envanter üretildi, kapsamı sayıldı ve tazeliğinin bedeli yazıldı. Cevaplanmayan dördüncü soru ise yerinde duruyor: bu envanter hangi yapı çıktısını tarif ediyor? Envanter ayrı bir dosyadır; yanına başka bir envanter konabilir, bir kaydı değiştirilebilir, bambaşka bir çıktıyla eşleştirilebilir. 73 kaydın hiçbiri, tarif ettiği baytların gerçekten o baytlar olduğunu söylemez.
Sonraki ders bu bağı gerçek bir anahtar çiftiyle kurar ve tek bir soruyu ikiye ayırır: imza neyi kanıtlar, neyi kanıtlamaz? Doğrulaması geçen bir çıktı için kaç ayrı iddia ileri sürülebilir ve bunların kaçı imzanın gerçekten dayandığı iddiadır — ve o anahtar bir kez ele geçtiğinde geriye dönük olarak kaç çıktı şüpheli hale gelir?
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.