Ders 01 / 11
Mimari Karar Kayıtları
Bir kararın bağlam, karar ve sonuç alanlarıyla kayda geçirilmesi: altı ay sonra gelen birinin soruları bir soru kümesi olarak modellenir, aynı karar kümesi kayıtsız ve yapılandırılmış kayıtlı düzende o kümeye uygulanır, yanıtlanan ve yanıtlanamayan soru sayılır, kaydın yazılma maliyeti satır ve dakika olarak ölçülür ve yerine geçilmiş karar alanının eski kararı okunabilir tutup tutmadığı sayılır.
İçindekiler
Önceki kurs kararın nasıl verildiğini ölçtü: alternatif–nitelik tablosu bir veri yapısına çevrildi, ağırlıklar tarandı, kaynağı yazılmamış ağırlık bulundu. O çalışmanın tamamı tek bir yerde duruyor: kararı verenin belleğinde. Altı ay sonra “bu neden böyle” diye soran birine verilecek bir şey yok.
Bu kursun ölçüsü buradan çıkar. Bir belgenin değeri, sonradan gelen birinin sorduğu soruların kaçını yanıtladığıdır: yanıtlanan soru sayısı, yanıtlanamayan soru sayısı ve kaydın ödettiği bedel. “Belge önemlidir” bir karar taşımaz; iki düzen aynı soru kümesinde koşturulur.
Soru Kümesi ve Karar Kümesi
Through-line bölgesel bir kütüphane ağının yazılım sistemidir; kurgu bir modeldir. Modelde on dört şube, dışarıdan alınmış bir katalog sistemi ve içeride yazılmış ödünç servisleri vardır.
Mimari karar kaydı, tasarımı anlatmakta kullanılan karar kaydının yapılandırılmış biçimidir: serbest açıklama yerine adı belli alanları olan bir belge — bağlam, karar, sonuç. Alanların belli olması ölçmeyi mümkün kılar, çünkü her soru bir alana düşer.
Soru kümesi on sorudur ve her soru bir alanı gösterir; karar kümesi dört karardır. Kayıtsız düzende yanıt yalnızca çalışan koddan, sürüm geçmişinden ya da kararı verenin belleğinden gelebilir; kayıtlı düzende alanların hepsi kayıttadır. Alanların dolu hali (KK1), kaynak eşlemesi ve kararı verenin altı ay sonra ulaşılabilir olup olmadığı (KK2) modelin girdisidir.
// kayit/model.mjs — soru kumesi ve karar kumesi veri yapisi olarak; kurgu bir modeldir export const SORU = [ ["S1", "bu neden boyle", "gerekce"], ["S2", "hangi alternatif elendi", "alternatif"], ["S3", "eleme neye dayandi", "olcut"], ["S4", "hangi varsayimla verildi", "varsayim"], ["S5", "ne zaman verildi", "tarih"], ["S6", "kim verdi", "veren"], ["S7", "bilinen bedeli ne", "sonuc"], ["S8", "hala gecerli mi", "durum"], ["S9", "oncekinin yerine mi gecti", "yerine"], ["S10", "bu sayi nereden geldi", "esik"], ].map(([kod, metin, alan]) => ({ kod, metin, alan })); // Alanlarin dolu hali ve sahibin alti ay sonra ulasilabilirligi modelin girdisidir (KK1, KK2). export const KARAR = [ { ad: "odunc-erisim", sahip: false, secim: "subede salt-okunur onbellek", gerekce: "sube agi gunde iki kez kopuyor", alternatif: "tek-merkez, sube-kopya, dis-modul", olcut: "agirlikli puan taramasi", varsayim: "gunluk hacim onbellege sigar", tarih: "2. ceyrek", veren: "platform ekibi", sonuc: "gecersizlestirme bakim ister", durum: "gecerli", yerine: "sube-kopya kararinin yerine", esik: "tazelik siniri 90 sn" }, { ad: "katalog-siniri", sahip: true, secim: "yalniz esleme katmanindan erisim", gerekce: "katalog semasi surum basina degisiyor", alternatif: "dogrudan tablo, gorunum okuma", olcut: "surum basina kirilan cagri sayisi", varsayim: "disa acik arayuz kararli kalir", tarih: "2. ceyrek", veren: "tumlestirme ekibi", sonuc: "her alan iki kez yaziliyor", durum: "gecerli", yerine: "yok - ilk karar", esik: null }, { ad: "ceza-hesabi", sahip: true, secim: "tek kural motorunda toplanir", gerekce: "ceza kurali yilda birkac kez degisiyor", alternatif: "katalog ceza modulu, sube ayari", olcut: "degisiklikte dokunulan dosya sayisi", varsayim: "kural her subede ayni", tarih: "3. ceyrek", veren: "odunc ekibi", sonuc: "istisna gelirse motor buyuyor", durum: "gecerli", yerine: "yok - ilk karar", esik: "gecikme esigi 14 gun" }, { ad: "iade-uyarisi", sahip: false, secim: "kuyruk uzerinden asenkron gonderim", gerekce: "gonderim yanit suresine girmemeli", alternatif: "senkron gonderim, gunluk toplu is", olcut: "kesintinin odunc islemine yansimasi", varsayim: "bir saatlik gecikme kabul edilebilir", tarih: "4. ceyrek", veren: "odunc ekibi", sonuc: "olu mektup kutusu izlenmeli", durum: "gecerli", yerine: "yok - ilk karar", esik: "uyari penceresi 3 gun" }, ]; // Kayitsiz duzende hangi alan hangi kaynaktan okunabilir (KK2); kayitli duzende hepsi kayitta. export const KAYNAK = { kod: ["durum"], surum: ["tarih", "veren"], bellek: ["gerekce", "varsayim", "sonuc"] }; export const uygulanabilir = (k) => SORU.filter((s) => k[s.alan] !== null); export function yanitla(k, duzen) { return uygulanabilir(k).map((s) => { let kaynak = null; if (duzen === "kayitli") kaynak = "kayit"; else if (KAYNAK.kod.includes(s.alan)) kaynak = "kod"; else if (KAYNAK.surum.includes(s.alan)) kaynak = "surum"; else if (KAYNAK.bellek.includes(s.alan) && k.sahip) kaynak = "bellek"; return { soru: s.kod, kaynak, yanitlandi: kaynak !== null }; }); } if (import.meta.url.endsWith(process.argv[1].split("/").pop())) { const toplam = KARAR.reduce((t, k) => t + uygulanabilir(k).length, 0); console.log(`${SORU.length} soru x ${KARAR.length} karar -> uygulanabilir ${toplam} soru`); for (const duzen of ["kayitsiz", "kayitli"]) { let y = 0; const kaynakSay = {}; for (const k of KARAR) for (const s of yanitla(k, duzen).filter((s) => s.yanitlandi)) { y += 1; kaynakSay[s.kaynak] = (kaynakSay[s.kaynak] ?? 0) + 1; } console.log(`[${duzen}] ${y}/${toplam} yanit, ${toplam - y} yanitlanamayan, kaynak: ` + Object.entries(kaynakSay).map(([a, n]) => `${a}=${n}`).join(" ")); } console.log("\nkayitsiz duzende soru basina yanit veren karar sayisi:"); for (const s of SORU) { const uygun = KARAR.filter((k) => k[s.alan] !== null); const kac = uygun.filter((k) => yanitla(k, "kayitsiz").find((r) => r.soru === s.kod).yanitlandi); console.log(` ${s.kod.padEnd(4)} ${s.metin.padEnd(26)} ${kac.length}/${uygun.length}` + `${kac.length ? "" : " <- hicbir kararda yanit yok"}`); } }
10 soru x 4 karar -> uygulanabilir 39 soru [kayitsiz] 18/39 yanit, 21 yanitlanamayan, kaynak: surum=8 kod=4 bellek=6 [kayitli] 39/39 yanit, 0 yanitlanamayan, kaynak: kayit=39 kayitsiz duzende soru basina yanit veren karar sayisi: S1 bu neden boyle 2/4 S2 hangi alternatif elendi 0/4 <- hicbir kararda yanit yok S3 eleme neye dayandi 0/4 <- hicbir kararda yanit yok S4 hangi varsayimla verildi 2/4 S5 ne zaman verildi 4/4 S6 kim verdi 4/4 S7 bilinen bedeli ne 2/4 S8 hala gecerli mi 4/4 S9 oncekinin yerine mi gecti 0/4 <- hicbir kararda yanit yok S10 bu sayi nereden geldi 0/3 <- hicbir kararda yanit yok
Kayıtlı düzenin 39/39’u tanım gereğidir: alanlar doluysa her soru yanıtlanır. Bu bir bulgu değil, ölçüm çubuğunun sıfır noktasıdır.
Kayıtsız düzen 18 soruyu yanıtlıyor ve bunların 12’si sürüm geçmişiyle koddan, yani kimin ne zaman ne yaptığından geliyor. Gerekçeye, varsayıma ve bedele değinen altı yanıt, tabloda 4 yerine 2 gösteren S1, S4 ve S7 satırlarındadır: o üç soru yalnızca kararı verenin hâlâ ulaşılabilir olduğu iki kararda yanıt buluyor. Belleğin kayıt yerine geçmesi bir kişinin işte kalmasına bağlıdır.
Dört soru ise hiçbir kararda yanıt bulmuyor: hangi alternatif elendi, eleme neye dayandı, bu karar bir öncekinin yerine mi geçti, bu sayı nereden geldi. Bunlar bellekten de gelmiyor; karar anında yazılmamış bir ölçüt altı ay sonra hatırlanmıyor. Yanıtlanamayan 21 sorunun 15’i bu dörttür.
Kaydın Bedeli
Yanıtlanan soru bedelsiz değildir. Aşağıdaki koşum kayıt metnini gerçekten üretir, her alanın kaç satır tuttuğunu sayar ve satır başına dakikayla çarpar. İki yazma hızı ayrılır: üstbilgi alanları aranıp yazılır, gövde alanları düşünülerek (KK3). Okumada her kaynağın soru başına dakikası verilir (KK4).
// kayit/maliyet.mjs — kaydin yazilma maliyeti ve alan basina getirisi import { SORU, KARAR, uygulanabilir, yanitla } from "./model.mjs"; const EN = 76, KISA_DK = 0.4, UZUN_DK = 2.0; // satir basina dakika (KK3) const OKUMA = { kayit: 2, kod: 10, surum: 8, bellek: 25 }; // soru basina okuma dakikasi (KK4) const KISA = ["durum", "tarih", "veren", "yerine"]; // tek satirlik ustbilgi alanlari const UZUN = ["secim", "gerekce", "varsayim", "esik", "alternatif", "olcut", "sonuc"]; const sar = (m) => m.split(" ").reduce((s, k) => // kayit metni gercekten uretilir ((s.at(-1) + " " + k).trim().length > EN ? s.push(k) : (s[s.length - 1] += " " + k), s), [""]); const satir = {}, dk = {}; let tS = 0, tD = 0; const ekle = (a, n, b) => { satir[a] = (satir[a] ?? 0) + n; dk[a] = (dk[a] ?? 0) + n * b; tS += n; tD += n * b; }; for (const k of KARAR) { tS += 1; tD += KISA_DK; // kaydin baslik satiri for (const a of KISA) ekle(a, 1, KISA_DK); for (const a of UZUN) if (k[a] !== null) ekle(a, 2 + sar(k[a]).length, UZUN_DK); // bos satir + baslik + govde } const N = KARAR.reduce((t, k) => t + uygulanabilir(k).length, 0); console.log(`${KARAR.length} kayit: ${tS} satir, ${tD.toFixed(0)} dk; kayit basina ` + `${(tS / KARAR.length).toFixed(1)} satir, ${(tD / KARAR.length).toFixed(0)} dk`); const soruSay = Object.fromEntries(SORU.map((s) => [s.alan, KARAR.filter((k) => k[s.alan] !== null).length])); const kalem = [{ a: "ustbilgi (4 alan)", dk: KISA.reduce((t, a) => t + dk[a], 0), soru: KISA.reduce((t, a) => t + (soruSay[a] ?? 0), 0) }, ...UZUN.filter((a) => soruSay[a]).map((a) => ({ a, dk: dk[a], soru: soruSay[a] }))]; console.log("\nalan, yazma dakikasi, actigi soru (dakika basina getiri sirasiyla):"); let kdk = 0, ks = 0; for (const r of kalem.sort((x, y) => y.soru / y.dk - x.soru / x.dk)) { kdk += r.dk; ks += r.soru; console.log(` ${r.a.padEnd(17)} ${r.dk.toFixed(1).padStart(5)} dk ${r.soru} soru | ` + `birikimli ${String(ks).padStart(2)}/${N}, ${kdk.toFixed(1).padStart(5)} dk`); } console.log(` soru kumesine yanit vermeyen bolum: ${(tD - kdk).toFixed(1)} dk (%${(100 * (tD - kdk) / tD).toFixed(0)})`); const olc = (d, bDk = OKUMA.bellek) => KARAR.reduce((t, k) => t + yanitla(k, d).filter((s) => s.yanitlandi) .reduce((u, s) => u + (s.kaynak === "bellek" ? bDk : OKUMA[s.kaynak]), 0), 0); const say = (d) => KARAR.reduce((t, k) => t + yanitla(k, d).filter((s) => s.yanitlandi).length, 0); console.log("\nsoru kumesi bir tur sorulunca okuma maliyeti:"); for (const d of ["kayitsiz", "kayitli"]) console.log(` ${d.padEnd(9)} ${say(d)} yanit, ${olc(d)} dk, yanit basina ${(olc(d) / say(d)).toFixed(1)} dk`); const bas = (b) => (tD / (olc("kayitsiz", b) - say("kayitsiz") * OKUMA.kayit)).toFixed(2); console.log(`basabas (ortak ${say("kayitsiz")} soru): bellek 25 dk ise ${bas(25)} tur, 5 dk ise ${bas(5)} tur`);
4 kayit: 101 satir, 170 dk; kayit basina 25.3 satir, 43 dk alan, yazma dakikasi, actigi soru (dakika basina getiri sirasiyla): ustbilgi (4 alan) 6.4 dk 16 soru | birikimli 16/39, 6.4 dk gerekce 24.0 dk 4 soru | birikimli 20/39, 30.4 dk varsayim 24.0 dk 4 soru | birikimli 24/39, 54.4 dk esik 18.0 dk 3 soru | birikimli 27/39, 72.4 dk alternatif 24.0 dk 4 soru | birikimli 31/39, 96.4 dk olcut 24.0 dk 4 soru | birikimli 35/39, 120.4 dk sonuc 24.0 dk 4 soru | birikimli 39/39, 144.4 dk soru kumesine yanit vermeyen bolum: 25.6 dk (%15) soru kumesi bir tur sorulunca okuma maliyeti: kayitsiz 18 yanit, 254 dk, yanit basina 14.1 dk kayitli 39 yanit, 78 dk, yanit basina 2.0 dk basabas (ortak 18 soru): bellek 25 dk ise 0.78 tur, 5 dk ise 1.73 tur
Dört kayıt 101 satır ve 170 dakika. Asıl bilgi toplamda değil, getirinin dağılımındadır.
Dört üstbilgi alanı 6,4 dakikada 16 soru açıyor; kalan 23 soru 138 dakika istiyor. Durum, tarih, karar veren ve yerine geçilen karar alanları tek satırlıktır ve düşünmeyi değil bakmayı gerektirir. Kayıt tutmaya başlayan bir ekibin ilk yazacağı şey gerekçe değil, bu dört alandır.
Toplam sürenin yüzde 15’i soru kümesine hiç yanıt vermeyen bölüme gidiyor: kaydın başlığı ve kararın ne olduğunu anlatan bölüm. Kararın ne olduğu zaten koddan okunabiliyor; o bölüm kaydı okunabilir kılar, ölçüye girmez, kısa tutulur.
Okumada kayıtsız düzen yanıt başına 14,1, kayıtlı düzen 2,0 dakika harcıyor. Ortak yanıtlanan 18 soru üzerinden yazma maliyeti, bellekten yanıt 25 dakika sürüyorsa 0,78 turda, 5 dakika sürüyorsa 1,73 turda kapanıyor. Aradaki beş kat fark sonucu değiştirmiyor: soru kümesi iki kez sorulduğunda kayıt her iki uçta da kendini ödemiş oluyor.
Yerine Geçilmiş Karar
Kayıt bir kez yazılıp bitmiyor: karar değişince kayda ne olacağı, eski kararın okunabilir kalıp kalmadığını belirler. Aşağıdaki koşum aynı kararın üç sürümünü (KK5) üç kayıt biçiminde karşılaştırır — güncel kaydın üzerine yazan, üzerine yazıp eski metni sürüm geçmişinden kazıyan ve her sürümü ayrı kayıt olarak tutup yerine geçilmiş karar alanıyla bağlayan biçim. Okuma dakikaları modelin girdisidir (KK6).
// kayit/yerine.mjs — yerine gecilmis karar: eski karar okunabilir kaliyor mu (KK5) const ZINCIR = [ // ayni kararin uc surumu, modelin girdisi { ad: "1", secim: "her subede tam kopya", gerekce: "kesinti dayanimi en agir sayildi", birakma: "kopya gideri olculunce maliyet agirligi 0.25 cikti", yerine: "2" }, { ad: "2", secim: "subede salt-okunur onbellek", gerekce: "olculmus agirlikla en yuksek puan", birakma: "iki buyuk subede hacim sigayi asti", yerine: "3" }, { ad: "3", secim: "iki subede kopya, kalanda onbellek", gerekce: "esigi asan sube ikide kaldi", birakma: null, yerine: null }, ]; const SORU = [["G1", "o zaman ne yapiliyordu", "secim"], ["G2", "neden secilmisti", "gerekce"], ["G3", "neden birakildi", "birakma"], ["G4", "yerine ne gecti", "yerine"]]; const KAZILABILIR = ["secim", "gerekce"]; // eski dosya metninde yazili olan alanlar const BICIM = { "uzerine-yaz": null, "uzerine-yaz-kazi": "kazi", "yerine-gec": "kayit" }; const DK = { kayit: 2, kazi: 12 }; // soru basina okuma dakikasi (KK6) const guncel = ZINCIR.length - 1; const oku = (bicim, i, alan) => { const kaynak = i === guncel ? "kayit" : BICIM[bicim]; return kaynak === "kayit" || (kaynak === "kazi" && KAZILABILIR.includes(alan)) ? kaynak : null; }; const satirSay = (s) => Object.values(s).filter((v) => v !== null).length; for (const bicim of Object.keys(BICIM)) { const tutulan = BICIM[bicim] === "kayit" ? ZINCIR.reduce((t, s) => t + satirSay(s), 0) : satirSay(ZINCIR[guncel]); let y = 0, n = 0, dk = 0; const eksik = []; for (const [i, s] of ZINCIR.entries()) for (const [kod, , alan] of SORU) { if (s[alan] === null) continue; n += 1; const k = oku(bicim, i, alan); if (k) { y += 1; dk += DK[k]; } else eksik.push(`${kod}@${s.ad}`); } console.log(`${bicim.padEnd(17)} tutulan ${String(tutulan).padStart(2)} satir | ` + `${y}/${n} soru, ${dk} dk | yanitlanamayan: ${eksik.join(" ") || "-"}`); }
uzerine-yaz tutulan 3 satir | 2/10 soru, 4 dk | yanitlanamayan: G1@1 G2@1 G3@1 G4@1 G1@2 G2@2 G3@2 G4@2 uzerine-yaz-kazi tutulan 3 satir | 6/10 soru, 52 dk | yanitlanamayan: G3@1 G4@1 G3@2 G4@2 yerine-gec tutulan 13 satir | 10/10 soru, 20 dk | yanitlanamayan: -
Üzerine yazan biçim 3 satır tutup 10 sorunun 2’sini yanıtlıyor; yerine geçilmiş karar alanını kullanan biçim 13 satır tutup 10’unu yanıtlıyor. Fark 10 satır ve 8 sorudur, satır başına neredeyse bir soru.
Kazı satırı tek başına bir bulgudur. Sürüm geçmişi eski metni saklıyor, dolayısıyla “o zaman ne yapılıyordu” ve “neden seçilmişti” kazıyla yanıtlanabiliyor: 2 yerine 6 yanıt. Bedeli 52 dakika, yani yanıt başına 8,7 dakika ve kayıttan okumanın dört katı. Kazı iki soruyu ise hiçbir bedelle açmıyor: “neden bırakıldı” ve “yerine ne geçti”. Bu iki alan hiçbir sürümde yazılmamıştı, çünkü üzerine yazarken kimse bıraktığı kararın gerekçesini yazmaz. Sürüm geçmişi yazılmış olanı saklar, yazılmamış olanı üretmez.
Geçmiş kararların yeniden gündeme gelmesi de buradan çıkar: bırakılma gerekçesi hiçbir yerde durmuyorsa, iki yıl önce elenmiş bir alternatif yeni bir öneri olarak geri gelir. Yerine geçilmiş karar alanının işi eski kararı arşivlemek değil, neden bırakıldığını okunabilir tutmaktır.
Özet
- Bir kaydın değeri, soru kümesinden kaç soruyu yanıtladığıyla ölçülür; aynı dört karar kayıtsız düzende 39 sorunun 18’ini, yapılandırılmış kayıtla 39’unu yanıtladı.
- O 18 yanıtın 12’si sürüm geçmişiyle koddan geliyor; gerekçeye, varsayıma ve bedele değinen altı yanıt yalnızca kararı verenin ulaşılabilir olduğu iki kararda çıktı.
- Dört soru hiçbir kararda yanıt bulmadı: hangi alternatif elendi, eleme neye dayandı, öncekinin yerine mi geçti, bu sayı nereden geldi — yanıtlanamayan 21 sorunun 15’i bu dörttür.
- Dört kayıt 101 satır ve 170 dakika tuttu; dört üstbilgi alanı 6,4 dakikada 16 soru açarken kalan 23 soru 138 dakika istedi, toplamın yüzde 15’i hiç soru yanıtlamayan bölüme gitti.
- Yerine geçilmiş karar alanı 10 satır fazladan tutup 8 fazladan soru yanıtladı; sürüm geçmişini kazımak yazılmış alanları yanıt başına 8,7 dakikaya çıkarıyor, hiç yazılmamış bırakma gerekçesini ise açmıyor.
Sonraki Adım
Kayıt, kararın verildikten sonraki halidir; bu dersin ölçtüğü her şey karar çoktan verilmişken başladı. Dört kaydın “karar veren” alanında bir ekip adı yazıyor, ama o ekibin kararı nasıl verdiği — kimin önerdiği, kimin itiraz ettiği, itirazın kararı değiştirip değiştirmediği — kayda girmiyor. Sözlü onayla geçen karar ile yazılı bir öneri üzerinden değerlendirilen karar aynı kaydı üretir. Sonraki ders bu farkı ölçer: aynı karar iki süreçte koşturulur ve değerlendirme turunda kaç kararın yön değiştirdiği sayılır.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.