İçeriğe geç
academia.sh

Ders 18 / 27

Modül Yazımı

Üç toplama noktasında ayrı ayrı yazılmış aynı kayıt tek bir modüle çıkarılır; girdi yüzeyi, satır kazancı ve altı sürüm boyunca büyüyen esneklik bedeli sayılır. Modülü yazan tarafın hiç görmediği dört kalem de ölçülür.

İçindekiler

Bir önceki ders aynı tanımı birden çok ortamda koşturdu: çalışma alanı ayrımı durumu böldü, ortamlar birbirinin kaynağına dokunmadan aynı metinden üretildi. Tanımın kendisi ise hâlâ tek parça. Bölgesel ölçüm ağının üç toplama noktası aynı üç kaynağı içeriyor ve her biri kendi kayıt bloğunu baştan sona taşıyor; ortak hiçbir şey ortak bir yerde durmuyor.

Modül, birlikte oluşturulup birlikte silinen bir kaynak kümesine ad veren, arayüzü girdi ve çıktı olan yapılandırma parçasıdır. Sözcük yazılım paketlerindeki modül ile aynıdır; bağlam farkı şu: orada arayüz çağrılabilir işlevlerdi ve üretilen şey davranıştı, burada arayüz bir değişken listesi, üretilen şey kaynağın kendisi.

Ölçüm yine bölgesel ölçüm ağı üzerindedir: su sayacı okumalarını toplayan kurgu bir yazılım. Gerçek altyapı sağlanmaz; ölçülen şey node ile kurulmuş bir modeldir.

DM25. Üç toplama noktası vardır; her biri üç kaynaktan oluşur ve üç kayıt ayrı ayrı elle yazılmıştır. DM26. Yapılandırma dili bu ders için tanımlanır: kaynak <tür> <ad> { alan = değer }, girdi <ad> [= varsayılan], cikti <ad> = <değer>, modul <tür> <ad> { alan = değer }. $ad çağrının adına bağlıdır, değeri yok olan alan üretilmez, eger yazan kaynak koşulludur. DM27. Modül elle yazılır; hangi alanın arayüze çıkacağına yazan karar verir. Kod modülü üç kayda karşı sınar ve kayıtta değişen alanlarla arayüzü karşılaştırır. DM28. Çağrıya yalnız zorunlu girdilerle varsayılandan sapan alanlar yazılır. DM29. Gerçekte dört kalem elle değiştirilmiştir ve hiçbir kayıtta yoktur; dördü de yazılıdır, rastgelelik yoktur. DM30. Modül beş yeni kullanımla altı sürüme çıkar; her sürümün değişikliği önceden yazılıdır ve açılan sekiz kullanımdan biri sonradan kapatılır.

Üç Yerde Aynı Kayıt

// kayit.mjs — bolgesel olcum aginin uc toplama noktasi; ucu de ayri ayri elle yazilmis kayit.
export const KAYIT = `# nokta kuzey
kaynak sanal-makine-kumesi kuzey-toplayici {
  sayi = 4
  imaj = toplayici-4.2.0
  bolge = kuzey-1
}
kaynak disk kuzey-tampon {
  boyut-gb = 200
  baglanan = kuzey-toplayici
}
kaynak ag-kurali kuzey-giris {
  kaynak-blok = 10.20.0.0/16
  kapi = 8080
  yon = giris
}
# nokta bati
kaynak sanal-makine-kumesi bati-toplayici {
  sayi = 6
  imaj = toplayici-4.2.0
  bolge = bati-1
}
kaynak disk bati-tampon {
  boyut-gb = 500
  baglanan = bati-toplayici
}
kaynak ag-kurali bati-giris {
  kaynak-blok = 10.30.0.0/16
  kapi = 8080
  yon = giris
}
# nokta dogu
kaynak sanal-makine-kumesi dogu-toplayici {
  sayi = 6
  imaj = toplayici-4.2.0
  bolge = dogu-1
}
kaynak disk dogu-tampon {
  boyut-gb = 500
  baglanan = dogu-toplayici
}
kaynak ag-kurali dogu-giris {
  kaynak-blok = 10.40.0.0/16
  kapi = 8443
  yon = giris
}`;

export const NOKTA = ["kuzey", "bati", "dogu"];
export const ELLE = [                                  // gerceginde var, hicbir kayitta yok
  ["bati", "disk", "boyut-gb", "750"],                 // disk elle buyutuldu
  ["kuzey", "sanal-makine-kumesi", "sayi", "6"],       // kume elle olceklendi
  ["dogu", "ag-kurali", "ek-kapi", "9443"],            // kayitta olmayan alan elle eklendi
  ["kuzey", "nesne-deposu", "*", "kuzey-arsiv"]];      // kayit disi kaynak elle acildi

Kayıt kırk iki satırdır ve her noktada sekiz alan yuvası vardır. ELLE listesi kaydın yanında duran ikinci gerçekliktir: kayıtta yazmayan ama sahada var olan dört kalem. Modülü yazan taraf bu dördün kaçını görür — ölçülecek şey budur.

Modülün Girdi Yüzeyi

// modul.mjs — elle yazilmis modul uc kayda karsi sinanir; girdi yuzeyi, satir kazanci ve kayit-gercek farki.
import { KAYIT, NOKTA, ELLE } from "./kayit.mjs";

export const satir = (t) => t.split("\n").filter((s) => s.trim()).length;
export const METIN = Object.fromEntries(KAYIT.split(/^# nokta /m).filter(Boolean)
  .map((p) => [p.split("\n")[0], p.split("\n").slice(1).join("\n").trim()]));
export const K = Object.fromEntries(NOKTA.map((n) => [n, METIN[n].split("\n").reduce((b, s) => {
  const k = s.match(/^kaynak (\S+) (\S+) \{$/), f = s.match(/^ {2}([\w-]+) = (.+)$/);
  if (k) b.push({ tur: k[1], ad: k[2], alan: [] }); else if (f) b.at(-1).alan.push([f[1], f[2]]);
  return b;
}, [])]));
const D = Object.fromEntries(NOKTA.map((n) => [n, Object.fromEntries(K[n].flatMap((b) => b.alan))]));

export const MODUL = `girdi kaynak-blok
girdi sayi = 6
girdi boyut-gb = 500
girdi kapi = 8080
kaynak sanal-makine-kumesi $ad-toplayici {
  sayi = $sayi
  imaj = toplayici-4.2.0
  bolge = $ad-1
}
kaynak disk $ad-tampon {
  boyut-gb = $boyut-gb
  baglanan = $ad-toplayici
}
kaynak ag-kurali $ad-giris {
  kaynak-blok = $kaynak-blok
  kapi = $kapi
  yon = giris
}
cikti tampon-adi = $ad-tampon
cikti giris-kapisi = $kapi`;                           // elle yazildi; cikti secimi kayitta yok

export const uret = (mod, ad, arg) => {                // $ad cagri adidir; degeri `yok` olan alan yazilmaz
  const isim = [...mod.matchAll(/^girdi ([\w-]+)/gm)].map((m) => m[1]).sort((a, b) => b.length - a.length);
  const c = []; let atla = false;
  for (const s of mod.split("\n")) {
    if (/^(girdi|cikti) /.test(s)) continue;
    let t = s.replaceAll("$ad", ad);
    for (const i of isim) t = t.replaceAll("$" + i, arg[i]);
    const k = t.match(/ eger (\S+) \{$/);
    if (k) { atla = k[1] === "hayir"; t = t.replace(/ eger \S+ \{$/, " {"); }
    else if (t === "}" && atla) { atla = false; continue; }
    if (!atla && !/ = yok$/.test(t)) c.push(t);
  }
  return c.join("\n");
};
export const zorunlu = (m) => [...m.matchAll(/^girdi ([\w-]+)$/gm)].map((x) => x[1]);
export const varsayilan = (m) => Object.fromEntries([...m.matchAll(/^girdi ([\w-]+) = (.+)$/gm)].map((x) => [x[1], x[2]]));
export const gecen = (m, n) => {                       // cagriya zorunlu ve varsayilandan sapan alan yazilir
  const v = varsayilan(m);
  return Object.fromEntries([...zorunlu(m), ...Object.keys(v)].filter((a) => D[n][a] !== v[a]).map((a) => [a, D[n][a]]));
};
export const cs = (m, n) => Object.keys(gecen(m, n)).length + 2;   // cagrinin satir sayisi

if (import.meta.main) {                                // surum.mjs iceri aktarinca bu bolum kosmaz
  const alan = Object.keys(D[NOKTA[0]]), v = varsayilan(MODUL);
  const degis = alan.filter((a) => new Set(NOKTA.map((n) => D[n][a])).size > 1);
  const ad = [...zorunlu(MODUL), ...Object.keys(v)], disi = degis.filter((a) => !ad.includes(a));
  const ayni = NOKTA.filter((n) => uret(MODUL, n, { ...v, ...gecen(MODUL, n) }) === METIN[n]).length;
  const el = NOKTA.reduce((t, n) => t + satir(METIN[n]), 0), c = NOKTA.map((n) => cs(MODUL, n));
  const t = c.reduce((x, y) => x + y);
  console.log(`alan yuvasi ${alan.length} | degisen ${degis} | sabit ${alan.length - degis.length}` +
    ` | girdi yuzeyi ${ad.length} = zorunlu ${zorunlu(MODUL)} + varsayilanli ${Object.keys(v)}`);
  console.log(`degisip arayuzde olmayan ${disi.length} (${disi}) | modul ${satir(MODUL)} = ${ad.length}+` +
    `${satir(MODUL) - ad.length - 2}+2 | elle ${el} | modul yolu ${satir(MODUL)}+${t}=${satir(MODUL) + t}` +
    ` | kazanc ${el - satir(MODUL) - t} | cagri ${c} | asliyla ayni ${ayni}/3`);
  let kap = 0;
  for (const [n, tur, a, d] of ELLE) {                 // kayit ile gercek arasindaki fark
    const b = K[n].find((x) => x.tur === tur), y = b?.alan.find(([x]) => x === a);
    console.log(`  ${n.padEnd(6)} ` + (!b ? `kayit disi kaynak ${d}` : !y ? `kayitta olmayan alan ${a} = ${d}`
      : `deger farki ${a}: kayit ${y[1]}, gercek ${d}`).padEnd(44) + (b ? "kapsamda" : "kapsam disi"));
    if (b) kap++;
  }
  console.log(`kayitta yazmayan kalem ${ELLE.length} | modulu yazanin gordugu 0 | geri alinacak ${kap}` +
    ` | kayitsiz kalan ${ELLE.length - kap}`);
}
alan yuvasi 8 | degisen sayi,bolge,boyut-gb,baglanan,kaynak-blok,kapi | sabit 2 | girdi yuzeyi 4 = zorunlu kaynak-blok + varsayilanli sayi,boyut-gb,kapi
degisip arayuzde olmayan 2 (bolge,baglanan) | modul 20 = 4+14+2 | elle 42 | modul yolu 20+12=32 | kazanc 10 | cagri 5,3,4 | asliyla ayni 3/3
  bati   deger farki boyut-gb: kayit 500, gercek 750 kapsamda
  kuzey  deger farki sayi: kayit 4, gercek 6         kapsamda
  dogu   kayitta olmayan alan ek-kapi = 9443         kapsamda
  kuzey  kayit disi kaynak kuzey-arsiv               kapsam disi
kayitta yazmayan kalem 4 | modulu yazanin gordugu 0 | geri alinacak 3 | kayitsiz kalan 1

Girdi yüzeyi dörttür: bir zorunlu, üç varsayılanlı. Modül yirmi satırdır; dördü arayüz bildirimi, on dördü gövde, ikisi çıktıdır.

İlk satır arayüzün eksik yanını sayar. Sekiz alan yuvasının altısı üç kayıt arasında değişiyor ama arayüzde yalnız dördü var; değişip arayüzde olmayan iki alan bolge ile baglanan. İkisi de $ad üzerinden yazıldığı için ölçüm onları ayırt edemiyor. Oysa baglanan haklı olarak dışarıdadır: bir bağımlılık adıdır, modülün kendi ürettiği kaynağa işaret eder. bolge öyle değil — bölge değerinin nokta adıyla başlaması bir kural değil, üç örnekte tutan bir kalıptır. Modülü yazan taraf kalıba güvenip alanı arayüzden sakladı; bedeli bir sonraki bölümde ödenir.

Ters yönde cikti durur. Hangi kaynağın adının dışarıya verileceği üç kaydın hiçbir yerinde yazmaz. Girdi kaydın farkından çıkarılabilir, çıktı çıkarılamaz.

Çağrı satırları beş, üç ve dörttür. Batı üç satırla çağrılıyor çünkü üç varsayılanın üçü de batının kaydına uyuyor; varsayılan seçimi çoğunluğu ödüllendirir, azınlıkta kalana bir satır fazladan yazdırır. Satır kazancı kırk ikiden otuz ikiye, yani yüzde yirmi dörttür — üç kullanımda tek başına modül yazdıracak bir sayı değil.

Kaydın Kapsamadığı Dört Kalem

Modül üç kaydı birebir yeniden üretti, ama gerçekte var olan dört kalemin sıfırını gördü: bakılan yer kayıttı. İkisi değer farkıdır — batının diski beş yüz yerine yedi yüz elli gigabayt, kuzeyin makine kümesi dört yerine altı. Biri kayıtta hiç olmayan alandır: doğunun ağ kuralına elle ikinci bir kapı açılmış. Sonuncusu kayıt dışı kaynaktır: kuzeydeki kuzey-arsiv nesne deposundan hiçbir kayıt söz etmiyor.

Modülün kapsamı bu dördü ikiye böler. Üçü modülün ürettiği blokların içindedir; uygulandığı anda disk beş yüze, makine sayısı dörde döner ve fazladan kapı silinir — modülleştirme bu üç kalemi görmedi ama uygulama onları sessizce geri alacak. Dördüncüsü modülün hiç bilmediği bir kaynak türündedir; modül onu ne üretir ne siler. Modülleştirme sapmayı azaltmaz: kapsadığını tek bir yerden geri alınabilir kılar, kapsamadığını olduğu yerde bırakır.

Esneklik Bedeli

// surum.mjs — modul bes yeni kullanimla alti surume cikar; girdi yuzeyi ve esneklik bedeli sayilir.
import { MODUL, uret, satir, zorunlu, varsayilan } from "./modul.mjs";

const ek = (m, ...s) => m.replace(/^(girdi .*)$(?![\s\S]*^girdi )/m, ["$1", ...s].join("\n"));
const SURUM = [                                        // her surumun getirdigi degisiklik onceden yazili
  ["v1", "elle yazildi", ["kuzey", "bati", "dogu"], (m) => m],
  ["v2", "bolge adi kalibi kirildi", ["guney"], (m) =>
    ek(m.replace("  bolge = $ad-1", "  bolge = $bolge"), "girdi bolge")],
  ["v3", "disk istemeyen kullanim", ["sinama-alani"], (m) =>
    ek(m.replace("k disk $ad-tampon {", "k disk $ad-tampon eger $disk-var {"), "girdi disk-var = evet")],
  ["v4", "cikis yonu ve ikinci kapi", ["saha-laboratuvari"], (m) => ek(m.replace("  yon = giris", "  yon = $yon")
    .replace("  kapi = $kapi\n", "  kapi = $kapi\n  ek-kapi = $ek-kapi\n"), "girdi yon = giris", "girdi ek-kapi = yok")],
  ["v5", "eski imaji isteyen kullanim", ["arsiv-nokta"], (m) =>
    ek(m.replace("  imaj = toplayici-4.2.0", "  imaj = $imaj"), "girdi imaj = toplayici-4.2.0")],
  ["v6", "makine etiketi", ["ic-anadolu"], (m) =>
    ek(m.replace("  sayi = $sayi\n", "  sayi = $sayi\n  etiket = $etiket\n"), "girdi etiket = yok")]];

const HAM = {                                          // her kullanimin module gectigi degerler
  kuzey: "kaynak-blok=10.20.0.0/16 bolge=kuzey-1 sayi=4 boyut-gb=200",
  bati: "kaynak-blok=10.30.0.0/16 bolge=bati-1",
  dogu: "kaynak-blok=10.40.0.0/16 bolge=dogu-1 kapi=8443",
  guney: "kaynak-blok=10.50.0.0/16 bolge=guney-2",
  "sinama-alani": "kaynak-blok=10.60.0.0/16 bolge=sinama-1 disk-var=hayir sayi=1",
  "saha-laboratuvari": "kaynak-blok=10.70.0.0/16 bolge=saha-1 yon=cikis ek-kapi=9443 sayi=1",
  "arsiv-nokta": "kaynak-blok=10.80.0.0/16 bolge=arsiv-1 imaj=toplayici-3.9.0 boyut-gb=2000",
  "ic-anadolu": "kaynak-blok=10.90.0.0/16 bolge=ic-1 etiket=pilot sayi=2" };
const G = Object.fromEntries(Object.entries(HAM).map(([k, v]) =>
  [k, Object.fromEntries(v.split(" ").map((x) => x.split("=")))]));

let m = MODUL, acik = [], onceki = 1;
console.log("surum  degisiklik                   kullanim  girdi (zorunlu/varsayilanli)  modul satir  kirici duzeltme");
for (const [s, not, yeni, d] of SURUM) {
  m = d(m);
  const zor = zorunlu(m).length, top = zor + Object.keys(varsayilan(m)).length;
  const kirici = zor > onceki ? acik.length : 0;       // yeni zorunlu girdi eski cagrilari kirar
  acik = [...acik, ...yeni];
  console.log(`${s}     ${not.padEnd(28)} ${String(acik.length).padEnd(9)} ${`${top} (${zor}/${top - zor})`.padEnd(28)} ` +
    `${String(satir(m)).padEnd(12)} ${kirici ? `${kirici} tanim, ${kirici} satir` : "-"}`);
  onceki = zor;
}
acik = acik.filter((a) => a !== "arsiv-nokta");        // bir kullanim kapandi, actirdigi girdi modulde kaldi
const vd = varsayilan(m), vars = Object.keys(vd);
const kac = (a) => acik.filter((k) => a in G[k]).length;
const olu = vars.filter((a) => kac(a) === 0), tek = vars.filter((a) => kac(a) === 1);
const el = acik.reduce((t, a) => t + satir(uret(m, a, { ...vd, ...G[a] })), 0);
const cs = acik.reduce((t, a) => t + Object.keys(G[a]).length + 2, 0);

console.log(`acilan 8, kapanan 1, acik ${acik.length} | modulu degistirmeden karsilanan 0/5 | girdi yuzeyi 4 -> ` +
  `${vars.length + 2}, kullanim basina ${((vars.length - 2) / 5).toFixed(1)} | elle ${el} | modul yolu ` +
  `${satir(m)}+${cs}=${satir(m) + cs} | kazanc ${el - satir(m) - cs}`);
console.log(`varsayilanli girdi ${vars.length}: iki ve uzeri ${vars.length - tek.length - olu.length}` +
  `, tek kullanim ${tek.length} (${tek}), olu ${olu.length} (${olu})`);
surum  degisiklik                   kullanim  girdi (zorunlu/varsayilanli)  modul satir  kirici duzeltme
v1     elle yazildi                 3         4 (1/3)                      20           -
v2     bolge adi kalibi kirildi     4         5 (2/3)                      21           3 tanim, 3 satir
v3     disk istemeyen kullanim      5         6 (2/4)                      22           -
v4     cikis yonu ve ikinci kapi    6         8 (2/6)                      25           -
v5     eski imaji isteyen kullanim  7         9 (2/7)                      26           -
v6     makine etiketi               8         10 (2/8)                     28           -
acilan 8, kapanan 1, acik 7 | modulu degistirmeden karsilanan 0/5 | girdi yuzeyi 4 -> 10, kullanim basina 1.2 | elle 96 | modul yolu 28+38=66 | kazanc 30
varsayilanli girdi 8: iki ve uzeri 1, tek kullanim 6 (boyut-gb,kapi,disk-var,yon,ek-kapi,etiket), olu 1 (imaj)

Esnekliğin bedeli tek satırda okunur: beş yeni kullanımın sıfırı modülü değiştirmeden karşılandı. Her yeni kullanım arayüzde yeni bir girdi açtırdı; girdi yüzeyi dörtten ona, modül metni yirmiden yirmi sekiz satıra çıktı. Ölçülen hız kullanım başına 1,2 girdidir.

İkinci sürümdeki kirici duzeltme sütunu bedelin en görünür biçimidir. bolge alanının ad kalıbı dördüncü noktada kırılınca alan zorunlu girdiye çıktı ve o anda var olan üç tanımın üçü birden geçersiz oldu. Yeni bir zorunlu girdi açmanın yükü modülü yazanın değil, kullananların üstündedir.

Son satır esnekliğin kime hizmet ettiğini sayar. Sekiz varsayılanlı girdinin yalnız biri iki ya da daha çok kullanım tarafından geçiliyor; altısı tek bir kullanım için var, biri hiçbiri için. Ölü girdi olan imaj beşinci sürümde arşiv noktası eski bir imaj istediği için açıldı; arşiv noktası kapandı, girdi modülde kaldı. Bir girdiyi kaldırmak kullanan her tanımı ilgilendirdiği için kimse kaldırmaya girişmez.

Kazanç yine de büyüdü. Yedi açık kullanım elle yazılsaydı doksan altı satır tutardı; modül yolu altmış altı satır ve kazanç yüzde otuz bir. Kazanç kullanım sayısıyla büyür, arayüz de büyür, ikisi aynı hızda gitmez.

Özet

  • Elle yazılan modülün girdi yüzeyi 4’tür: 1 zorunlu, 3 varsayılanlı; 20 satırın 4’ü arayüz bildirimi, 2’si çıktıdır ve çıktı seçimi kayıttan okunamaz.
  • 8 alan yuvasının 6’sı üç kayıt arasında değişiyor ama arayüzde 4’ü var; bolge bir ad kalıbına güvenildiği için dışarıda kaldı.
  • Satır kazancı üç kullanımda 42’den 32’ye (yüzde 24), yedi kullanımda 96’dan 66’ya (yüzde 31) çıktı.
  • Modül üç kaydı birebir yeniden üretti (3/3) ama gerçekte var olan 4 kalemin 0’ını gördü; uygulamada 3’ü sessizce geri alınır, kayıt dışı kuzey-arsiv kayıtsız kalır.
  • 5 yeni kullanımın 0’ı modülü değiştirmeden karşılandı; girdi yüzeyi 4’ten 10’a çıktı, 8 varsayılanlı girdinin 6’sı tek kullanıma, 1’i hiçbirine hizmet ediyor.
  • Yeni bir zorunlu girdi açmak kırıcıdır: ikinci sürüm o anki 3 tanımın 3’ünde 1’er satır düzeltme istedi; bu yük kullananların üstündedir.

Sonraki Adım

Bu modülün altı sürümü de aynı depoda, aynı elin altında durdu; yazan da kullanan da aynı taraftı. Modüller paylaşılan bir yerden çekilmeye başladığında bu ilişki kopar: yükseltme kararı bir yerde alınır, başka yerlerde hissedilir. Bir sonraki ders sürümü sabitlenmiş ve sabitlenmemiş iki düzeni bir dönem boyunca koşturur ve şunu sayar: tanım dosyası hiç değişmediği hâlde kaç uygulama farklı sonuç üretiyor, bir yükseltme kaç kullanıcıya dokunuyor, kırıcı bir değişiklik kaç tanımda kaç satır düzeltme istiyor. Sabitlemenin bedeli de aynı yerde ölçülür: kaç modül eski sürümde kalıyor ve kaç düzeltmeyi hiç almıyor.

İ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