Ders 26 / 30
Paketleme ve Şablonlama
Aynı uygulama beş ortama önce elle, sonra tek şablonla kurulur; yazılan satır, ortam başına değişen değer ve kopyala-yapıştır sürüklenmesi sayılır. Şablonun kendi bedeli de ölçülür: değişken sayısı, koşullu dal sayısı ve şablonu okuyarak kaç satırın kesin bilinebildiği.
İçindekiler
Bir önceki ders güvenlik bağlamını politikaya çevirdi: ayrıcalıklı çalışma, salt okunur kök ve yetenek kısıtı artık kümenin bildirim kabul ederken dayattığı kurallar. Dayatma çalışıyor, ama dayatılan şeyi yazan taraf değişmedi. Aynı uygulamanın beş ortama kurulması için aynı nesne kümesi hâlâ beş kez yazılıyor ve beş kopya arasında yalnız birkaç değer değişiyor.
Dağıtım paketi, bir uygulamanın küme üzerinde çalışması için gereken nesnelerin tümüdür: sürüm nesnesi, servis nesnesi, yapılandırma nesnesi, giriş kuralı, kota. Şablon, o paketin ortamdan bağımsız kısmını bir kez yazıp değişen kısmı dışarı çıkaran metindir. Devredilen karar şudur: her ortamın nesne kümesinin son hâlini insan yazmayı bırakır, bir üretici yazar. Bu dersin ölçtüğü şey o devrin bedeli — hem kazandırdığı satır hem de üreticinin kendi getirdiği yük.
Ölçüm yine bölgesel ölçüm ağı üzerindedir: su sayacı okumalarını toplayan, doğrulayan ve saha
iş emri açan kurgu yazılım. Gerçek küme koşturulmaz; ölçülen şey node ile kurulmuş bir
modeldir — metin üzerinde çalışan bir şablon motoru ve elle düzenlemenin adım adım taklidi.
OI31. Dağıtım paketi beş nesneden oluşur ve on altı satırla temsil edilir. OI32. Beş ortam vardır: geliştirme, sınama, ön-üretim, üretim-batı, üretim-doğu. OI33. Elle yolda taban paket bir kez yazılır, dört kez kopyalanır; kopyalar sonra düzeltilir. OI34. Üç değişiklik turu koşulur; her turda kimin atladığı ya da yanlış yazdığı önceden yazılıdır, rastgelelik yoktur. OI35. Şablon motoru üç işi bilir — değer yerleştirme, koşullu blok, döngü — ve nesne şemasını bilmez. OI36. Şema ders içinde tanımlanır ve yalnız üretilmiş çıktıya uygulanır.
Beş Ortam, Tek Nesne Kümesi
// veri.mjs — taban nesne kumesi, ortam basina degisen degerler, sablon ve sablon motoru. export const TABAN = `nesne surum-nesnesi okuma-toplayici kopya 3 imge-etiketi 4.2.0 istek-cpu 200 sinir-cpu 500 nesne servis-nesnesi okuma-toplayici kapi 8080 nesne yapilandirma-nesnesi toplayici-ayar kutuk-duzeyi ozet toplu-boyut 500 dogrulayici-adres dogrulayici.olcum-agi:8080 nesne giris-kurali toplayici-giris konak toplayici.on.ic nesne kota olcum-agi cpu-toplam 4000 bellek-toplam 8192`; // on-uretim ortami icin elle yazildi export const ALAN = ["kopya", "kutuk-duzeyi", "toplu-boyut", "konak", "cpu-toplam", "bellek-toplam", "sinir-cpu"]; const HAM = { // ortam: [yedi deger] + acik dallar "gelistirme": [1, "ayrinti", 50, "toplayici.gelistirme.ic", 1000, 2048, 300, []], "sinama": [2, "ozet", 200, "toplayici.sinama.ic", 2000, 4096, 400, ["giris"]], "on-uretim": [3, "ozet", 500, "toplayici.on.ic", 4000, 8192, 500, ["giris"]], "uretim-bati": [6, "uyari", 1000, "toplayici.bati.ic", 12000, 24576, 800, ["giris"]], "uretim-dogu": [6, "uyari", 1000, "toplayici.dogu.ic", 12000, 24576, 800, ["giris", "yedek"]] }; export const ORTAM = Object.keys(HAM); export const DEGER = Object.fromEntries(ORTAM.map((o) => [o, Object.fromEntries( [...ALAN.map((a, i) => [a, HAM[o][i]]), ...HAM[o][7].map((x) => [x, true])])])); export const SABLON = `nesne surum-nesnesi okuma-toplayici kopya {{kopya}} imge-etiketi 4.2.0 istek-cpu 200 sinir-cpu {{sinir-cpu}} nesne servis-nesnesi okuma-toplayici kapi 8080 nesne yapilandirma-nesnesi toplayici-ayar kutuk-duzeyi {{kutuk-duzeyi}} toplu-boyut {{toplu-boyut}} dogrulayici-adres dogrulayici.olcum-agi:8080 {{#eger giris}}nesne giris-kurali toplayici-giris konak {{konak}} {{/eger}}{{#eger yedek}}nesne surum-nesnesi okuma-toplayici-yedek kopya {{yedek-kopya}} imge-etiketi 4.2.0 {{/eger}}nesne kota olcum-agi cpu-toplam {{cpu-toplam}} bellek-toplam {{bellek-toplam}} {{#her ek-etiket}} etiket {{.}} {{/her}}`; export const uret = (s, d) => s // yerlestirme, kosullu blok, dongu .replace(/\{\{#eger ([\w-]+)\}\}([\s\S]*?)\{\{\/eger\}\}/g, (_, a, g) => (d[a] ? g : "")) .replace(/\{\{#her ([\w-]+)\}\}([\s\S]*?)\{\{\/her\}\}/g, (_, a, g) => (d[a] ?? []).map((x) => g.replaceAll("{{.}}", x)).join("")) .replace(/\{\{([\w-]+)\}\}/g, (_, a) => String(d[a] ?? `<eksik:${a}>`)) .split("\n").filter((s) => s.trim() !== "").join("\n"); export const satir = (m) => m.split("\n").filter((s) => s.trim() !== "").length;
Taban paket ön-üretim ortamı için yazılmıştır ve on altı satırdır. Beş ortam arasında değişen değer
yedi tanedir; geri kalan dokuz satır beş ortamda da birebir aynıdır. Şablon bu ayrımı metne
taşır: yedi değer {{...}} ile dışarı çıkar, iki nesne koşullu bloğa girer.
Ayrım burada iki türlü değişkenlik olduğunu gösterir. Birincisi değer değişkenliğidir — kopya sayısı, kütük düzeyi, kota tavanı; nesne kümesi aynı kalır, içindeki sayı değişir. İkincisi yapı değişkenliğidir — geliştirme ortamında giriş kuralı hiç yoktur, üretim-doğuda fazladan bir sürüm nesnesi vardır; değişen şey değer değil, nesnenin varlığıdır. Değer değişkenliği düz bir listeyle kapatılır; yapı değişkenliği koşullu dal ister ve dersin sonunda görüleceği gibi bedelin büyük kısmı oradan gelir.
Yazılan Satır ve Kopyala-Yapıştır Sürüklenmesi
// olc.mjs — ayni uygulama bes ortama elle ve sablonla kurulur; satir, dokunma, surukleme sayilir. import { TABAN, SABLON, uret, satir, ALAN, ORTAM, DEGER } from "./veri.mjs"; const ortak = ["imge-etiketi", "istek-cpu", "kapi", "dogrulayici-adres", "hazir-olma-denetimi"]; let yazilan = satir(TABAN), kopyalanan = 0, duzenlenen = 0; const elle = {}; for (const o of ORTAM) { // taban bir kez yazilir, dort kez kopyalanir let m = TABAN; if (o !== "on-uretim") { kopyalanan += satir(TABAN); for (const a of ALAN) { const y = m.replace(new RegExp(`( ${a}) .*`), `$1 ${DEGER[o][a]}`); if (y !== m) duzenlenen++; m = y; } if (!DEGER[o].giris) { m = m.replace(/nesne giris-kurali.*\n konak .*\n/, ""); duzenlenen += 2; } if (DEGER[o].yedek) { m += "\nnesne surum-nesnesi okuma-toplayici-yedek\n kopya 6"; duzenlenen += 2; } } elle[o] = m; } const tur = [ // uc tur; atlama ve yanlis yazma onceden yazilidir { f: (m) => m.replace(/( sinir-cpu .*)/, "$1\n hazir-olma-denetimi /saglik"), atla: "uretim-dogu" }, { f: (m) => m.replaceAll(/( imge-etiketi) .*/g, "$1 4.3.0"), atla: "sinama" }, { f: (m) => m.replace(/( istek-cpu) .*/, "$1 250"), yanlis: { "on-uretim": (m) => m.replace(/( istek-cpu) .*/, "$1 300") } }]; let sablon = SABLON, sDuzenlenen = 0; for (const t of tur) { for (const o of ORTAM) { if (t.atla === o) continue; elle[o] = (t.yanlis?.[o] ?? t.f)(elle[o]); duzenlenen++; } sablon = t.f(sablon); // sablon yolunda ayni tur tek dokunus sDuzenlenen++; } const uretilen = Object.fromEntries(ORTAM.map((o) => [o, uret(sablon, DEGER[o])])); const ayrisma = (kume) => { // ortak alanlarda cogunluktan sapan kopyalar const sapan = {}; let alan = 0; for (const a of ortak) { const d = ORTAM.map((o) => (kume[o].match(new RegExp(` ${a} (.*)`)) ?? [, "-"])[1]); const cok = d.map((v) => [v, d.filter((x) => x === v).length]).sort((x, y) => y[1] - x[1])[0][0]; ORTAM.forEach((o, i) => { if (d[i] !== cok) { alan++; (sapan[o] ??= []).push(a); } }); } return { kopya: Object.keys(sapan).length, alan, sapan }; }; const e = ayrisma(elle), s = ayrisma(uretilen); console.log(`elle yol : yazilan ${yazilan} kopyalanan ${kopyalanan} elle duzenlenen ${duzenlenen}` + ` depodaki satir ${ORTAM.map((o) => satir(elle[o])).reduce((a, b) => a + b)}`); console.log(`sablon yolu: yazilan ${satir(SABLON)} deger satiri ${ORTAM.length}` + ` elle duzenlenen ${sDuzenlenen} depodaki satir ${satir(SABLON) + ORTAM.length}`); console.log(`ortam basina degisen deger ${ALAN.length} ortak kalmasi gereken alan ${ortak.length}`); console.log(`suruklenme (elle) : ${e.kopya}/5 kopya ayrismis, ${e.alan} alan`); for (const [o, a] of Object.entries(e.sapan)) console.log(` ${o.padEnd(12)} ${a.join(", ")}`); console.log(`suruklenme (sablon): ${s.kopya}/5 kopya ayrismis, ${s.alan} alan`);
elle yol : yazilan 16 kopyalanan 64 elle duzenlenen 44 depodaki satir 84
sablon yolu: yazilan 21 deger satiri 5 elle duzenlenen 3 depodaki satir 26
ortam basina degisen deger 7 ortak kalmasi gereken alan 5
suruklenme (elle) : 3/5 kopya ayrismis, 3 alan
sinama imge-etiketi
on-uretim istek-cpu
uretim-dogu hazir-olma-denetimi
suruklenme (sablon): 0/5 kopya ayrismis, 0 alan
Elle yolda depoda seksen dört satır durur ve bunların altmış dördü kopyadır. Asıl sayı kopya değil, elle düzenlenen kırk dört satırdır: otuz biri ilk kuruluşta, on üçü üç değişiklik turunda. Şablon yolunda aynı iş yirmi bir satırlık bir şablon, beş satırlık değer listesi ve üç turda üç dokunuşla biter. Depodaki satır oranı üçe bir; değişiklik turlarındaki dokunuş oranı on üçe üç.
Sürüklenme bu oranın nedenini gösterir. Beş kopyanın ortak kalması gereken beş alanı vardır ve üç tur sonunda üç kopya üç alanda ayrışmıştır: sınama eski görüntü etiketinde kalmış, ön-üretim kaynak isteğine 250 yerine 300 yazmış, üretim-doğu hazır olma denetimini hiç almamış. Üçünün de tek nedeni aynıdır — ortak değerin beş ayrı yerde durması. Ayrışmanın ölçülebilir yanı da burada: kopyalar ayrışsa bile hiçbiri hata vermez, çünkü her biri kendi başına geçerli bir pakettir. Şablon yolunda aynı üç tur sıfır ayrışma bırakır; ortak değer tek yerde durduğu için atlanacak bir kopya yoktur.
Şablonun Kendi Bedeli
// bedel.mjs — sablonun kendi bedeli: degisken, kosullu dal, okunabilirlik, sema kusuru. import { SABLON, uret, ORTAM, DEGER } from "./veri.mjs"; const degisken = new Set([...SABLON.matchAll(/\{\{([\w-]+)\}\}/g)].map((m) => m[1])); const dal = [...SABLON.matchAll(/\{\{#(?:eger|her) ([\w-]+)\}\}/g)].map((m) => m[1]); const bicim = new Set(ORTAM.map((o) => dal.map((a) => (DEGER[o][a] ? 1 : 0)).join(""))); const im = SABLON.replace(/\{\{#(?:eger|her) [\w-]+\}\}([\s\S]*?)\{\{\/(?:eger|her)\}\}/g, (_, g) => g.split("\n").map((s) => (s.trim() ? "@" + s : s)).join("\n")); const sS = im.split("\n").filter((s) => s.trim()), kosullu = sS.filter((s) => s.startsWith("@")).length; console.log(`sablon ${sS.length} satir degisken ${degisken.size} kosullu dal ${dal.length}` + ` uretilebilir bicim ${2 ** dal.length} bes ortamda kullanilan ${bicim.size}`); console.log(`sablonu okuyarak kesin bilinen satir ${sS.length - kosullu}/${sS.length};` + ` ${kosullu} satir icin uretim gerekir`); const SEMA = { kopya: "sayi", "imge-etiketi": "metin", "istek-cpu": "sayi", "sinir-cpu": "sayi", kapi: "sayi", "kutuk-duzeyi": "metin", "toplu-boyut": "sayi", "dogrulayici-adres": "metin", konak: "metin", "cpu-toplam": "sayi", "bellek-toplam": "sayi", etiket: "metin" }; const denetle = (m) => m.split("\n").filter((s) => s.startsWith(" ")).flatMap((s) => { const [a, d] = s.trim().split(/ (.+)/); // sema motorun disindadir, ciktiya uygulanir if (!(a in SEMA)) return [`bilinmeyen alan ${a}`]; if (d?.startsWith("<eksik:")) return [`${a} = ${d}`]; return SEMA[a] === "sayi" && !/^\d+$/.test(d ?? "") ? [`${a} sayi degil`] : []; }); let kusur = 0, olu = 0; for (const o of ORTAM) { const c = uret(SABLON, DEGER[o]), h = denetle(c); kusur += h.length; if (h.length) console.log(` ${o.padEnd(12)} uretildi, sema kusuru: ${h.join("; ")}`); if (!c.includes(String(DEGER[o].konak))) olu++; } console.log(`motorun yakaladigi ${0}/${kusur} uretim sonrasi sema denetimi ${kusur}/${kusur}` + ` olu deger ${olu} hic uretilmemis bicim ${2 ** dal.length - bicim.size}`);
sablon 20 satir degisken 8 kosullu dal 3 uretilebilir bicim 8 bes ortamda kullanilan 3 sablonu okuyarak kesin bilinen satir 14/20; 6 satir icin uretim gerekir uretim-dogu uretildi, sema kusuru: kopya = <eksik:yedek-kopya> motorun yakaladigi 0/1 uretim sonrasi sema denetimi 1/1 olu deger 1 hic uretilmemis bicim 5
Şablonun bedeli üç sayıda toplanır. Birincisi değişken sayısıdır: sekiz değişken, yedisi değer listesinde tanımlı, biri hiçbir yerde tanımlı değil. İkincisi koşullu dal sayısıdır: üç dal, sekiz üretilebilir biçim, beş ortamda yalnız üçü kullanılıyor — kalan beş biçim şablonda duruyor ama hiç üretilmiyor, dolayısıyla hiç sınanmıyor.
Üçüncüsü okunabilirliktir ve şu sayıya bağlanır: yirmi satırlık şablonun on dördü okunarak kesin bilinir, altısı için üretim gerekir. Bu altı satır koşullu blokların içindedir; bir ortamın çıktısında olup olmadıklarına şablona bakarak karar verilemez, değer listesiyle birlikte üretilmeleri gerekir. Elle yazılan paket bu yükü taşımaz — orada yazılan şey çıktının kendisidir. Şablon yolunda insan artık çıktıyı değil, çıktıyı üreten programı okur.
Sonuncu satır devrin asıl bedelini gösterir. Şablon motoru üç iş bilir ve nesne şemasını bilmez;
yedek-kopya değişkeni hiçbir değer listesinde tanımlı olmadığı için üretim-doğu çıktısına
<eksik:yedek-kopya> yazar ve hata vermez. Motor bu kusurun sıfırını yakalar; üretim sonrası
şema denetimi birini de yakalar. Aynı sessizlik ters yönde de görünür: geliştirme ortamı için
tanımlanmış konak değeri hiçbir çıktıda kullanılmaz, çünkü o ortam giriş kuralı dalına girmez —
ölü değer, hiçbir yerde uyarı üretmeden değer listesinde durur.
Devrin Bilançosu
| Ölçü | Elle | Şablonla |
|---|---|---|
| ilk kuruluşta elle düzenlenen satır | 31 | 21 (şablon) + 5 (değer) |
| üç değişiklik turunda elle dokunuş | 13 | 3 |
| depodaki satır | 84 | 26 |
| ortak alanda ayrışan kopya | 3/5 | 0/5 |
| okunarak kesin bilinen satır | 84/84 | 14/20 |
| hiç üretilmemiş biçim | yok | 5 |
| şema kusurunu yakalayan | insan | motor 0, sonradan şema denetimi 1 |
Devredilen karar tek cümleyle yazılır: her ortamın nesne kümesinin son hâline insan değil bir üretici karar verir. Bunun için gereken bilgi kalemleri de sayılıdır — ortam başına yedi değer, ortam başına açık dal listesi, ve hangi alanın ortak kalması gerektiği. İlk ikisi değer listesindedir; üçüncüsü hiçbir yerde yazılı değildir. Şablon “imge etiketi bütün ortamlarda aynı olmalı” bilgisini taşımaz; o alan şablonda sabit yazıldığı için ortak kalır. Aynı alan yarın bir dalın içine taşınırsa ortaklık sessizce biter ve bunu haber verecek bir denetim yoktur.
Bilginin maliyeti de bu tablodan okunur. Yedi değer ortam başına bir kez yazılır ve ortam eklendikçe doğrusal büyür; dal listesi ortam başına bir satırdır ama şablonun dal sayısını büyüttüğü anda üretilebilir biçim sayısı ikinin kuvvetiyle artar. Bugünkü üç dal sekiz biçim demek; dördüncü bir dal eklendiğinde beş ortam yine aynı beş çıktıyı üretir, sınanmayan biçim sayısı ise beşten on bire çıkar. Şablonun büyümesi değer listesinin büyümesiyle aynı hızda gitmez.
Kazanç da bedel de aynı yerden çıkar: çıktı artık okunmuyor. Elle yolda seksen dört satırın seksen dördü okunabilirdi ve kopyalar yine de ayrıştı; şablon yolunda ayrışma sıfır, ama okunan metin çıktı değil üretici oldu ve üreticinin altı satırı ancak koşturularak biliniyor. Şablon sürüklenmeyi ortadan kaldırmadı, sürüklenmenin yerini değiştirdi: beş kopya arasındaki farktan, bir üreticinin hiç üretilmemiş beş biçimine taşıdı.
Özet
- Beş ortam elle kurulduğunda 16 satır yazıldı, 64 satır kopyalandı, 44 satır elle düzenlendi; şablonla aynı iş 21 satır şablon, 5 satır değer ve 3 dokunuşla bitti.
- Üç değişiklik turu sonunda elle yolda 5 kopyanın 3’ü, ortak kalması gereken 5 alanın 3’ünde ayrıştı; şablon yolunda ayrışma 0 kaldı.
- Şablonun bedeli 8 değişken ve 3 koşullu daldır: 8 üretilebilir biçimin yalnız 3’ü kullanılıyor, 5’i hiç üretilmiyor ve hiç sınanmıyor.
- Şablonun 20 satırının 14’ü okunarak kesin bilinir, 6’sı için üretim gerekir; insan artık çıktıyı değil, çıktıyı üreten programı okur.
- Motor şemayı bilmediği için tanımsız değişkeni
<eksik:...>diye çıktıya yazdı ve 1 kusurun 0’ını yakaladı; aynı sessizlik ters yönde 1 ölü değer bıraktı.
Sonraki Adım
Şablon, var olan nesne türlerinden yeni birleşimler üretir; yeni bir tür üretemez. Bölgesel ölçüm ağının okuma toplama düzeni beş nesnenin hiçbirine tam oturmuyor — sürüm nesnesi, kotası ve zamanlanmış işi bir arada, tek bir kavram olarak anılması gerekiyor. Bir sonraki ders kümenin tanıdığı kaynak türlerini genişletir: kurgu bir tür tanımlanır, alanları bir şemaya bağlanır ve geçersiz bildirimlerin kaç tanesinin kabul anında yakalandığı, kaçının ancak koşumda patladığı sayılır. Şema büyüdükçe yakalanan hata ile yazım yükünün birlikte nasıl arttığı, ve eski bildirimlerin yeni şemada kaç alanda kırıldığı da orada ölçülür.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.