Ders 08 / 12
Yapı Çıktısı ve Değişmezlik
Bir kez üret her yere dağıt ilkesi ölçülür: aynı kaynaktan iki kez üretilen çıktının özeti karşılaştırılır, yeniden üretilebilirliği bozan dört kaynak tek tek kapatılır ve ortam başına yeniden derlemenin doğurduğu ayrı ikili sayısı tek çıktı düzeniyle karşılaştırılır.
İçindekiler
Önceki ders dört ortamı üretime giden zincirin halkaları olarak ölçtü ve her halkanın yalnız üretimle aynı olduğu boyutlardaki kusurları görebildiğini saydı. O ölçüm bir şeyi varsaydı: halkalar arasında yolculuk eden nesne hep aynı nesnedir. Sınamada doğrulanan şey ile üretimde koşan şey aynı değilse, sınamanın verdiği güvence de o kadar azalır.
Bu ders o nesneyi adlandırır. Yapı çıktısı (build artifact), kaynaktan üretilen ve dağıtılabilen tek nesnedir: bir arşiv, bir paket, çalıştırılabilir tek bir dosya. Sorular ölçülebilir: aynı kaynaktan iki kez üretilen çıktı gerçekten aynı mı, değilse farkı nereden alıyor, ve her ortam için yeniden derlemek kaç ayrı nesne doğuruyor?
DK7. Aşağıdaki yapı adımı bir modeldir: üç kaynak dosya okunur ve tek dosyada birleştirilir.
Gerçek bir derleyicinin yaptığı iş değildir, ama yeniden üretilebilirlik açısından aynı
kırılganlıkları taşır. DK8. İki makine künyeyle modellenir — A yazan ekibin makinesi,
B yapıyı çalıştıran koşucu — ve saat de künyede yazılıdır ki ölçüm yinelenebilsin. A1 ve
A2 aynı makinenin iki ayrı koşumudur. DK9. Özet, dosyanın sha256 değerinin ilk on altı
onaltılık basamağıdır; kısaltma yalnız okunabilirlik içindir.
Bir Kez Üret, Her Yere Dağıt
Değişmezlik ilkesi tek cümledir: bir yapı çıktısı bir kez üretilir, üretildikten sonra değiştirilmez ve her ortama aynısı dağıtılır. Üretimdeki bir dosyayı yerinde yamalamak, sınamada doğrulanmış nesneyi bilinmeyen bir nesneye çevirir; düzeltme gerekiyorsa yeni bir çıktı üretilir ve yeni bir özet doğar. Geri alma da aynı kuralla çalışır: önceki çıktı yeniden dağıtılır, mevcut çıktı geri düzenlenmez.
İlkenin ihlali de somuttur. Üretimde tek bir dosyanın elle düzeltilmesi, o ortamda artık hiçbir yerde kaydı olmayan yeni bir nesne yaratır: zincirde bir nesne dolaşırken üretimde ikincisi durur, ikincisinin özeti hiçbir dağıtım kaydında yazmaz ve ortam künyesi bunu göstermez. Bir sonraki dağıtım bu düzeltmeyi sessizce siler; ortadan kalkan davranışın nedeni de aranacak yerlerin hiçbirinde bulunmaz. Yerinde yamalamanın bedeli budur: fark, ölçüm alanının tümüyle dışında bir yere saklanır.
İlkenin işe yaraması tek koşula bağlıdır: çıktının kimliğinin olması. Kimlik, içeriğin özetidir. İki çıktının aynı olup olmadığı ancak özetleri karşılaştırılarak söylenebilir; dosya adı, üretim tarihi ya da sürüm etiketi bunu söylemez. Bu yüzden ölçüm özetten başlar.
// olcum-agi/yapi.mjs — kaynagi diske yazar, tek dosyalik yapi ciktisi uretir (model) import { mkdirSync, writeFileSync, readFileSync, rmSync } from "node:fs"; import { createHash } from "node:crypto"; const KAYNAK = "kaynak"; export const kaynagiYaz = () => { rmSync(KAYNAK, { recursive: true, force: true }); mkdirSync(KAYNAK, { recursive: true }); writeFileSync(`${KAYNAK}/okuma-cozumleyici.js`, "export const cozumle = (s) => Number(s.slice(4));\n"); writeFileSync(`${KAYNAK}/fatura-kurallari.js`, "export const tutar = (m3) => m3 * 7.4;\n"); writeFileSync(`${KAYNAK}/is-emri.js`, "export const emirAc = (no) => ({ no, tur: 'yerinde' });\n"); }; // Makine kunyesi: saat de kunyede yazilidir, boylece olcum yinelenebilir. export const A1 = { ad: "A1", saat: "21:04:07", dizin: "/ev/yazan/olcum-agi", yapan: "yazan-ekip", okumaSirasi: ["okuma-cozumleyici.js", "fatura-kurallari.js", "is-emri.js"] }; export const A2 = { ...A1, ad: "A2", saat: "21:19:52" }; export const B = { ad: "B", saat: "22:41:30", dizin: "/opt/kosucu/is-1873", yapan: "hat-kosucusu", okumaSirasi: ["is-emri.js", "fatura-kurallari.js", "okuma-cozumleyici.js"] }; export const DORT_KAYNAK = ["zamanDamgasi", "dizinSirasi", "ortamDegiskeni", "mutlakYol"]; export const yalniz = (ad) => Object.fromEntries(DORT_KAYNAK.map((k) => [k, k === ad])); export const yapiYap = (kunye, secenek, hedef, gomulu = null) => { const dosyalar = secenek.dizinSirasi ? kunye.okumaSirasi : [...kunye.okumaSirasi].sort(); const baslik = []; if (secenek.zamanDamgasi) baslik.push(`// uretim ani: ${kunye.saat}`); if (secenek.ortamDegiskeni) baslik.push(`// yapan: ${kunye.yapan}`); if (secenek.mutlakYol) baslik.push(`// kaynak koku: ${kunye.dizin}/${KAYNAK}`); if (gomulu) baslik.push(`export const AYAR = ${JSON.stringify(gomulu)};`); const govde = dosyalar.map((d) => readFileSync(`${KAYNAK}/${d}`, "utf8")).join(""); mkdirSync("dagitim", { recursive: true }); writeFileSync(`dagitim/${hedef}`, baslik.join("\n") + (baslik.length ? "\n" : "") + govde); return createHash("sha256").update(readFileSync(`dagitim/${hedef}`)).digest("hex").slice(0, 16); };
Yeniden Üretilebilirliği Bozan Dört Kaynak
DK10. Yapı adımına dört ayrı bozucu kaynak yerleştirilmiştir ve her biri ayrı ayrı
kapatılabilir: üretim anının çıktıya yazılması (zamanDamgasi), kaynak dosyaların dizin okuma
sırasıyla birleştirilmesi (dizinSirasi), yapıyı çalıştıranın adının gömülmesi
(ortamDegiskeni) ve kaynak kökünün mutlak yolunun yazılması (mutlakYol). Dördü de gerçek
yapı adımlarında karşılaşılan kaynaklardır.
Tarama üç koşum yapar — aynı makinede iki an (A1, A2) ve ikinci makine (B) — ve her
seçenek kümesi için kaç ayrı özet doğduğunu sayar.
// olcum-agi/tara.mjs — yeniden uretilebilirligi bozan kaynaklari tek tek tarar import { kaynagiYaz, yapiYap, yalniz, DORT_KAYNAK, A1, A2, B } from "./yapi.mjs"; kaynagiYaz(); const hepsi = Object.fromEntries(DORT_KAYNAK.map((k) => [k, true])); const hicbiri = Object.fromEntries(DORT_KAYNAK.map((k) => [k, false])); const ozetler = (secenek) => [A1, A2, B].map((k) => yapiYap(k, secenek, `paket-${k.ad}.js`)); const satir = (etiket, secenek) => { const [a1, a2, b] = ozetler(secenek); const ayri = new Set([a1, a2, b]).size; console.log(etiket.padEnd(24) + (a1 === a2 ? "AYNI " : "FARKLI ").padEnd(12) + (a1 === b ? "AYNI " : "FARKLI ").padEnd(11) + ayri); }; console.log("etkin kaynak".padEnd(24) + "A1-A2".padEnd(12) + "A1-B".padEnd(11) + "ayri ozet"); satir("dordu birden", hepsi); for (const k of DORT_KAYNAK) satir("yalniz " + k, yalniz(k)); satir("hicbiri", hicbiri); const [a1, a2, b] = ozetler(hicbiri); console.log(`\ndort kaynak kapatildiginda uc kosumun ozeti: ${a1}, ${a2}, ${b}`); console.log("ayni girdi, ayni cikti: " + (a1 === a2 && a2 === b));
etkin kaynak A1-A2 A1-B ayri ozet dordu birden FARKLI FARKLI 3 yalniz zamanDamgasi FARKLI FARKLI 3 yalniz dizinSirasi AYNI FARKLI 2 yalniz ortamDegiskeni AYNI FARKLI 2 yalniz mutlakYol AYNI FARKLI 2 hicbiri AYNI AYNI 1 dort kaynak kapatildiginda uc kosumun ozeti: f8aa0bd8108fe25b, f8aa0bd8108fe25b, f8aa0bd8108fe25b ayni girdi, ayni cikti: true
Tablo iki şeyi ayırır. Birincisi: dört kaynağın her biri tek başına yeniden üretilebilirliği bozmaya yeter. Yalnız biri etkinken bile ikinci makinede üretilen çıktının özeti farklıdır; aynı kaynaktan üretilmiş iki nesnenin aynı olduğu söylenemez.
İkincisi ve daha önemlisi: dört kaynağın üçü aynı makinedeki iki koşumda görünmez. Dizin
okuma sırası, ortam değişkeni ve mutlak yol, tek makinede sabit kaldıkları için A1 ile A2
arasında fark üretmez. Yazan ekip kendi makinesinde yapıyı iki kez çalıştırıp aynı özeti görür
ve yapının yeniden üretilebilir olduğu sonucuna varır. Fark, çıktı ikinci bir makinede
üretildiği anda ortaya çıkar. Önceki dersin ayrımı burada da geçerlidir: bildirilmiş bir fark
değildir bu, yalnız koşumda — hem de başka bir makinedeki koşumda — görünen bir farktır ve
gizlediği hata sınıfının adı doğrulanmamış ikilidir: sınamadan geçen nesnenin üretime giden
nesne olduğu ölçülmemiştir.
Dördü de kapatıldığında üç koşum tek özet üretir. Bu, özetin artık koşumu değil girdiyi adlandırdığı anlamına gelir: aynı özet, aynı kaynak. Kaynakların kapatılma yolları da bellidir ve dördü de yapı adımının içindedir — zaman damgasını çıktıya yazmamak, dosyaları dizin okuma sırasıyla değil ada göre sıralamak, yapıyı çalıştıranın kimliğini gömmemek ve yolları kaynak köküne göre göreli tutmak.
Özetin ne söylediği ile ne söylemediği de ayrılmalıdır. Özet bayt kimliğini söyler: iki çıktının özeti aynıysa iki nesne aynıdır, dağıtım kaydında biri diğerinin yerine yazılabilir. Söylemediği iki şey vardır. Birincisi, çıktının hangi kaynaktan üretildiğini söylemez; özet çıktının kendisinden hesaplanır, kaynağın kimliğinden değil. Yapı adımı yeniden üretilebilir olduğunda bu boşluk kapanabilir: aynı kaynaktan yapıyı ikinci kez çalıştıran bir başkası aynı özeti elde eder ve iddia sınanabilir hâle gelir — yeniden üretilebilirliğin düzenden öte değeri buradadır. İkincisi, özet farkının anlamlı bir davranış farkı olduğunu söylemez; çıktıya eklenen tek bir açıklama satırı da özeti baştan sona değiştirir. Farklı özet, bir kanıt değil, incelenmesi gereken bir işarettir.
Ortam Başına Derleme ile Tek Çıktı
Zincirin her halkası için ayrı derleme yapmanın somut karşılığı vardır: ortama göre değişen değerler yapı adımında çıktının içine yazılır. DK11. Kurgudaki ağda bu değerler dörttür — ortam adı, fatura kapısının gerçek mi taklit mi olduğu, kütük düzeyi ve gecelik toplu işin yığın eşiği. DK12. Tek çıktı düzeninde aynı dört değer açılışta ortamdan okunur; okumanın nasıl yapıldığı ve hangi değerin sır sayıldığı sonraki dersin konusudur.
// olcum-agi/ortam-basina.mjs — ortam basina yeniden derleme ile tek ciktiyi karsilastirir import { kaynagiYaz, yapiYap, DORT_KAYNAK, A1, A2, B } from "./yapi.mjs"; const AYAR = { gelistirme: { ortamAdi: "gelistirme", faturaKapisi: "taklit", kutuk: "ayrintili", esik: 500 }, sinama: { ortamAdi: "sinama", faturaKapisi: "taklit", kutuk: "bilgi", esik: 20000 }, uretim: { ortamAdi: "uretim", faturaKapisi: "gercek", kutuk: "uyari", esik: 250000 }, }; const ORTAM = Object.keys(AYAR); const hicbiri = Object.fromEntries(DORT_KAYNAK.map((k) => [k, false])); const hepsi = Object.fromEntries(DORT_KAYNAK.map((k) => [k, true])); kaynagiYaz(); console.log("gomulu deger anahtarlari: " + Object.keys(AYAR.uretim).join(", ")); const ayri = ORTAM.map((o) => [o, yapiYap(A1, hicbiri, `paket-${o}.js`, AYAR[o])]); for (const [o, ozet] of ayri) { const fark = Object.keys(AYAR[o]).filter((k) => AYAR[o][k] !== AYAR.uretim[k]); console.log(`${o.padEnd(11)} ozet ${ozet} uretim ikilisinden farkli deger ${fark.length}`); } console.log(`ortam basina derleme: ${new Set(ayri.map(([, h]) => h)).size} ayri ikili, ` + `ikililere gomulu deger ${ORTAM.length * Object.keys(AYAR.uretim).length}`); const tek = ORTAM.map((o) => yapiYap(A1, hicbiri, `paket-tek-${o}.js`)); console.log(`tek cikti duzeni : ${new Set(tek).size} ayri ikili, ozet ${tek[0]}, gomulu deger 0`); console.log("\nortam basina derleme farkli makinelerde ve anlarda yapilirsa:"); const gurultu = [[A1, "sinama"], [B, "uretim"], [A2, "uretim"]] .map(([k, o]) => [`${o} @ ${k.ad}`, yapiYap(k, hepsi, `paket-g-${o}-${k.ad}.js`, AYAR[o])]); for (const [ad, ozet] of gurultu) console.log(" " + ad.padEnd(14) + "ozet " + ozet); console.log(" ayni ortam icin iki kosum ayni ozeti verdi: " + (gurultu[1][1] === gurultu[2][1]));
gomulu deger anahtarlari: ortamAdi, faturaKapisi, kutuk, esik gelistirme ozet e9b4bbcaec4a64a4 uretim ikilisinden farkli deger 4 sinama ozet 3cb0ab9967d14e1a uretim ikilisinden farkli deger 4 uretim ozet 440bf8366e892e51 uretim ikilisinden farkli deger 0 ortam basina derleme: 3 ayri ikili, ikililere gomulu deger 12 tek cikti duzeni : 1 ayri ikili, ozet f8aa0bd8108fe25b, gomulu deger 0 ortam basina derleme farkli makinelerde ve anlarda yapilirsa: sinama @ A1 ozet b47af357c8f24d3c uretim @ B ozet 9ee506035c8f4235 uretim @ A2 ozet 19f12b586982fdc3 ayni ortam icin iki kosum ayni ozeti verdi: false
Ortam başına derleme üç ayrı ikili doğurur ve bu üç ikilinin içinde toplam on iki gömülü değer taşınır. Sınamanın onayladığı nesne, üretime giden nesneden dört değerde ayrılır: ortam adı, fatura kapısı, kütük düzeyi ve toplu iş eşiği. Bu dördü çıktının içinde durur; dışarıdan bakan bir kişi üç dosyanın da aynı adı taşıdığını görür, farkı yalnız özet söyler. Fatura kapısının sınama ikilisinde taklit, üretim ikilisinde gerçek olması, önceki dersin sözleşme sapması sınıfını sınamanın hiçbir koşumunda görünmez kılar.
Tek çıktı düzeninde üç ortam tek ikili paylaşır: özet üçünde de aynıdır ve gömülü değer sayısı sıfırdır. On iki değer yok olmaz, yer değiştirir — çıktının içinden ortamın künyesine geçer. Önceki dersin ölçüsüyle söylenirse: bildirilmemiş, yalnız koşumda görünen on iki fark, bildirilmiş on iki farka dönüşür. Farkın miktarı aynı kalır, saklandığı yer değişir; ölçülebilir hâle gelen de budur.
Son üç satır ortam başına derlemenin ikinci bedelini gösterir. Yapılar farklı makinelerde ve farklı anlarda koştuğunda, aynı ortam için üretilen iki çıktı bile farklı özet taşır. Özet artık ortamı adlandırmaz; koşumu adlandırır. İki özet farklı olduğunda bunun nedeninin ortam farkı mı yoksa yapı gürültüsü mü olduğu ayırt edilemez ve “üretimde koşan şey sınamada doğrulanan şey mi” sorusunun yanıtı ölçülemez hâle gelir.
Çıktının gerçekten diskte durduğu ve özetin o dosyanın özeti olduğu ayrıca doğrulanabilir.
# dogrula.sh — diskteki cikti gercekten var mi, ozeti node'un bastigiyla ayni mi ls dagitim/paket-tek-*.js shasum -a 256 dagitim/paket-tek-uretim.js | cut -c1-16 cat dagitim/paket-tek-uretim.js
dagitim/paket-tek-gelistirme.js
dagitim/paket-tek-sinama.js
dagitim/paket-tek-uretim.js
f8aa0bd8108fe25b
export const tutar = (m3) => m3 * 7.4;
export const emirAc = (no) => ({ no, tur: 'yerinde' });
export const cozumle = (s) => Number(s.slice(4));
Üç ortam için yazılan dosyaların içeriği aynıdır ve kabuktan alınan özet, node sürecinin
bastığı f8aa0bd8108fe25b değeriyle örtüşür. Dosya adları farklı olsa da nesne tektir; bu,
değişmezlik ilkesinin ölçülmüş hâlidir. Dosyaların birleşme sırasının ada göre olduğu da
görülür — dizin okuma sırası kapatıldığı için.
Zincirde Kaç Nesne Dolaşıyor
Önceki dersin zinciri bir nesnenin dört durakta ilerlemesiydi. Ölçüm iki düzenin bu zincire ne verdiğini de sayar. Ortam başına derlemede zincirde üç ayrı nesne dolaşır ve üç ayrı yapı koşumu yapılır; her koşum, taramada sayılan dört bozucu kaynağın önüne yeniden açıktır. Tek çıktı düzeninde nesne birdir, koşum birdir ve halkalar arası geçiş bir kopyalama işlemine iner.
Fark, sorulabilecek soruların sayısında görünür. Tek çıktı düzeninde “sınamada doğrulanan nesne üretime giden nesne mi” sorusu tek bir özet karşılaştırmasıyla yanıtlanır: iki değer eşitse evet, değilse hayır. Ortam başına derlemede aynı soru sorulamaz bile, çünkü iki ortamın özeti zaten tanım gereği farklıdır ve farkın hangi kısmının ortam ayarından, hangi kısmının yapı gürültüsünden geldiği ayrıştırılamaz. Ölçülebilir bir güvence, ölçülemeyen bir alışkanlığa dönüşür.
Ayrım teslimat ölçütlerine de yazılır. Bir düzeltmenin üretime ulaşması ortam başına derlemede üç yapı koşumu gerektirir; üçünün de başarılı olması ve doğru ortama gitmesi gerekir, bu da öncü süreye üç kez yapı ve üç kez dağıtım hazırlığı ekler. Geri dönüş oranı tarafında ise doğrulanmamış ikili sınıfı vardır: sınamadan geçen nesne üretime gitmediği için, sınamanın yakaladığı hata sınıflarının bir bölümü üretimde yeniden doğabilir. Tek çıktı düzeni bu iki ölçütü aynı yönde oynatır — ama bedeli, ortama göre değişen on iki değeri çıktının dışında bir yerde tutmayı zorunlu kılmasıdır.
Özet
- Yapı çıktısı kaynaktan üretilen ve dağıtılan tek nesnedir; değişmezlik ilkesi bu nesnenin bir kez üretilmesini, yerinde değiştirilmemesini ve her ortama aynısının gitmesini söyler.
- İki çıktının aynı olup olmadığı yalnız içerik özetiyle söylenebilir; dosya adı, tarih ve sürüm etiketi bunu söylemez.
- Yeniden üretilebilirliği bozan dört kaynak ölçüldü — zaman damgası, dizin okuma sırası, ortam değişkeni, mutlak yol — ve her biri tek başına ikinci makinede farklı özet üretmeye yetti. Dördü kapatıldığında üç koşum tek özet verdi.
- Bu dört kaynağın üçü aynı makinedeki iki koşumda görünmez; fark ancak yapı başka bir makinede koştuğunda ortaya çıkar. Gizlediği hata sınıfı doğrulanmamış ikilidir.
- Ortam başına yeniden derleme üç ayrı ikili ve on iki gömülü değer üretti; tek çıktı düzeninde ikili sayısı bire, gömülü değer sayısı sıfıra indi. Değerler yok olmadı, çıktının içinden ortamın künyesine geçti.
Sonraki Adım
Tek çıktı düzeni bir soruyu yanıtsız bıraktı: üretilen bu tek nesne nerede durur ve nasıl adlandırılır? Bir özet kimliktir ama ad değildir; on altı onaltılık basamağı dağıtım kayıtlarına elle yazmak sürdürülebilir bir düzen değildir. Ayrıca çıktı bir kez üretilip her yere dağıtılacaksa, üretildiği yerden dağıtılacağı yere kadar bir yerde saklanması ve geri alma gerektiğinde önceki sürümün hâlâ bulunabilmesi gerekir. Sonraki ders bu saklama yerini ölçer: sürüm nasıl etiketlenir, değişmez sürüm ile taşınabilir etiket arasındaki fark kaç dağıtımda kendini gösterir ve saklama süresi kuralı geri alma penceresini nerede kapatır?
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.