İçeriğe geç
academia.sh

Ders 20 / 21

Geliştirici Deneyimi

İki geri bildirim döngüsünün değişiklik başına karşılaştırması: imajı yeniden üretmenin geçersizleştirdiği katman, kopyaladığı bayt ve adım sayısı ile kaynağı bağlayıp süreci yeniden yüklemenin sıfırladığı bu üç sayı, ve bağlamanın bedeli olan ortam uzaklığı.

İçindekiler

Önceki ders dört servisi bir kez ayağa kaldırdı ve orada bıraktı. Yerel ortamın asıl kullanımı ise tek bir başlatma değil, gün boyunca tekrarlanan bir döngüdür: bir satır değiştirilir, sonucu görülür, bir satır daha değiştirilir. Geri bildirim döngüsü bu müfredatın ilk kursunda ölçülmüş bir niceliktir; o sayım burada tekrarlanmaz. Buradaki soru döngünün süresi değil, konteynerli bir düzende döngünün iki ayrı biçimi olması ve ikisinin farklı şeyler ödemesidir. Birinci biçim imajı yeniden üretip konteyneri yeniden başlatır. İkinci biçim kaynak dizinini konteynere bağlar ve süreci yerinde yeniden yükler. Ölçülen şey, değişiklik başına her birinin ne kadar bayt kopyaladığı, kaç katmanı geçersizleştirdiği, kaç adım sürdüğü — ve ikincinin bunun karşılığında neyi bozduğudur.

Ölçüm nesnesi bölgesel ölçüm ağının okuma toplayıcı servisidir; kurgu bir yazılımdır, kaynak ağacı gerçekten diske yazılır ve bayt boyutları ile içerik özetleri gerçek dosyalardan okunur.

CZ37 — imaj tanımı beş adımdan oluşur: temel katman, bağımlılık bildirimi, bağımlılık kurulumu, kaynak kopyalama, derleme. CZ38 — temel katman 41.943.040, bağımlılık kurulumu 18.874.368 bayttır; bu iki sayı modeldir, geri kalan bayt gerçek dosyalardan gelir. CZ39 — bir adımın girdisi değişirse o adım ve kendinden sonraki bütün adımlar geçersizleşir. CZ40 — yok sayma kuralları beş kalıptır ve bunlara uyan dosya imaja girmez. CZ41 — bağlama biçiminde kaynak dizininin tamamı takılır, yok sayma kuralları uygulanmaz ve kopyalama olmaz. CZ42 — geri bildirim adımı, dosya yazıldıktan sonra sonucun görülmesine kadarki ayrık adım sayısıdır.

Katman Zinciri ve İki Küme

İlk koşum ağacı kurar, hangi dosyanın imaja girdiğini yok sayma kurallarıyla belirler ve beş adımın zincirini hesaplar. Her adımın özeti bir önceki özete ve kendi girdilerinin içeriğine bağlıdır; zincir bu yüzden bir yerden koptuğunda arkasındaki her şey kopar.

// kaynak.mjs — gercek kaynak agaci, gercek bayt boyutlari, gercek icerik ozetleri.
// Dosyalar ve boyutlari gercektir; imaj adimlari ile temel katman/kurulum bayti MODELDIR.
import { mkdirSync, writeFileSync, rmSync, statSync, readFileSync } from 'node:fs';
import { createHash } from 'node:crypto';

export const KOK = './kaynak';

export const DOSYALAR = {
  'bagimlilik.json': '{"ad":"okuma-toplayici","surum":"7.2.0",'
    + '"bagimlilik":{"cozumleyici":"2.4.1","kayit-istemci":"1.9.3","zaman":"4.0.2"}}\n',
  'uygulama/baslat.mjs': "import { topla } from './toplayici.mjs';\n"
    + "import { AYAR } from './ayar.local.mjs';\ntopla(AYAR.kaynak);\n",
  'uygulama/toplayici.mjs': "import { bicimle } from './bicim.mjs';\n"
    + 'export const topla = (k) => bicimle(k);\n',
  'uygulama/dogrulayici.mjs': "import { bicimle } from './bicim.mjs';\n"
    + 'export const dogrula = (o) => bicimle(o).length > 0;\n',
  'uygulama/bicim.mjs': 'export const bicimle = (x) => String(x).trim();\n',
  'uygulama/ayar.local.mjs': "export const AYAR = { kaynak: 'yerel-sayac', gunluk: 'ayrintili' };\n",
  'uygulama/ornek-olcum.json': '[{"sayac":"S-001","deger":1420},{"sayac":"S-002","deger":980}]\n',
  'sinama/toplayici.sinama.mjs': "import { topla } from '../uygulama/toplayici.mjs';\n"
    + "console.log(topla(' 12 '));\n",
  'notlar.md': '# yerel notlar\n- sayac verisi ornek dosyadan okunuyor\n',
  '.gizli-ayar': 'depo-belirteci=yerel-gelistirme\n',
};

// CZ40: imaja alinmayanlar. Baglama bicimi bu kurallari uygulamaz, dizinin tamamini takar.
export const YOK_SAY = [/^sinama\//, /\.local\./, /(^|\/)ornek-/, /^notlar\.md$/, /(^|\/)\.gizli-/];
export const imajda = (yol) => !YOK_SAY.some((r) => r.test(yol));

// CZ37/CZ38: bes adim. cikti verilmisse MODEL bayt, verilmemisse girdilerin gercek bayti.
export const ADIMLAR = [
  { ad: 'temel katman', onek: [], cikti: 41943040 },
  { ad: 'bagimlilik bildirimi', onek: ['bagimlilik.json'] },
  { ad: 'bagimlilik kurulumu', onek: ['bagimlilik.json'], cikti: 18874368 },
  { ad: 'kaynak kopyalama', onek: ['uygulama/'] },
  { ad: 'derleme', onek: ['uygulama/'], derle: true },
];

export function kur() {
  rmSync(KOK, { recursive: true, force: true });
  for (const [yol, icerik] of Object.entries(DOSYALAR)) {
    const dizin = yol.includes('/') ? `${KOK}/${yol.slice(0, yol.lastIndexOf('/'))}` : KOK;
    mkdirSync(dizin, { recursive: true });
    writeFileSync(`${KOK}/${yol}`, icerik);
  }
  return Object.keys(DOSYALAR).map((yol) => ({ yol, bayt: statSync(`${KOK}/${yol}`).size }));
}

const ozetle = (m) => createHash('sha256').update(m).digest('hex');
const girdileri = (adim, agac) => agac.filter((d) => imajda(d.yol)
  && adim.onek.some((o) => d.yol === o || d.yol.startsWith(o)));

// Derleme adimi gercekten kosar: imaja giren kaynak modulleri tek cikti dosyasinda birlestirilir.
export function derle(agac) {
  const parcalar = girdileri(ADIMLAR[4], agac).filter((d) => d.yol.endsWith('.mjs'))
    .map((d) => readFileSync(`${KOK}/${d.yol}`, 'utf8'));
  mkdirSync(`${KOK}/dagitim`, { recursive: true });
  writeFileSync(`${KOK}/dagitim/paket.mjs`, parcalar.join('\n'));
  return statSync(`${KOK}/dagitim/paket.mjs`).size;
}

// Katman zinciri: her adimin ozeti bir onceki ozete ve kendi girdilerinin icerigine baglidir.
export function zincir(agac) {
  let onceki = '0'.repeat(64);
  return ADIMLAR.map((adim) => {
    const girdi = girdileri(adim, agac);
    const icerik = girdi.map((d) => ozetle(readFileSync(`${KOK}/${d.yol}`))).join('');
    onceki = ozetle(onceki + adim.ad + icerik);
    const bayt = adim.cikti ?? (adim.derle ? derle(agac) : girdi.reduce((a, d) => a + d.bayt, 0));
    return { ad: adim.ad, girdi: girdi.length, bayt, ozet: onceki.slice(0, 12) };
  });
}

if (import.meta.url === `file://${process.argv[1]}`) {
  const agac = kur();
  const s = (x, n) => String(x).padStart(n);
  const t = (x) => x.toLocaleString('tr-TR');
  console.log(`${'dosya'.padEnd(28)}${s('bayt', 6)}${s('imajda', 8)}`);
  for (const d of agac) console.log(`${d.yol.padEnd(28)}${s(d.bayt, 6)}${s(imajda(d.yol) ? 'evet' : 'hayir', 8)}`);
  const imaj = agac.filter((d) => imajda(d.yol));
  console.log(`baglanan kume ${agac.length} dosya / ${t(agac.reduce((a, d) => a + d.bayt, 0))} bayt`
    + `   imaj kumesi ${imaj.length} dosya / ${t(imaj.reduce((a, d) => a + d.bayt, 0))} bayt`);

  console.log(`\n${'adim'.padEnd(22)}${s('girdi', 6)}${s('cikti bayt', 12)}${s('ozet', 15)}`);
  for (const k of zincir(agac)) {
    console.log(`${k.ad.padEnd(22)}${s(k.girdi, 6)}${s(t(k.bayt), 12)}${s(k.ozet, 15)}`);
  }
}
dosya                         bayt  imajda
bagimlilik.json                118    evet
uygulama/baslat.mjs            102    evet
uygulama/toplayici.mjs          79    evet
uygulama/dogrulayici.mjs        92    evet
uygulama/bicim.mjs              48    evet
uygulama/ayar.local.mjs         68   hayir
uygulama/ornek-olcum.json       63   hayir
sinama/toplayici.sinama.mjs     79   hayir
notlar.md                       54   hayir
.gizli-ayar                     32   hayir
baglanan kume 10 dosya / 735 bayt   imaj kumesi 5 dosya / 439 bayt

adim                   girdi  cikti bayt           ozet
temel katman               0  41.943.040   40f844bcb047
bagimlilik bildirimi       1         118   f52d32c295f8
bagimlilik kurulumu        1  18.874.368   a884990dc6c3
kaynak kopyalama           4         321   e7d548395511
derleme                    4         324   c4729347b567

Tablonun iki yarısı arasındaki oran bu dersin bütün ölçüsünü belirliyor. Kaynak dört dosya ve 321 bayttır; onun altındaki iki adım 60.817.408 bayttır, yani kaynağın yaklaşık yüz doksan bin katı. Değişikliklerin çoğu kaynağa gelir, bedelin çoğu ise kaynağın altında durur. Katman zincirinin bütün anlamı budur: değişen adımın üstündekiler yeniden üretilmez.

Değişiklik Başına İki Döngü

İkinci koşum üç değişiklik sınıfını gerçekten dosyaya yazar, zinciri yeniden hesaplar ve hangi adımların özetinin değiştiğini karşılaştırır. Her sınıf iki biçimde ölçülür.

// dongu.mjs — iki geri bildirim dongusu: imaji yeniden uretmek ile kaynagi baglayip yeniden yuklemek.
import { readFileSync, writeFileSync, statSync } from 'node:fs';
import { KOK, DOSYALAR, ADIMLAR, imajda, kur, zincir, derle } from './kaynak.mjs';

const agacOku = () => Object.keys(DOSYALAR).map((yol) => ({ yol, bayt: statSync(`${KOK}/${yol}`).size }));

const DEGISIKLIK = {
  'uygulama/bicim.mjs': DOSYALAR['uygulama/bicim.mjs'].replace('trim()', 'trim().slice(0, 64)'),
  'bagimlilik.json': DOSYALAR['bagimlilik.json'].replace('2.4.1', '2.5.0'),
  'notlar.md': `${DOSYALAR['notlar.md']}- yok sayma listesi gozden gecirilecek\n`,
};

// CZ42: geri bildirim adimi = dosya yazildiktan sonra sonucun gorulmesine kadarki ayrik adim.
const uretimAdimi = (g) => 1 + (g === 0 ? 1 : g + 3);   // yaz + adimlar + durdur + baslat + hazir ol
const BAGLAMA_ADIMI = 2;                                 // yaz + surec yeniden yuklemesi

const temel = zincir(kur());
const s = (x, n) => String(x).padStart(n);
const t = (x) => x.toLocaleString('tr-TR');
console.log(`${'degisiklik'.padEnd(22)}${'bicim'.padEnd(23)}${s('gecersiz katman', 16)}`
  + `${s('kopyalanan bayt', 17)}${s('adim', 6)}`);
for (const [yol, yeniIcerik] of Object.entries(DEGISIKLIK)) {
  kur();
  writeFileSync(`${KOK}/${yol}`, yeniIcerik);
  const yeni = zincir(agacOku());
  const gecersiz = yeni.filter((k, i) => k.ozet !== temel[i].ozet);
  const bayt = gecersiz.reduce((a, k) => a + k.bayt, 0);
  const bagliBicim = yol === 'bagimlilik.json' ? 'yeniden uretime duser' : 'bagla + yeniden yukle';
  console.log(`${yol.padEnd(22)}${'yeniden uret'.padEnd(23)}${s(`${gecersiz.length}/${ADIMLAR.length}`, 16)}`
    + `${s(t(bayt), 17)}${s(uretimAdimi(gecersiz.length), 6)}`);
  console.log(`${''.padEnd(22)}${bagliBicim.padEnd(23)}`
    + `${s(yol === 'bagimlilik.json' ? '-' : `0/${ADIMLAR.length}`, 16)}`
    + `${s(yol === 'bagimlilik.json' ? '-' : '0', 17)}`
    + `${s(yol === 'bagimlilik.json' ? '-' : BAGLAMA_ADIMI, 6)}`);
  if (gecersiz.length) console.log(`${''.padEnd(22)}gecersizlesen: ${gecersiz.map((k) => k.ad).join(', ')}`);
}

// Baglama biciminin bedeli: calisan ortamin uretimden uzakligi.
const agac = kur();
const bagli = agac;                                   // CZ41: dizinin tamami takilir
const imaj = agac.filter((d) => imajda(d.yol));
const ICE = /from\s+'(\.[^']+)'/g;
function cozulmeyen(kume) {
  const yollar = new Set(kume.map((d) => d.yol));
  const eksik = [];
  for (const d of kume.filter((x) => x.yol.endsWith('.mjs'))) {
    const taban = d.yol.slice(0, d.yol.lastIndexOf('/'));
    for (const [, hedef] of readFileSync(`${KOK}/${d.yol}`, 'utf8').matchAll(ICE)) {
      const coz = new URL(hedef, `file:///${taban}/`).pathname.slice(1);
      if (!yollar.has(coz)) eksik.push(`${d.yol} -> ${hedef}`);
    }
  }
  return eksik;
}
const derlenmis = derle(agac);
console.log(`\n${'ortam'.padEnd(22)}${s('dosya', 7)}${s('bayt', 7)}${s('cozulmeyen ice aktarma', 24)}`);
for (const [ad, kume] of [['baglanan kaynak', bagli], ['uretim imaji', imaj]]) {
  console.log(`${ad.padEnd(22)}${s(kume.length, 7)}${s(kume.reduce((a, d) => a + d.bayt, 0), 7)}`
    + `${s(cozulmeyen(kume).length, 24)}`);
}
for (const e of cozulmeyen(imaj)) console.log(`  yalniz imajda kirilan: ${e}`);
console.log(`imajda olmayip baglanan ${bagli.length - imaj.length} dosya`
  + ` (${bagli.filter((d) => !imajda(d.yol)).map((d) => d.yol).join(', ')})`);
console.log(`baglama biciminde hic kosmayan derleme adimi: ${imaj.filter((d) => d.yol.endsWith('.mjs')).length}`
  + ` kaynaktan ${derlenmis} baytlik tek cikti`);
degisiklik            bicim                   gecersiz katman  kopyalanan bayt  adim
uygulama/bicim.mjs    yeniden uret                        2/5              671     6
                      bagla + yeniden yukle               0/5                0     2
                      gecersizlesen: kaynak kopyalama, derleme
bagimlilik.json       yeniden uret                        4/5       18.875.131     8
                      yeniden uretime duser                 -                -     -
                      gecersizlesen: bagimlilik bildirimi, bagimlilik kurulumu, kaynak kopyalama, derleme
notlar.md             yeniden uret                        0/5                0     2
                      bagla + yeniden yukle               0/5                0     2

ortam                   dosya   bayt  cozulmeyen ice aktarma
baglanan kaynak            10    735                       0
uretim imaji                5    439                       1
  yalniz imajda kirilan: uygulama/baslat.mjs -> ./ayar.local.mjs
imajda olmayip baglanan 5 dosya (uygulama/ayar.local.mjs, uygulama/ornek-olcum.json, sinama/toplayici.sinama.mjs, notlar.md, .gizli-ayar)
baglama biciminde hic kosmayan derleme adimi: 4 kaynaktan 324 baytlik tek cikti

Kaynak satırı değişikliği en sık gelen sınıftır ve iki biçim arasındaki fark burada en açık görünür: beş adımın ikisi geçersizleşir, 671 bayt kopyalanır ve altı adım geçer; bağlama biçiminde sıfır katman, sıfır bayt ve iki adım. Adım sayısındaki üç kat, bayt sayısındaki farktan daha anlamlıdır çünkü bayt sayısı kaynak ağacının büyüklüğüyle ölçeklenir, adım sayısı ölçeklenmez. Buradaki 671 bayt on satırlık bir ağaçtan gelir; ağaç bin dosya olsaydı bayt yüz binlere çıkardı, adım sayısı yine altı kalırdı.

Altı adımın nelerden oluştuğu da sayının bir parçasıdır: dosya yazılır, geçersizleşen iki adım yeniden yürütülür, konteyner durdurulur, yenisiyle başlatılır ve hazır olması beklenir. Bunların hiçbiri değişikliğin içeriğiyle ilgili değildir; hepsi çıktının yeniden üretilmesinin ve çalışan sürecin değiştirilmesinin bedelidir. Günde kırk kaynak değişikliği yapan bir kişi için iki biçim arasındaki fark dört ek adımın kırk katıdır — yüz altmış durak, ve her durak sonucun görülmesinden önce beklenen bir yerdir. Bağlama biçiminin satın aldığı şey kopyalanmayan 671 bayt değil, bu duraklardır.

İkinci satır bağlama biçiminin sınırını çiziyor. Bağımlılık bildirimi değiştiğinde beş adımın dördü geçersizleşir, kopyalanan bayt 18.875.131’e — kaynak değişikliğinin yaklaşık yirmi sekiz bin katına — çıkar ve döngü sekiz adım sürer. Bağlama bu sınıfa dokunamaz: bağımlılıklar kaynak dizininde değil, kurulum adımının çıktısındadır, dolayısıyla döngü yeniden üretime düşer. Bağlamanın kazancı tek bir değişiklik sınıfında geçerlidir ve o sınıfın dışına çıkıldığında birinci biçimin bütün bedeli geri gelir.

Üçüncü satır sessiz olanı gösteriyor. Yok sayılan bir dosya değiştiğinde imaj tarafında hiçbir katman geçersizleşmez; bu doğru davranıştır. Ama aynı değişiklik bağlama biçiminde görünür, çünkü dosya takılan dizinin içindedir ve süreç onu okuyabilir. İki biçim aynı sayıları verir, farklı şeyler yapar. Bu satır bir sonraki bölümün konusudur.

Bağlamanın Bedeli: Uzaklaşan Ortam

Çıktının ikinci yarısı bağlama biçiminin ödediği bedeli sayıyor. Yerelde çalışan ağaç on dosya ve 735 bayttır; üretime giden imaj beş dosya ve 439 bayt. Yerelde koşan sürecin gördüğü dosyaların yarısı üretimde yoktur. Bu fark bir kaza değil, yok sayma kurallarının kasıtlı sonucudur — ve bağlama biçimi tam olarak o kuralları atlayarak çalışır.

Farkın somut sonucu bir satır aşağıdadır. baslat.mjs dosyası ./ayar.local.mjs dosyasını içe aktarır. Bağlanan kümede bu içe aktarma çözülür ve süreç sorunsuz kalkar; üretim imajında dosya bulunmadığı için çözülmez. Ölçüm bunu sayıyor: bağlanan kümede sıfır, imajda bir çözülmeyen içe aktarma. Yerel döngü bu hatayı hiçbir koşumda gösteremez, çünkü hatanın koşulu tam da yerel döngünün ortadan kaldırdığı şeydir. Bu, bağlama biçiminin gizlediği hata sınıfının adıdır: yok sayma kuralının imajdan çıkardığı bir dosyaya kalan bağımlılık.

İkinci gizli sınıf son satırdadır. Derleme adımı bağlama biçiminde hiç koşmaz; dört kaynaktan üretilen 324 baytlık çıktı yerelde hiçbir zaman çalıştırılmaz. Yerelde çalışan şey kaynağın kendisidir, üretimde çalışan şey derlemenin ürünüdür ve ikisinin aynı davrandığı hiçbir yerde sınanmaz. Bu iki sınıf birlikte, geliştirici deneyiminin gerçek fiyatını verir: döngü altı adımdan iki adıma inerken, yerel ortam üretimden beş dosya, bir çözülmeyen bağımlılık ve bir hiç koşmayan adım kadar uzaklaşır.

Yalıtımın delindiği yer bu derste bir kusur değil, aracın kendisidir. Bağlama takması imajın kapattığı sınırı bilerek deler; kaynak dizini artık çıktının içinde değil, ortamdadır. Önceki iki dersin ölçüsü buraya doğrudan bağlanır: takılan dizinin dosya sahipliği içerideki kimlikle uyuşmayabilir ve bileşim dosyası bu takmayı her servis için ayrı ayrı bildirir. Deliğin ölçüsü yukarıdaki üç sayıdır ve ölçülebilir olması, deliğin kapatılmasını değil, ne zaman kapatılacağının bilinmesini sağlar.

Özet

  • Kaynak dört dosya ve 321 bayt, altındaki iki adım 60.817.408 bayttır; değişikliklerin çoğu üstte, bedelin çoğu alttadır ve katman zinciri değişen adımın üstündekileri yeniden üretmez.
  • Kaynak satırı değişikliğinde yeniden üretim beş katmanın ikisini geçersizleştirir, 671 bayt kopyalar ve altı adım sürer; bağlama biçiminde sıfır katman, sıfır bayt, iki adım.
  • Bayt farkı ağaç büyüdükçe büyür, adım farkı büyümez: üç katlık adım oranı bu ölçümün ağaçtan bağımsız kısmıdır.
  • Bağımlılık bildirimi değiştiğinde dört katman geçersizleşir ve 18.875.131 bayt kopyalanır; bağlama biçimi bu sınıfa dokunamaz, döngü yeniden üretime düşer.
  • Bağlanan ağaç on dosya ve 735 bayt, üretim imajı beş dosya ve 439 bayttır; yerelde çözülen bir içe aktarma imajda çözülmez ve bu hatayı yerel döngü hiçbir koşumda gösteremez.
  • Derleme adımı bağlama biçiminde hiç koşmaz: dört kaynaktan üretilen 324 baytlık çıktı yerelde çalıştırılmaz. Döngü altı adımdan iki adıma inerken ortam beş dosya, bir çözülmeyen bağımlılık ve bir koşmayan adım kadar üretimden uzaklaşır.

Sonraki Adım

Bu dersin sonunda yalıtım bilerek delinmiş durumda: bir dizin dışarıdan takılı, kaynak imajın içinde değil, derleme adımı atlanmış. Üç ders boyunca ölçülen şey hep aynı eksenin bir parçasıydı — kimin çalıştığı, kaç servisin birbirini gördüğü, hangi dosyanın nereden geldiği. Bu delikler yerel ortamda bilinçli bir seçimdir. Aynı seçimler üretimde de yapılabilir ve orada bedelleri başkadır: bir konteynerin çekirdekten isteyebileceği ayrıcalıklar, dosya sisteminin yazılabilir kalması, kaldırılan her kısıtın açtığı yol. Sonraki ders çalışma zamanı güvenliğini bu eksende ele alır ve yalıtımın isteğe bağlı olan kısımlarını tek tek sayar: hangisi varsayılan olarak açık, kapatıldığında ne kırılıyor, açık bırakıldığında hangi yol açılı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