Ders 11 / 12
On İki Etmen Uygulama
On iki ilkenin sekizi diskteki dosyalara bakıp karar veren koşturulabilir bir denetime çevrilir ve kurgu bir uygulamanın gerçek dosyaları üzerinde koşturulur; yerleştirilmiş sekiz kusurdan kaçının yakalandığı, kaçının kaçtığı ve kaç yanlış alarm verildiği sayılır, çevrilemeyen dört ilkenin yerine ne konduğu yazılır.
İçindekiler
On iki etmen uygulama ilkeleri, uygulamayı yazan ile onu çalıştıran arasındaki sözleşmeyi adlandırır. İlkelerin ne söylediği Sunucu Tarafı Temelleri kursunda kurulmuştu ve burada tekrarlanmaz. Bu dersin sorusu başkadır: bir ilke, diskteki dosyalara bakıp evet ya da hayır diyen koşturulabilir bir denetime çevrilebiliyor mu? Çevrilebiliyorsa denetim neyi yakalıyor, neyi kaçırıyor, neye boş yere alarm veriyor? Çevrilemiyorsa yerine ne konuyor?
Bir ilkenin karar taşıması, cümlesinin doğru görünmesine değil, ihlal edildiğinde birinin bunu göreceğine bağlıdır. Denetime çevrilemeyen ilke ancak biri bakmayı akıl ettiğinde görünür ve akıl edilmesi gereken bakış, ölçülmemiş bakıştır. Ölçüm yine bölgesel ölçüm ağı üzerinde yapılır: su sayacı okumalarını toplayan, doğrulayan, faturaya çeviren ve saha iş emri açan kurgu bir yazılım. Aşağıdaki dosya ağacı gerçekten diske yazılır, denetleyici gerçekten koşar; dosyaların ve bağımlılık adlarının tamamı kurgudur.
DK23. Denetleyici yalnız diskteki dosya ağacını görür; depo geçmişini, işletim adımlarını ve ekip alışkanlıklarını görmez. DK24. Bir ilke, tek bir ağaç üzerinde tek bir koşumda ikili karar üretebiliyorsa çevrilebilir sayılır; karar için ikinci bir ortam, ikinci bir süreç ya da insan yorumu isteyen ilke çevrilemez sayılır. DK25. Kusurlar taramadan önce bilerek yerleştirilir ve sayıları bilinir; kaçanın sayılabilmesi buna bağlıdır. DK26. Denetimlerin tamamı dizgi ve dosya düzeyindedir; sözdizim ağacı çözümlemesi yoktur. Kaçan kusurların sınıfı doğrudan bu seçimden doğar.
Denetime Çevrilen Sekiz İlke
Aşağıdaki tablo ilkenin ne söylediğini değil, ilkenin diskte neye baktığını yazar.
| İlke | Denetimin baktığı şey | Kararı veren |
|---|---|---|
| Bağımlılıklar | bildirim dosyasının yanında kilit dosyası var mı | dosya varlığı |
| Yapılandırma | kaynakta gömülü varsayılan ve gömülü sunucu adresi | ?? kalıbı, adres kalıbı |
| Bağlantı noktası bağlama | kaynak kendi soketini açıyor mu | .listen( çağrısı |
| Kütükler | kütük standart çıktı yerine dosyaya mı yazılıyor | dosya yazma çağrısı |
| Tek kullanımlık olma | bildirilen her giriş noktası kapanma sinyalini işliyor mu | SIGTERM dizgesi |
| Süreçler | süreç belleğinde kalıcı durum tutuluyor mu | üst düzey değişebilir kap |
| Derle, yayımla, çalıştır | yapı çıktısına ortam değeri gömülmüş mü | adres kalıbı |
| Yönetim süreçleri | tek seferlik iş uygulamanın kodunu kullanıyor mu | içe aktarım yolu |
Sekizinin ortak yanı, kararın tek bir ağaçtan okunabilmesidir. Kalan dört ilke bu tabloya girmedi; nedenleri ölçümden sonra sayılır.
Üçüncü sütun, tablonun asıl bilgisini taşır. İlke bir niyet cümlesidir; denetim ise bir metin kalıbıdır. Aradaki çeviri her satırda bir daraltmadır: “kütükler bir olay akışıdır” cümlesi “kaynak dizininde dosya yazma çağrısı bulunmasın” kuralına indirgenir, “süreçler durumsuzdur” cümlesi “üst düzeyde değişebilir kap bildirilmesin” kuralına. Daraltma iki yönde de kayıp üretir. Kural ilkeden dar kaldığı yerde kusur kaçar; ilkenin kapsamadığı bir dosyaya uzandığı yerde yanlış alarm doğar. Aşağıdaki sayılar bu iki kaybın ikisini de ayrı ayrı ölçer, çünkü tek bir “geçti / kaldı” toplamı hangi yönde kaybedildiğini söylemez.
Kurgu Uygulamanın Dosyaları
#!/usr/bin/env bash # Kurgu olcum agi uygulamasinin dosya agacini diske yazar. Bagimlilik adlari da kurgudur. set -e mkdir -p kaynak sinama betik yapi cat > paket.json <<'SON' { "ad": "olcum-agi", "tur": "modul", "girisler": ["kaynak/sunucu.mjs", "betik/gecelik-duzeltme.mjs"], "bagimlilik": { "olcum-cozumleyici": "2.4.0", "fatura-bicimleyici": "1.1.3" } } SON cat > kaynak/ayar.mjs <<'SON' export const ayar = { ortam: process.env.ORTAM_ETIKETI, veriAdresi: process.env.OLCUM_VERI_ADRESI, baglantiNoktasi: Number(process.env.OLCUM_BAGLANTI_NOKTASI), yenidenOkumaGun: Number(process.env.YENIDEN_OKUMA_GUN ?? 30), }; SON cat > kaynak/sunucu.mjs <<'SON' import { createServer } from "node:http"; import { ayar } from "./ayar.mjs"; export const sunucu = createServer((istek, yanit) => yanit.end(ayar.ortam ?? "yok")); sunucu.listen(ayar.baglantiNoktasi); SON cat > kaynak/kutuk.mjs <<'SON' import { appendFileSync } from "node:fs"; export const kutuk = (k) => appendFileSync("/var/kutuk/olcum.log", JSON.stringify(k) + "\n"); SON cat > kaynak/rapor.mjs <<'SON' import { writeFileSync } from "node:fs"; export const faturayiYaz = (f) => writeFileSync(`cikti/fatura-${f.no}.json`, JSON.stringify(f)); SON cat > kaynak/toplu-is.mjs <<'SON' const IS_EMRI_ADRESI = "is-emri-uretim.ic:8081"; export const kuyruk = (() => { const bekleyen = new Map(); return { ekle: (id, v) => bekleyen.set(id, v), sayi: () => bekleyen.size, IS_EMRI_ADRESI }; })(); SON cat > sinama/sahte-olcum.mjs <<'SON' // Surec ici sahte olcum deposu; adres gercek bir hedefe gitmez. export const sahteAdres = "127.0.0.1:5432"; SON cat > betik/gecelik-duzeltme.mjs <<'SON' const ADRES = process.env.OLCUM_VERI_ADRESI; console.log(`duzeltme calisti -> ${ADRES}`); SON cat > yapi/paket.mjs <<'SON' // Yapi cikti dosyasi (kurgu): uretim adresi derleme aninda gomuldu. export const HEDEF = "olcum-uretim.ic:" + 5432; SON
Bu ağaca sekiz kusur yerleştirilmiştir. Sayıları denetleyicinin içinde yazılı olduğu için kaçan da sayılabilir; denetimlerin hiçbiri o listeyi okumaz.
Koşum
// denetim.mjs — cevrilebilen ilkeleri kurgu uygulamanin gercek dosyalari uzerinde kosturur. import { readFileSync, existsSync } from "node:fs"; const DOSYA = ["paket.json", "kaynak/ayar.mjs", "kaynak/sunucu.mjs", "kaynak/kutuk.mjs", "kaynak/rapor.mjs", "kaynak/toplu-is.mjs", "sinama/sahte-olcum.mjs", "betik/gecelik-duzeltme.mjs", "yapi/paket.mjs"]; const metin = Object.fromEntries(DOSYA.map((y) => [y, readFileSync(y, "utf8")])); const alt = (on) => DOSYA.filter((y) => y.startsWith(on)); const ara = (kapsam, kalip) => kapsam .flatMap((y) => metin[y].split("\n").map((s, i) => ({ yol: y, satirNo: i + 1, satir: s }))) .filter((b) => kalip.test(b.satir)); const not = (yol, satir) => ({ yol, satirNo: 0, satir }); const ADRES = /["'][\w.-]+:\d{4,5}["']/; const DENETIM = [ ["bagimliliklar", () => existsSync("paket.kilit") ? [] : [not("paket.json", "paket.kilit yok")]], ["yapilandirma", () => [...ara(alt("kaynak/"), /process\.env\.\w+\s*\?\?/), ...ara([...alt("kaynak/"), ...alt("sinama/"), ...alt("betik/")], ADRES)]], ["baglanti-noktasi", () => ara(alt("kaynak/"), /\.listen\(/).length ? [] : [not("kaynak/", "listen cagrisi yok")]], ["kutukler", () => ara(alt("kaynak/"), /(appendFileSync|writeFileSync|createWriteStream)\(/)], ["tek-kullanimlik", () => JSON.parse(metin["paket.json"]).girisler .filter((y) => !metin[y].includes("SIGTERM")).map((y) => not(y, "SIGTERM isleyicisi yok"))], ["surecler", () => ara(alt("kaynak/"), /^(const|let|var)\s+\w+\s*=\s*new (Map|Set)\(/)], ["derle-yayimla-calistir", () => ara(alt("yapi/"), ADRES)], ["yonetim-surecleri", () => alt("betik/").filter((y) => !metin[y].includes("kaynak/")) .map((y) => not(y, "kaynak/ altindan ice aktarim yok"))], ]; const YERLESIK = [ // taramadan once yerlestirilen kusurlar ["paket.json", "paket.kilit"], ["kaynak/ayar.mjs", "?? 30"], ["kaynak/toplu-is.mjs", "is-emri-uretim.ic"], ["kaynak/kutuk.mjs", "appendFileSync"], ["kaynak/sunucu.mjs", "SIGTERM"], ["kaynak/toplu-is.mjs", "new Map("], ["yapi/paket.mjs", "olcum-uretim.ic:"], ["betik/gecelik-duzeltme.mjs", "kaynak/"], ]; const bulgu = DENETIM.flatMap(([ad, f]) => f().map((b) => ({ ...b, denetim: ad }))); const gercek = (b) => YERLESIK.some(([y, iz]) => y === b.yol && b.satir.includes(iz)); const kacan = YERLESIK.filter(([y, iz]) => !bulgu.some((b) => b.yol === y && b.satir.includes(iz))); const yanlis = bulgu.filter((b) => !gercek(b)); const satir = (a, b, c, d) => a.padEnd(24) + b.padStart(6) + c.padStart(8) + d.padStart(8); console.log(satir("denetim", "bulgu", "gercek", "yanlis")); for (const [ad, f] of DENETIM) { const b = f(); console.log(satir(ad, `${b.length}`, `${b.filter(gercek).length}`, `${b.filter((x) => !gercek(x)).length}`)); } console.log(`cevrilebilen ilke: ${DENETIM.length} cevrilemeyen: ${12 - DENETIM.length}`); console.log(`yerlestirilen kusur: ${YERLESIK.length} bildirilen bulgu: ${bulgu.length}`); console.log(`yakalanan: ${YERLESIK.length - kacan.length} kacan: ${kacan.length} ` + `yanlis alarm: ${yanlis.length}`); for (const [y, iz] of kacan) console.log(` [KACAN ] ${y} iz=${iz}`); for (const b of yanlis) console.log(` [YANLIS] ${b.yol}:${b.satirNo} denetim=${b.denetim}`);
denetim bulgu gercek yanlis bagimliliklar 1 1 0 yapilandirma 3 2 1 baglanti-noktasi 0 0 0 kutukler 2 1 1 tek-kullanimlik 2 1 1 surecler 0 0 0 derle-yayimla-calistir 0 0 0 yonetim-surecleri 1 1 0 cevrilebilen ilke: 8 cevrilemeyen: 4 yerlestirilen kusur: 8 bildirilen bulgu: 9 yakalanan: 6 kacan: 2 yanlis alarm: 3 [KACAN ] kaynak/toplu-is.mjs iz=new Map( [KACAN ] yapi/paket.mjs iz=olcum-uretim.ic: [YANLIS] sinama/sahte-olcum.mjs:2 denetim=yapilandirma [YANLIS] kaynak/rapor.mjs:2 denetim=kutukler [YANLIS] betik/gecelik-duzeltme.mjs:0 denetim=tek-kullanimlik
Yakalanan, Kaçan, Yanlış Alarm
Sekiz kusurdan altısı yakalandı, ikisi kaçtı ve denetimler üç yanlış alarm verdi. Tabloda okunması
gereken asıl satırlar sıfır bildirenlerdir. baglanti-noktasi sıfır bulgu bildirdi ve haklıydı:
kaynak kendi soketini açıyor. surecler ile derle-yayimla-calistir de sıfır bildirdi ve
ikisi de yanılıyordu — kusur her ikisinde de yerindeydi. Sıfır bulgu kusursuzluğu değil,
kuralın kapsamını ölçer ve tablo bu iki durumu aynı biçimde yazar.
Kaçan iki kusurun sınıfı ortaktır. toplu-is.mjs süreç belleğinde bir eşleme tutuyor, ama eşleme
bir kapanışın içinde: kural üst düzey bir bildirim arıyor, kap ise iki boşluk içeride. Yapı
çıktısındaki üretim adresi ise derleme anında birleştirilmiş ("olcum-uretim.ic:" + 5432), yani
adres kalıbının aradığı biçimde hiç durmuyor. İkisi de dizgi düzeyinde çalışan bir kuralın yapı
düzeyinde duran bir olguyu göremediği örneklerdir; bir önceki dersin sır tarayıcısı da tam bu
sınıfı kaçırmıştı. Bu, denetimlerin dikkatsizliği değil, DK26’nın doğrudan sonucudur — kural
dizgiye bakar, kusur ağaca yerleşir.
Üç yanlış alarmın üçü de doğru davranışı işaretledi. Sınama düzeneğindeki sabit adres, süreç içinde kalan sahte bir depoya işaret ediyor; orada yapılandırmadan okumak anlamsızdır. Fatura dosyası yazan çağrı bir kütük değil, ürünün kendisidir. Gecelik düzeltme betiği tek seferlik bir iştir ve kapanma sinyali işlemesi gerekmez. Üçü de kuralın kapsamı ile ilkenin konusu arasındaki açıklıktan doğuyor: kural dosyaya bakıyor, ilke ise o dosyanın ne işe yaradığını soruyor.
Yanlış alarmın bedeli, kaçanın bedeliyle aynı yöne çalışır. Dokuz satırlık bir raporun üçü boşsa, raporu ikinci kez okuyan kişi satırları tek tek açmaz; kaçan iki kusurun bulunma olasılığı da bu noktada düşer. Bir denetim kümesini değerlendiren tek sayı yoktur — yakalanan, kaçan ve yanlış alarm ayrı ayrı yazılmadıkça hangisinin arttığı görünmez.
Kaçanın Koşumdaki Karşılığı
Kaçan iki kusur denetim tablosunda yalnız birer boşluktu. Boşluğun neye karşılık geldiğini görmek için ikisi de aynı süreçte çalıştırılır.
// kacan.mjs — denetimin goremedigi iki kusuru kosumda gosterir. import { kuyruk } from "./kaynak/toplu-is.mjs"; import { HEDEF } from "./yapi/paket.mjs"; kuyruk.ekle("sayac-1041", 3120); // birinci okuma paketi kuyruk.ekle("sayac-2277", 8840); // ikinci paket, ayni surec console.log(`ORTAM_ETIKETI : ${process.env.ORTAM_ETIKETI}`); console.log(`kuyrukta kalan kayit : ${kuyruk.sayi()}`); console.log(`yapi ciktisinin hedefi : ${HEDEF}`);
#!/usr/bin/env bash # Ayni yapi ciktisini iki ortam etiketiyle kosturur. for o in sinama uretim; do ORTAM_ETIKETI="$o" node kacan.mjs; done
ORTAM_ETIKETI : sinama kuyrukta kalan kayit : 2 yapi ciktisinin hedefi : olcum-uretim.ic:5432 ORTAM_ETIKETI : uretim kuyrukta kalan kayit : 2 yapi ciktisinin hedefi : olcum-uretim.ic:5432
Kuyrukta iki kayıt kaldı. İkinci okuma paketi, birincinin süreç belleğinde bıraktığını buldu. Süreç yeniden başladığında bu kayıtlar yok olur; ölçek değiştiğinde ikinci sürecin kuyruğu boş başlar ve iki süreç aynı gecelik işi iki farklı durumla koşar. Denetimin göremediği kusur, koşumda bir sayı olarak görünüyor.
İkinci satır daha keskindir. Ortam etiketi sınamayken de üretimken de yapı çıktısının hedefi aynı kalıyor: üretim veri deposunun adresi. Yapı çıktısı ortamı taşıyor, ortam yapı çıktısını değil. Sınama ortamında koşan bir gecelik toplu iş üretim verisine bakar ve bunu hiçbir yapılandırma dosyası bildirmez, çünkü fark yapılandırmada değil, derleme anında alınmış bir kararda saklıdır. Bir önceki dersin ortam farkı sayımı bu değeri hiç göremezdi; sayılan şey yapılandırma dosyalarıydı ve bu adres onların hiçbirinde geçmiyor.
Bu, kursun kuralının en dar biçimidir. Aynı yazılım iki ortamda başka davranmadı — aynı
davrandı, ve sorun tam olarak budur. Ortam etiketi değişti, davranış değişmedi; fark, ortam
değişkeninin erişemeyeceği bir yere gömüldüğü için ortamın onu değiştirme yetkisi kalmadı.
Denetim tablosundaki derle-yayimla-calistir satırının sıfırı, bu farkın üstünü örten şeydi.
İki kusuru da yakalamanın yolu bellidir: kural dizgiye değil, çözümlenmiş bir sözdizim ağacına bakar; kapanış içindeki bildirimi ve derleme anında birleştirilen değeri ancak o düzeyde görür. Bedeli DK26’nın kaldırılmasıdır — denetim artık dosyayı okumakla kalmaz, dili de çözümlemek zorundadır. Sekiz denetimin ucuzluğu bu seçimden geliyordu; ucuzluk kaybedilince denetimin sık koşma özelliği de tartışmaya açılır.
Çevrilemeyen Dört İlkenin Yerine
| İlke | Tek ağaçta neden ölçülemez | Yerine konan |
|---|---|---|
| Kod tabanı | tek kod tabanından kaç dağıtım çıktığı depo düzeyinde bir olgudur; ağaç tek bir dağıtımı gösterir | yapı çıktısının taşıdığı kaynak kimliğinin dağıtım kayıtlarında eşleşmesi |
| Destekleyici servisler | adresin yapılandırmadan geldiği denetlenir, ama kaynağın değiştirilebilir olduğu ancak değiştirilerek görülür | adres değiştirilip koşumun yinelenmesi |
| Eşzamanlılık | süreç modeliyle ölçeklendiği tek süreçlik bir ağaçtan okunamaz | iki süreçle yük altında koşum ve süreç sayısıyla değişmeyen çıktı |
| Geliştirme/üretim eşliği | iki ortamın karşılaştırılmasını ister; ağaç tek ortamdır | bir önceki dersin ortam farkı sayımı |
Dördünün ortak yanı, karar için ikinci bir gözlem istemeleridir: ikinci bir dağıtım, ikinci bir adres, ikinci bir süreç, ikinci bir ortam. Sınır tam buradan geçiyor ve adı tek gözlemle karar verilebilirliktir. Sekiz denetim tek ağaçta karar verdi; dördü karşılaştırma istedi.
Bu ayrım maliyeti de belirler. Sekiz denetim her değişiklikte saniyeler içinde koşar; kalan dördü ayrı bir ortam, ayrı bir koşum ya da yük üreten bir düzenek ister. Ucuz olan sık koşulur, pahalı olan seyrek — ve seyrek koşan ölçümün yakaladığı kusur yakalandığında çoktan üretime gitmiştir. Bir önceki dersin ortam farkı sayımı, dört pahalı ilkeden birinin yerine konan şeydi ve bu dersteki hiçbir denetim onun yerini tutmuyor.
Maliyet farkı sahiplik farkını da getirir. Sekiz ucuz denetim yazan ekibin elindedir; kod değiştiği anda koşar ve sonucunu değiştiren kişi görür. Dört pahalı ölçüm ise ikinci ortamı, ikinci süreci ya da yük düzeneğini elinde tutan işleten ekibin alanına düşer. Aynı ilke kümesinin yarısı bir ekipte, yarısı ötekinde ölçülüyor; ihlalin hangi ekibe göründüğü, ihlalin türüne göre değişiyor. Ölçüm ağının iki ekibi arasındaki sinyal farkı bu bölünmeden doğar.
Özet
- On iki ilkenin sekizi tek bir dosya ağacında karar veren denetime çevrildi, dördü çevrilemedi; ayıran ölçüt tek gözlemle karar verilebilirliktir.
- Yerleştirilen sekiz kusurdan altısı yakalandı, ikisi kaçtı; denetimler dokuz bulgu bildirdi ve üçü yanlış alarmdı.
- Sıfır bulgu bildiren üç denetimden biri haklıydı, ikisi yanılıyordu. Sıfır bulgu kuralın kapsamını ölçer, kusursuzluğu değil.
- Kaçan iki kusur da aynı sınıftan: kural dizgiye bakıyor, kusur yapıya yerleşiyor — kapanış içindeki durum ve derleme anında birleştirilen adres. Koşumda ikisi de göründü: süreç belleğinde iki kayıt kaldı, yapı çıktısının hedefi iki ortam etiketinde de üretim adresi çıktı.
- Üç yanlış alarmın üçü de doğru davranışı işaretledi; kuralın kapsamı ile ilkenin konusu arasındaki açıklıktan doğdular.
- Çevrilemeyen dört ilkenin yerine konan şeylerin hepsi ikinci bir gözlem ister; bu yüzden seyrek koşarlar ve yakaladıkları kusur geç yakalanır.
Sonraki Adım
Çevrilemeyen dört ilkenin dördü de ikinci bir gözlem istedi ve ikisi bu gözlemin adını doğrudan koydu: ikinci bir ortam. Buraya kadar ortamların var olduğu varsayıldı — geliştirme, sınama ve üretim vardı, aralarındaki fark sayıldı, aynı ağaç üzerinde denetimler koşturuldu. Ortamın kendisinin nasıl var olduğu ise hiç sorulmadı. Elle mi kuruluyor, bir betik mi kuruyor, yoksa istenen son durumu bildiren bir tanım mı? Sonraki ders bu üç biçimi karşılaştırır ve aynı soruyu sorar: aynı tanımdan iki kez kurulan iki ortam arasında kaç fark kalıyor ve o fark nerede saklanıyor?
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.