Ders 09 / 10
Değişiklik Yönetimi
Kapsam değişiminin denetimi: aynı isteğin çözümlemede 0, doğrulamada 8 tur yeniden iş yazması ve kalemi 12 turdan 39 tura taşıması, aynı altı isteği geldiğinde alan biçimin ikisini alıp 8 tur yeniden iş üretmesine karşılık sınırda toplayan biçimin dördünü alıp isteği ortalama 30 tur bekletmesi, denetim kaldırıldığında planın ikinci istekte sekiz kalemin birinde tutması ve ekip sınırının dışına düşen değişikliğin aynı kalemi 23 yerine 28 turda teslim ettirmesi.
İçindekiler
Önceki ders iş kaleminin süresini tahmin etti ve tahminin hata payını ölçtü. Ama planı asıl bozan şey tahminin sapması değildir: tahmin, sabiti bilinen bir işin süresine dair bir sayıdır; planın altındaki kapsam değişince o sayı yanlış çıkmaz, konusuz kalır.
Değişiklik denetimi (change control), kapsam değişikliğinin akışa hangi noktadan ve hangi bedelle gireceğini belirleyen karar yoludur. Aşağıdaki dört ölçüm onu sayıya çevirir.
Değişikliğin Girdiği An
Örnek, kursun kurgu bölgesel kütüphane ağı ile onu yazan üç ekiptir; ağ da ekipler de kurgudur. ET22: modül sahipliği üçe bölünür — E1 şube ön yüzü, terminal ve üyelik kapısını; E2 ödünç ile ücretlendirmeyi; E3 katalog kapısı, raporlama ve bildirimi tutar. ET23: iş kalemi dört aşamadan geçer (çözümleme, geliştirme, gözden geçirme, doğrulama); kalem ikinci bir ekibin modülüne dokunuyorsa o ekip yarım geliştirme ile gözden geçirmeyi alır. Her ekip aynı anda tek adım işler, biten adımdan sonra kalem kuyruğun sonuna döner. ET24: ilk kapsam sekiz kalem ve 71 tur iştir; plan kalemleri 0., 6. ve 12. turda bırakır, kapsam kaydı bu planın teslim turlarıdır. ET25: değişiklik kalemi çözümlemeye döndürür; o ana kadar yapılmış işin tamamı yeniden iştir, geri dönüşün kendisi bir el değiştirmedir. ET26: yineleme on tur, yineleme başına değişiklik payı sekiz turdur; paydan düşen tutar ek iş artı tetiklediği yeniden iştir, sığmayan istek reddedilir. Akış ile iki denetim biçimi süreç içi bir modeldir; ağ ya da saat yoktur, turlar sayıdır.
// degisiklik/akis.mjs — kurgu kutuphane aginin is kalemi akisi ve degisiklik denetimi // (model): ne ag ne depo var, kuyruklar veri yapisi, rastgelelik yok. export const EKIP = { "sube-onyuz": "E1", "sube-terminal": "E1", "uyelik-kapisi": "E1", odunc: "E2", ucret: "E2", rapor: "E3", bildirim: "E3", "katalog-kapisi": "E3" }; export const EKIPLER = [...new Set(Object.values(EKIP))]; export const YINELEME = 10, PAY = 8, COZ = 1; const cizelge = (s, f) => s.trim().split("\n").map((r) => f(r.trim().split(/\s+/))); // Ilk kapsam: ad, modul, asama turlari, plan girisi. export const KAPSAM = cizelge(` gecikme-ucreti-esigi ucret 2,4,2,2 0 sube-rafi-gorunumu sube-onyuz 2,3,1,2 0 katalog-alan-esleme katalog-kapisi 1,3,1,1 0 odunc-suresi-uzatma odunc 2,4,2,2 6 terminal-kart-okuma sube-terminal 1,3,1,2 6 aylik-odunc-raporu rapor,odunc 2,3,2,1 6 borc-bildirimi bildirim,ucret 2,4,2,2 12 uyelik-dogrulama uyelik-kapisi 2,3,1,2 12`, ([ad, m, i, g]) => ({ ad, modul: m.split(","), is: i.split(",").map(Number), giris: +g })); // Degisiklik: gelis turu, ad, hedef, modul, ek is. export const DEGISIKLIK = cizelge(` 3 ucret-muafiyeti gecikme-ucreti-esigi ucret 3 6 rafta-filtre sube-rafi-gorunumu sube-onyuz 2 9 uzatma-siniri odunc-suresi-uzatma odunc 3 12 raporda-ucret-sutunu aylik-odunc-raporu ucret 3 15 terminalde-uzatma terminal-kart-okuma odunc 2 18 ikinci-alan-eslemesi katalog-alan-esleme katalog-kapisi 3`, ([t, ad, h, m, e]) => ({ tur: +t, ad, hedef: h, modul: m.split(","), ek: +e })); // Sahip ekip cozumler ve gelistirir; ikinci ekip yarim gelistirme ile gozdeni alir. export function adimlar(modul, is) { const e = [...new Set(modul.map((m) => EKIP[m]))], sahip = e[0]; const a = [{ ad: "cozumleme", ekip: sahip, is: is[0] }, { ad: "gelistirme", ekip: sahip, is: is[1] }]; for (const x of e.slice(1)) a.push({ ad: "gelistirme", ekip: x, is: Math.ceil(is[1] / 2) }); return [...a, { ad: "gozden", ekip: e.at(-1), is: is[2] }, { ad: "dogrulama", ekip: sahip, is: is[3] }]; } const kur = (ad, modul, is, giris, kaynak) => ({ ad, modul: [...modul], taban: [...is], adim: adimlar(modul, is), i: 0, yapilan: 0, giris, kaynak, bitis: null, bekleme: 0, elDegistirme: 0, yenidenIs: 0 }); export const isi = (k) => k.adim.reduce((s, a) => s + a.is, 0); // "alan" istegi geldigi turda, "toplayan" yineleme sinirinda karara baglar; paya sigmayan red. export function kos(bicim, degisiklikler, ayar = {}) { const pay = ayar.pay ?? PAY; const havuz = KAPSAM.map((k) => kur(k.ad, k.modul, k.is, k.giris, "kapsam")); const kalem = [], harcanan = {}, kabul = [], red = [], tampon = []; const kuyruk = Object.fromEntries(EKIPLER.map((e) => [e, []])); const mevcut = Object.fromEntries(EKIPLER.map((e) => [e, null])); const koy = (k) => { kalem.push(k); kuyruk[k.adim[0].ekip].push(k); }; function karar(d, tur, yin) { const k = kalem.find((x) => x.ad === d.hedef && x.bitis === null); const yerinde = bicim === "alan" && k; // yerinde alinmiyorsa ayri kalem olur // Yerinde alma yapilmis isi yeniden ise cevirir; ayri kalem baglami kurar (COZ). const yapilmis = yerinde ? k.adim.slice(0, k.i).reduce((s, a) => s + a.is, 0) + k.yapilan : COZ; const asama = yerinde ? k.adim[k.i].ad : k ? "sinirda" : "teslim-sonrasi"; const m = d.ek + yapilmis; // paydan dusen tutar if ((harcanan[yin] ?? 0) + m > pay) return red.push({ d, asama, m }); harcanan[yin] = (harcanan[yin] ?? 0) + m; if (!yerinde) { const y = kur(d.ad, d.modul, [COZ, d.ek, 1, 1], d.tur, "degisiklik"); y.yenidenIs = COZ; koy(y); return kabul.push({ d, kalem: y, asama, m }); } k.yenidenIs += yapilmis; k.elDegistirme += 1; // geri donus de el degistirmedir k.modul = [...new Set([...k.modul, ...d.modul])]; k.taban[1] += d.ek; k.adim = adimlar(k.modul, k.taban); k.i = 0; k.yapilan = 0; for (const e of EKIPLER) { if (mevcut[e] === k) mevcut[e] = null; kuyruk[e] = kuyruk[e].filter((x) => x !== k); } kuyruk[k.adim[0].ekip].push(k); kabul.push({ d, kalem: k, asama, m }); } for (let tur = 0; tur < 80; tur++) { havuz.filter((k) => k.giris === tur).forEach(koy); for (const d of degisiklikler.filter((x) => x.tur === tur)) if (bicim === "alan") karar(d, tur, Math.floor(tur / YINELEME)); else tampon.push(d); if (bicim === "toplayan" && tur > 0 && tur % YINELEME === 0) for (const d of tampon.splice(0)) karar(d, tur, tur / YINELEME); for (const e of EKIPLER) if (!mevcut[e] && kuyruk[e].length) mevcut[e] = kuyruk[e].shift(); for (const e of EKIPLER) for (const k of kuyruk[e]) k.bekleme++; for (const e of EKIPLER) { const k = mevcut[e]; if (!k || ++k.yapilan < k.adim[k.i].is) continue; k.yapilan = 0; k.i++; mevcut[e] = null; if (k.i >= k.adim.length) k.bitis = tur + 1; else { k.elDegistirme++; kuyruk[k.adim[k.i].ekip].push(k); } } } if (kalem.some((k) => k.bitis === null)) throw new Error("kosum 80 turda bitmedi"); return { kalem, kabul, red, son: Math.max(...kalem.map((k) => k.bitis)) }; }
// degisiklik/olc.mjs — giris aninin bedeli, iki denetim bicimi, kapsam kaymasi, ekip siniri. import { kos, isi, KAPSAM, DEGISIKLIK, PAY, YINELEME } from "./akis.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 top = (r, a) => r.kalem.reduce((s, k) => s + k[a], 0); const ort = (a) => (a.reduce((s, x) => s + x, 0) / a.length).toFixed(1); const kapsamda = (r) => r.kalem.filter((k) => k.kaynak === "kapsam"); const T = kos("alan", []), KAYIT = Object.fromEntries(T.kalem.map((k) => [k.ad, k.bitis])); const TABAN_IS = T.kalem.reduce((s, k) => s + isi(k), 0); const satir = (r, ad) => { // bir kalemin akis olculeri const k = r.kalem.find((x) => x.ad === ad); return [k.yenidenIs, k.elDegistirme, k.bekleme, k.bitis - k.giris, r.son]; }; console.log(`kayitli plan: ${KAPSAM.length} kalem, ${TABAN_IS} tur is, son teslim ${T.son}. tur, ` + `ortalama teslim suresi ${ort(kapsamda(T).map((k) => k.bitis - k.giris))} tur; ` + `yineleme ${YINELEME} tur, yineleme payi ${PAY} tur`); // 1. Ayni degisiklik, farkli giris ani; pay sinirsiz. const HEDEF = "gecikme-ucreti-esigi"; const G = [-7, -16, 12, 15, 9, 15, 11]; console.log("\n1. ayni degisiklik (ucret-muafiyeti, 3 tur ek is), farkli giris ani"); yaz(G, "gelis", "girdigi asama", "yeniden is", "el degistirme", "bekleme", "teslim suresi", "akis sonu"); yaz(G, "yok", "-", ...satir(T, HEDEF)); for (const tur of [0, 8, 14]) { const r = kos("alan", [{ tur, ad: "ucret-muafiyeti", hedef: HEDEF, modul: ["ucret"], ek: 3 }], { pay: Infinity }); yaz(G, tur, r.kabul[0].asama, ...satir(r, HEDEF)); } // 2. Iki denetim bicimi, ayni alti istek, ayni pay. const R = { alan: kos("alan", DEGISIKLIK), toplayan: kos("toplayan", DEGISIKLIK) }; const sonuc = (r, d) => { // istegin bu bicimdeki sonucu const a = r.kabul.find((x) => x.d.ad === d.ad), x = r.red.find((y) => y.d.ad === d.ad); return a ? `alindi/${a.asama}/${a.m} -> ${a.kalem.bitis}` : `reddedildi/${x.asama}/${x.m}`; }; const B = [-22, 6, -33, -1]; console.log("\n2. degisiklik denetimi: geldiginde alan bicim ile sinirda toplayan bicim"); yaz(B, "degisiklik", "gelis", " alan bicim", " toplayan bicim"); for (const d of DEGISIKLIK) yaz(B, d.ad, d.tur, " " + sonuc(R.alan, d), " " + sonuc(R.toplayan, d)); const O = [-34, 12, 12]; console.log(); yaz(O, "olcu", "alan", "toplayan"); const ozet = (r) => ({ "ilk kapsam ort. teslim": ort(kapsamda(r).map((k) => k.bitis - k.giris)), "kayitli turunda biten": `${kapsamda(r).filter((k) => k.bitis <= KAYIT[k.ad]).length}/8`, "yeniden is": top(r, "yenidenIs"), bekleme: top(r, "bekleme"), "el degistirme": top(r, "elDegistirme"), "alinan / reddedilen": `${r.kabul.length}/${r.red.length}`, "degisikligin ort. teslimi": ort(r.kabul.map((x) => x.kalem.bitis - x.d.tur)), "akisin sonu": r.son, }); const oA = ozet(R.alan), oT = ozet(R.toplayan); for (const k of Object.keys(oA)) yaz(O, k, oA[k], oT[k]); // 3. Denetim reddetmezse kayit ne kadar dayaniyor. const K = [-12, 12, 17, 14, 11]; console.log("\n3. kapsam kaymasi (pay sinirsiz: her istek aliniyor)"); yaz(K, "alinan", "eklenen is", "eklenen/kayitli", "plan tuttu", "akis sonu"); for (const n of [0, 1, 2, 6]) { const r = kos("alan", DEGISIKLIK.slice(0, n), { pay: Infinity }); const kap = kapsamda(r), ek = r.kalem.reduce((s, k) => s + isi(k), 0) - TABAN_IS; let tuttu = 0; // plan sirasinda ilk sapmaya kadar for (const k of [...kap].sort((a, b) => KAYIT[a.ad] - KAYIT[b.ad])) { if (k.bitis > KAYIT[k.ad]) break; tuttu++; } yaz(K, n, ek, `%${((100 * ek) / TABAN_IS).toFixed(1)}`, `${tuttu}/8`, r.son); } // 4. Degisiklik ekip sinirinin icine mi disina mi. const M = [-24, 6, 12, 15, 9, 15, 11]; console.log("\n4. ayni degisiklik (terminal-kart-okuma, 12. tur, 2 tur ek is) nereye dusuyor"); yaz(M, "dusen yer", "adim", "yeniden is", "el degistirme", "bekleme", "teslim suresi", "akis sonu"); const AD = "terminal-kart-okuma"; yaz(M, "degisiklik yok", T.kalem.find((k) => k.ad === AD).adim.length, ...satir(T, AD)); for (const [yer, modul] of [["ekip sinirinin icine", ["sube-terminal"]], ["ekip sinirinin disina", ["odunc"]]]) { const r = kos("alan", [{ tur: 12, ad: "terminal-degisikligi", hedef: AD, modul, ek: 2 }], { pay: Infinity }); yaz(M, yer, r.kalem.find((k) => k.ad === AD).adim.length, ...satir(r, AD)); }
kayitli plan: 8 kalem, 71 tur is, son teslim 30. tur, ortalama teslim suresi 12.9 tur; yineleme 10 tur, yineleme payi 8 tur 1. ayni degisiklik (ucret-muafiyeti, 3 tur ek is), farkli giris ani gelis girdigi asama yeniden is el degistirme bekleme teslim suresi akis sonu yok - 0 3 2 12 30 0 cozumleme 0 4 8 21 33 8 dogrulama 8 7 18 39 39 14 teslim-sonrasi 0 3 2 12 34 2. degisiklik denetimi: geldiginde alan bicim ile sinirda toplayan bicim degisiklik gelis alan bicim toplayan bicim ucret-muafiyeti 3 alindi/gelistirme/6 -> 22 alindi/sinirda/4 -> 41 rafta-filtre 6 reddedildi/dogrulama/8 alindi/teslim-sonrasi/3 -> 26 uzatma-siniri 9 reddedildi/cozumleme/3 reddedildi/sinirda/4 raporda-ucret-sutunu 12 alindi/gelistirme/8 -> 36 alindi/sinirda/4 -> 44 terminalde-uzatma 15 reddedildi/dogrulama/7 alindi/teslim-sonrasi/3 -> 45 ikinci-alan-eslemesi 18 reddedildi/teslim-sonrasi/4 reddedildi/teslim-sonrasi/4 olcu alan toplayan ilk kapsam ort. teslim 16.3 16.3 kayitli turunda biten 4/8 3/8 yeniden is 8 4 bekleme 44 133 el degistirme 31 38 alinan / reddedilen 2/4 4/2 degisikligin ort. teslimi 21.5 30.0 akisin sonu 36 45 3. kapsam kaymasi (pay sinirsiz: her istek aliniyor) alinan eklenen is eklenen/kayitli plan tuttu akis sonu 0 0 %0.0 8/8 30 1 3 %4.2 2/8 34 2 5 %7.0 1/8 34 6 23 %32.4 1/8 44 4. ayni degisiklik (terminal-kart-okuma, 12. tur, 2 tur ek is) nereye dusuyor dusen yer adim yeniden is el degistirme bekleme teslim suresi akis sonu degisiklik yok 4 0 3 4 11 30 ekip sinirinin icine 4 4 6 10 23 30 ekip sinirinin disina 5 4 7 12 28 34
Sayılar ölçüm sınıfındadır; girdileri yukarıdaki varsayımlardır. Birinci tablo aynı isteği üç ayrı anda aynı akışa sokar. Çözümlemedeyken giren istek sıfır yeniden iş yazar; doğrulamadayken giren aynı istek sekiz tur yeniden iş, dört fazladan el değiştirme ve on altı tur fazladan bekleme getirir: teslim süresi 12 turdan 39 tura çıkar, üç turluk ek iş yirmi yedi tura mal olur. Erken giren de bedava değildir — büyüyen kalem sonradan bırakılanların arkasına düştüğü için 21 turda biter. Kalem teslim edilmişse istek ayrı bir kalem olur: hedef plandaki 12 turda kalır, akışın sonu 30’dan 34’e gider. Bedel kalemden çıkar, akışa geçer.
Toplayan Biçim ile Alan Biçim
İkinci ölçüm aynı altı isteği aynı payla iki biçimde koşturur: alan biçim isteği kalemin içine sokar, toplayan biçim kalemi plana göre bitirip isteği sınırda ayrı kalem olarak alır. Alan biçim ikisini alır, dördünü reddeder. Paydan düşen tutar yeniden işi de içerdiğinden doğrulamadaki bir kaleme gelen istek sekiz turluk payın tamamını yer; payı dolduran pahalı istek arkadan gelen ucuzu da reddettirir: dokuzuncu turdaki üç turluk istek, hedefi çözümlemedeyken sığmaz. Toplayan biçim dördünü alır ve yeniden işi yarıya indirir (8 yerine 4); bedeli geciktirdiği değerdir — gelişten teslime ortalama 30 tur geçer, alan biçimde 21,5. Aldığı dört isteğin ikisi sınıra geldiğinde hedef kalem çoktan bitmiştir. İlk kapsamın ortalama teslim süresi iki biçimde de 16,3 tur çıkar; ortalama ayrımı gizler: kayıtlı turunda biten kalem 4/8’e karşı 3/8’dir, alan biçimde gecikme değişen kalemlerde toplanır, toplayanda tümüne yayılır.
Kapsam Kayması ve Kaydın Ömrü
ET27: üçüncü ölçümde denetim kaldırılır, her istek alınır. Altı istek 71 turluk işe 23 tur ekler (yüzde 32,4’lük kapsam kayması), ama kayıt bu oranla orantılı bozulmaz. Tek istekte plan sırasında ilk sapmaya kadar tutan kalem sekizden ikiye, ikinci istekte bire iner ve altıncıya kadar orada kalır. Eklenen iş büyümeyi sürdürür, kaydın anlatma gücü ikinci değişiklikte dibi bulur. Kapsam kaydı o noktadan sonra bir plan değil bir tarihtir: hangi kalemin ne zaman biteceğini değil, bir zamanlar ne sanıldığını anlatır.
Değişiklik Hangi Sınıra Düşüyor
Dördüncü ölçüm aynı kaleme aynı turda aynı büyüklükte iki istek sokar; tek fark dokundukları modüldür. Sınırın içinde kalan istek kalemin dört adımını korur; dışarı düşen istek onu iki ekibin dokunduğu bir kaleme çevirir: adım beşe, el değiştirme altıdan yediye, bekleme 10’dan 12’ye çıkar, teslim süresi 23 yerine 28 tur olur, akışın sonu 30’dan 34’e gider. Yeniden iş ikisinde de dört turdur; fark bütünüyle sınırı geçmektendir. Ekip sınırıyla örtüşmeyen istek pahalıdır ve farkı büyüten şey isteğin kendisi değil modül sahipliğinin bölünüşüdür.
Özet
- Aynı istek çözümlemede sıfır, doğrulamada sekiz tur yeniden iş yazar; teslim süresi 12 turdan 39 tura çıkar. Teslim edilmiş kaleme gelen istek ayrı bir kalem olur.
- Aynı altı istek ve aynı payla alan biçim ikisini, toplayan biçim dördünü alır; yeniden iş 8’e karşı 4, bekleme 44’e karşı 133, akışın sonu 36’ya karşı 45’tir. Toplayan biçimin bedeli geciktirdiği değerdir: gelişten teslime ortalama 30 tur, alan biçimde 21,5; ilk kapsamın ortalaması ikisinde de 16,3 tur (kayıtlı turunda biten 4/8’e karşı 3/8).
- Denetim kaldırılınca altı istek kapsamı yüzde 32,4 büyütür ama plan daha erken kopar: birinci istekte 8/8’den 2/8’e, ikincide 1/8’e iner.
- Ekip sınırının dışına düşen istek bir adım, bir el değiştirme ve iki tur bekleme ekler; teslim süresi 23 yerine 28 turdur.
Sonraki Adım
Değişiklik denetlendi, kapsam kaydı tutuldu, bedeli yeniden iş ve bekleme cinsinden sayıldı. Ama bu ölçümlerin hepsi sayacı kalemin teslim edildiği anda durduruyor. Teslim modelde son adımın bitmesidir, alandaysa yazılımın asıl ömrünün başlaması: gece çalan bir çağrının kime gittiği ve devredilemeyen işin neye mal olduğu bu tabloların hiçbirinde yok. Sonraki ders sayacı teslimin ötesine taşır: geliştiren ile işleten arasındaki sorumluluk hangi kalemlerden oluşuyor ve devredilemeyen kalem kime kalıyor?
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.