Ders 11 / 17
İş Mantığı Açıkları
İş mantığı açığının akış sırası olarak modellenmesi: beş adımlı bir itiraz ve iade akışında dört adımlık altı yüz yirmi beş yolun beş yüz biri adım başına yetkilendirmede kural dışı sonuç verir, üç yüz altmış dokuzu ödeme yapar ve yüz on üçü kotayı aşar; durum makinesi bu sayıları sıfıra indirip ödeme yapan yolu bire düşürür, geçişi bildirilmemiş altıncı adım eklendiğinde adım başına kurulumda kural dışı yol bin yüz yetmiş ikiye çıkarken durum makinesinde sıfır kalır ve dizi uzunluğu büyüdükçe kural dışı yolların payı yüzde altmıştan yüzde doksana yükselir.
İçindekiler
Önceki ders yetki sorusunu tek bir istek üzerinde sordu ve üç sayı verdi: kaç uç nesne düzeyinde denetim yapıyor, kaç istek yanlış geçiyor, merkezî kapı kaçını kapatıyor. O ölçümün tamamı tek istek varsayımına dayanıyordu. Bir isteğin tek başına doğru olması ise bir kuralın korunduğu anlamına gelmez.
Bir sistemin kurallarının bir bölümü tek isteklerde değil, istek dizilerinde yaşar. Bir iade bir kez ödenir, bir indirim bir dönemde bir kez uygulanır, bir onay ödemeden önce gelir. Bu cümlelerin hiçbiri tek bir ucun içine yazılamaz; hepsi adımların sırası ve tekrarı hakkındadır. İş mantığı açığı bu kuralların denetlenmediği yerde doğar: her adım tek tek yetkilidir, dizinin kendisi yetkisizdir.
Kural Adımların Sırasında
Ölçüm için bir akış adım kümesine indirgenir. Her adımın iki özelliği yazılır: etkisi (ödeme, indirim) ve durum makinesinde hangi önceki durumlardan sonra gelebileceği. İkinci alan boş bırakılırsa adım hiçbir durumda çalışamaz — bu, önceki dersteki öntanımlı retin akış karşılığıdır.
İki kurulum karşılaştırılır. Adım başına yetkilendirme her adımı kendi başına yetkilendirir ve sırayı hiç sormaz; bu, uç başına yetki denetimi yapan bir servisin davranışıdır. Durum makinesi her adımdan önce mevcut durumu sınar ve izinli olmayan adımı çalıştırmaz.
Aşağıdaki akış bir modeldir: gerçek bir ödeme, gerçek bir tutar ve gerçek bir para birimi yoktur; tutarlar kurgu birimdir.
// akis/model.mjs — abone portalinin fatura itiraz ve iade akisi (model): gercek bir odeme, // gercek bir tutar ve gercek bir para birimi yok. Tutarlar kurgu birimdir. // KH13: her adim iki sey tasir — etkisi (odeme, indirim) ve durum makinesinde hangi onceki // durumlardan sonra gelebilecegi. `izin` bos ise adim hicbir durumda calisamaz. export const ETKI = { "basvur": { izin: ["yok"], sonra: "basvuruldu" }, "incele": { izin: ["basvuruldu"], sonra: "incelendi" }, "onayla": { izin: ["incelendi"], sonra: "onaylandi" }, "iade-ode": { izin: ["onaylandi"], sonra: "kapandi", odeme: 100 }, "indirim-uygula": { izin: ["basvuruldu", "incelendi"], sonra: null, indirim: 1 }, }; export const ADIM = Object.keys(ETKI); export const KOTA = 1; // indirim kotasi: donem basina bir kez // KH14: iki kurulum ayni adim dizisini kosar. "adim-basina" kurulumunda her adim kendi // yetkisini tasir ve sira sorulmaz; "durum-makinesi" kurulumunda izinli olmayan adim atlanir. export function kosu(dizi, kurulum, etki = ETKI) { const d = { durum: "yok", odeme: 0, indirim: 0 }; for (const ad of dizi) { const e = etki[ad]; if (kurulum === "durum-makinesi") { if (!e.izin.includes(d.durum)) continue; if (e.indirim && d.indirim >= KOTA) continue; } d.odeme += e.odeme ?? 0; d.indirim += e.indirim ?? 0; if (e.sonra) d.durum = e.sonra; } return d; } // KH15: yol kumesi, verilen adim kumesinden uretilen sabit uzunluktaki butun dizilerdir. export function diziler(adimlar, uzunluk) { let l = [[]]; for (let i = 0; i < uzunluk; i++) l = l.flatMap((d) => adimlar.map((a) => [...d, a])); return l; }
// akis/olcum.mjs — adimlarin atlanabildigi ya da tekrarlanabildigi kac yol var, durum // makinesi kacini kapatiyor, kaci disarida kaliyor. import { ETKI, ADIM, KOTA, kosu, diziler } from "./model.mjs"; const yaz = (g, ...s) => console.log(s.map((v, i) => (g[i] < 0 ? String(v).padEnd(-g[i]) : String(v).padStart(g[i]))).join("")); const KOD = { b: "basvur", i: "incele", o: "onayla", d: "iade-ode", n: "indirim-uygula" }; const A = [-10, 10, 12, 12, 14]; console.log("1. dort adimlik ornek diziler (b=basvur i=incele o=onayla d=iade-ode n=indirim)"); yaz(A, "dizi", "AB odeme", "AB indirim", "DM odeme", "DM indirim"); for (const kisa of ["biod", "bdio", "bidd", "bddd", "bnnn"]) { const dizi = [...kisa].map((c) => KOD[c]); const a = kosu(dizi, "adim-basina"), m = kosu(dizi, "durum-makinesi"); yaz(A, [...kisa].join(" "), a.odeme, a.indirim, m.odeme, m.indirim); } // KH16: bir yol, adim-basina kurulumda durum makinesinden daha comert bir sonuc veriyorsa // kural disidir. Asagidaki sayim butun yollari gercekten kosturur. const UZUNLUK = 4; const olc = (adimlar, etki, uzunluk = UZUNLUK) => { const yol = diziler(adimlar, uzunluk); const s = { yol: yol.length, odeyen: [0, 0], fazla: [0, 0], kota: [0, 0], disi: [0, 0], ayni: 0 }; for (const d of yol) { const k = [kosu(d, "adim-basina", etki), kosu(d, "durum-makinesi", etki)]; for (let i = 0; i < 2; i++) { if (k[i].odeme > 0) s.odeyen[i]++; if (k[i].odeme > 100) s.fazla[i]++; if (k[i].indirim > KOTA) s.kota[i]++; if (k[i].odeme > k[1].odeme || k[i].indirim > k[1].indirim) s.disi[i]++; } if (k[0].odeme === k[1].odeme && k[0].indirim === k[1].indirim) s.ayni++; } return s; }; const t = olc(ADIM, ETKI); const B = [-34, 16, 16]; console.log(`\n2. uzunluk ${UZUNLUK} dizilerin tamami (${ADIM.length} adim, ${t.yol} yol)`); yaz(B, "olcum", "adim-basina", "durum-makinesi"); for (const [ad, alan] of [["odeme yapan yol", "odeyen"], ["100 biriminden cok odeyen", "fazla"], ["indirim kotasini asan", "kota"], ["kural disi sonuc veren", "disi"]]) yaz(B, ad, t[alan][0], t[alan][1]); console.log(`iki kurulumun ayni sonucu verdigi yol: ${t.ayni} / ${t.yol}` + ` (%${((t.ayni / t.yol) * 100).toFixed(1)})`); // 3. bildirilmemis yeni adim: gecisi tanimlanmamis oldugu icin durum makinesi onu calistirmaz const GENIS = { ...ETKI, "ek-odeme": { izin: [], sonra: null, odeme: 100 } }; const g = olc(Object.keys(GENIS), GENIS); const C = [-28, 7, 8, 14, 13]; console.log("\n3. gecisi bildirilmemis yeni adim (ek-odeme) eklendiginde"); yaz(C, "kurulum", "adim", "yol", "odeme yapan", "kural disi"); for (const [ad, s, i, n] of [["adim-basina", t, 0, 5], ["durum-makinesi", t, 1, 5], ["adim-basina, yeni adim", g, 0, 6], ["durum-makinesi, yeni adim", g, 1, 6]]) yaz(C, ad, n, s.yol, s.odeyen[i], s.disi[i]); // 4. dizi uzunlugu buyudukce yol kumesi ve kural disi yol sayisi const D = [-9, 9, 16, 18, 16]; console.log(`\n4. dizi uzunlugu buyudukce (${ADIM.length} adim)`); yaz(D, "uzunluk", "yol", "AB kural disi", "AB kural disi %", "DM kural disi"); for (let u = 2; u <= 6; u++) { const s = olc(ADIM, ETKI, u); yaz(D, u, s.yol, s.disi[0], ((s.disi[0] / s.yol) * 100).toFixed(1), s.disi[1]); }
1. dort adimlik ornek diziler (b=basvur i=incele o=onayla d=iade-ode n=indirim) dizi AB odeme AB indirim DM odeme DM indirim b i o d 100 0 100 0 b d i o 100 0 0 0 b i d d 200 0 0 0 b d d d 300 0 0 0 b n n n 0 3 0 1 2. uzunluk 4 dizilerin tamami (5 adim, 625 yol) olcum adim-basina durum-makinesi odeme yapan yol 369 1 100 biriminden cok odeyen 113 0 indirim kotasini asan 113 0 kural disi sonuc veren 501 0 iki kurulumun ayni sonucu verdigi yol: 124 / 625 (%19.8) 3. gecisi bildirilmemis yeni adim (ek-odeme) eklendiginde kurulum adim yol odeme yapan kural disi adim-basina 5 625 369 501 durum-makinesi 5 625 1 0 adim-basina, yeni adim 6 1296 1040 1172 durum-makinesi, yeni adim 6 1296 1 0 4. dizi uzunlugu buyudukce (5 adim) uzunluk yol AB kural disi AB kural disi % DM kural disi 2 25 15 60.0 0 3 125 90 72.0 0 4 625 501 80.2 0 5 3125 2686 86.0 0 6 15625 14075 90.1 0
Sayılar ölçüm sınıfındadır; girdileri KH13–KH16’dır.
Beş Dizi, Üç Kusur Türü
Birinci tablo beş örnek diziyi iki kurulumda koşturuyor. İlk satır beklenen akıştır: başvuru, inceleme, onay, ödeme. İki kurulum da 100 birim ödüyor.
İkinci satır adım atlamadır: ödeme, onaydan önce geliyor. Adım başına yetkilendirmede 100 birim ödeniyor, çünkü ödeme adımının kendisi yetkilidir ve önündeki adımların tamamlanıp tamamlanmadığı sorulmuyor. Durum makinesinde aynı dizi 0 ödüyor.
Üçüncü ve dördüncü satırlar adım tekrarıdır: aynı ödeme adımı iki ve üç kez geçiyor, sonuç 200 ve 300 birim. Bu iki dizide ödeme adımı her seferinde tek başına yetkilidir; eksik olan, adımın bir kez çalışabileceği bilgisidir.
Beşinci satır kota aşımıdır. İndirim adımı üç kez uygulanıyor ve adım başına yetkilendirmede indirim 3’e çıkıyor. Durum makinesinde 1’de kalıyor, çünkü kota da bir durum bilgisidir: indirim uygulandıktan sonra ikinci uygulama izinli değildir.
Üç kusur türünün ortak yanı, hiçbirinin yetki denetimini kırmamasıdır. Her adım doğru rolle, doğru nesne üzerinde, doğru ölçütle çağrıldı. Kırılan şey akışın kendisidir.
Bu, önceki dersteki ölçümün niçin yetmediğini de gösteriyor. Yetki matrisi on dört ucun beşinde eksik ölçüt buldu; burada bütün ölçütler yerinde olsa bile 501 yol kural dışı sonuç veriyor. İki ölçüm birbirinin yerine geçmez: biri kimin hangi nesneye dokunabildiğini, öteki hangi sırayla dokunabildiğini denetler. Bir uç için “yetki denetimi tamam” demek, o ucun bir dizinin ortasında çağrılmasına karşı hiçbir şey söylemez.
Altı Yüz Yirmi Beş Yolun Beş Yüz Biri
İkinci tablo bütün yol kümesini sayar. Beş adımdan üretilen dört adımlık dizi sayısı 625’tir ve her biri iki kurulumda gerçekten koşturulur.
Yol kümesinin sabit uzunlukta üretilmesi bilinçli bir seçimdir. Gerçek bir istemci istediği adımı istediği sayıda çağırabilir; dört adımlık pencere, bu serbestliğin sayılabilir bir kesitidir. Kesit büyütüldüğünde sonuç değişmez, yalnız sayılar büyür — dördüncü tablo bunu gösteriyor. Ölçümün geçerliliği pencerenin genişliğine değil, aynı pencerenin iki kurulumda da kullanılmasına dayanır.
Adım başına yetkilendirmede 369 yol ödeme yapıyor; durum makinesinde 1. Aradaki fark 368 yoldur ve hepsi onaysız ödemedir. 113 yol 100 birimden çok ödüyor — bunlar ödeme adımının iki ya da üç kez geçtiği dizilerdir. Aynı sayı indirim kotası için de çıkıyor: 113 yol kotayı aşıyor.
Toplam sayı son satırdadır: 501 yol durum makinesinden daha cömert bir sonuç veriyor — yol kümesinin %80,2’si. İki kurulumun aynı sonucu verdiği yol yalnız 124, yani %19,8. Bu oran, sırayı denetlemeyen bir akışta “doğru davranan” yolların azınlıkta olduğunu söylüyor: kural dışı sonuç istisna değil, çoğunluktur.
Sayının bu kadar büyük olması adım kümesinin küçüklüğüne karşın şaşırtıcı değildir. Yol sayısı adım sayısının uzunluk kuvvetidir; beş adım ve dört uzunluk 625 verir, altı adım ve dört uzunluk 1.296. Akışa her yeni adım eklendiğinde denetlenmesi gereken dizi sayısı katlanarak artar. Bu yüzden kural dizi başına yazılamaz; durum başına yazılmalıdır.
Dördüncü tablo bu büyümeyi ölçüyor. Dizi uzunluğu 2’den 6’ya çıktığında yol sayısı 25’ten 15.625’e, adım başına kurulumda kural dışı yol 15’ten 14.075’e çıkıyor. Asıl bilgi son sütunda değil, oran sütununda: kural dışı yolların payı %60,0’tan %90,1’e yükseliyor. Dizi uzandıkça doğru davranan yolların payı düşer, çünkü doğru sıra tektir ve yanlış sıraların sayısı uzunlukla birlikte katlanır. Durum makinesi sütunu beş satırın beşinde de sıfırdır — geçiş tablosu dizi uzunluğundan bağımsızdır.
Bu, sırayı sınama yoluyla korumanın niçin yetmediğini de söylüyor. Dört adımlık dizilerin tamamını sınayan bir sınama kümesi 625 durum içerir ve beş adımda 3.125’e çıkar; beş satırlık bir geçiş tablosu ise değişmez. Kural dışı yol sayısını sıfırda tutan şey sınama sayısı değil, kararın yerleştiği yerdir.
Durum Makinesi Ne Kapatıyor, Neye Mal Oluyor
Durum makinesi 625 yolun 624’ünü kapatır ve kural dışı yol sayısını sıfıra indirir. Kapatma mekanizması bir denetim listesi değildir: her adımın izinli önceki durumları yazılıdır ve bu listede olmayan her geçiş reddedilir. Beş satırlık bir tablo, 501 kural dışı yolu birden kapatır.
Üçüncü tablo yeni adım sorusunu yanıtlar. Geçişi bildirilmemiş altıncı bir adım —
ek-odeme — eklendiğinde adım başına kurulumda yol sayısı 1.296’ya, ödeme yapan yol
1.040’a, kural dışı yol 1.172‘ye çıkıyor. Durum makinesinde aynı adım hiçbir yol açmıyor:
ödeme yapan yol 1’de, kural dışı yol 0’da kalıyor. izin listesi boş olduğu için adım hiçbir
durumdan sonra çalışmıyor.
Bedeli de buradadır ve yazılmalıdır. Bildirilmemiş adım hiç çalışmaz; yeni özellik
geçiş tablosuna eklenene kadar ölü kalır. Aynı bedel meşru esneklikte de görülür: indirim
adımı yalnız basvuruldu ve incelendi durumlarında izinlidir, dolayısıyla onaydan sonra
indirim uygulamak isteyen meşru bir iş kabul edilmez. Bu isteği karşılamak için tabloya bir
satır eklemek gerekir ve o satır bilinçli bir karardır — kod incelemesinde görülebilir,
sınanabilir ve geri alınabilir.
Bedelin ölçüsü de tablodadır. Durum makinesinde ödeme yapan yol sayısı 1’dir; adım başına kurulumda 369. Bu 368 yolun tamamı kapatılmıştır ve kullanıcının akışı tamamlamak için izleyebileceği tek bir sıra kalmıştır. Bir akışta meşru esneklik varsa — belgenin inceleme öncesinde de eklenebilmesi, indirimin iki farklı noktada uygulanabilmesi — bu esneklik geçiş tablosunda satır olarak görünmek zorundadır. Tabloda olmayan esneklik ürün kararı değil, kapatılmış bir yoldur.
Bir ayrıntı gerçekleştirimde önemlidir. Yukarıdaki modelde izinli olmayan adım sessizce atlanıyor; gerçek bir serviste atlamak yerine ret döndürmek gerekir. Sessiz atlama, çağıranın adımın çalıştığını sanmasına yol açar ve bu, kapatılan açığın yerine bir tutarlılık hatası koyar. Durum makinesinin değeri kararı vermesindedir; kararın bildirilmesi ayrı bir sorumluluktur.
Özet
- İş mantığı açığı tek bir isteğin yetkisinde değil, adım dizisinin sırasında ve tekrarında doğar; her adım tek tek yetkiliyken dizinin kendisi yetkisiz olabilir.
- Üç kusur türü ölçüldü: adım atlama (onaysız ödeme), adım tekrarı (200 ve 300 birim ödeme) ve kota aşımı (indirimin üç kez uygulanması).
- Beş adımdan üretilen 625 dört adımlık yolun 369’u ödeme yapıyor, 113’ü 100 birimden çok ödüyor, 113’ü kotayı aşıyor ve 501’i (%80,2) kural dışı sonuç veriyor.
- Durum makinesi kural dışı yolu sıfıra, ödeme yapan yolu bire indirir; kapatma mekanizması beş satırlık bir geçiş tablosudur, dizi başına yazılmış bir denetim değil.
- Geçişi bildirilmemiş altıncı adım eklendiğinde adım başına kurulumda kural dışı yol 501’den 1.172’ye çıkarken durum makinesinde 0 kalır; dizi uzunluğu 2’den 6’ya çıktığında kural dışı yolların payı %60,0’tan %90,1’e yükselirken durum makinesi sütunu sıfırda kalır.
- İzinli olmayan adım sessizce atlanmaz, reddedilir; sessiz atlama açığın yerine bir tutarlılık hatası koyar.
Sonraki Adım
Bu dersin bütün ölçümü tek bir varsayıma dayanıyor: adımlar birbiri ardına çalışıyor.
Durum makinesi, ödeme adımına geldiğinde durumun onaylandi olduğunu okuyor, kararı veriyor
ve durumu kapandi yapıyor. Okuma ile yazma arasında geçen sürede başka hiçbir şeyin
olmadığı varsayılıyor. İki istek aynı anda geldiğinde bu varsayım düşer: ikisi de durumu
onaylandi okuyabilir, ikisi de kararı geçebilir ve ikisi de ödeyebilir. Sonraki ders
eşzamanlı istek modelinde aynı kontrolün kaç kez geçtiğini sayar ve kilit ile tek yazarlı
kuyruğun kaçını kapattığını ölçer.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.