Tahmini İletmek ve Plan Kaydığında: Güncelleme, Kötü Haberi Erken Vermek ve Yeniden Planlamak
Plan kaydığında yapılacak ilk şey, kötü haberi seçenekleriyle birlikte hemen vermek; karar kapsam, tarih ya da kişi ekleme arasında iş tarafınındır ve geç kalmış projeye kişi eklemek çoğu zaman tarihi kurtarmaz. Bookstore'un üç sprint sonraki güncellemesi, kapsamın 142 point'e büyüdüğünü ve tam kapsamın 26 Mart'a kaydığını gösteriyor; C# koduyla seçenekleri hesaplayıp tek sayfalık tahmin notunu yazıyoruz.
Sürüm notu: Bu yazıdaki plan güncelleme kodu .NET 10 ile derlenip çalıştırılmıştır; kişi ekleme tartışması Fred Brooks'un The Mythical Man-Month (1975) kitabındaki gözleme dayanır.
Önce Tahmin Et
4 Ocak 2027 pazartesi. Sitedeki serilerin ortak örnek uygulaması Bookstore'un (bir online kitapçı) ekibi Kurumsal Sipariş Release 1'in üç sprint'ini bitirdi. Plan sprint başına ortalama 20 point'ti; Sprint 24'te 19, Sprint 25'te 17, yılbaşına denk gelen Sprint 26'da ise 11 point bitti. Aynı üç sprint'te kapsam 130 point'ten 142'ye çıktı: okul faturası alanları ve onay metni, beşinci yazıda gördüğümüz gibi, belirsiz risklerden somut story'lere dönüştü. Cuma günü Hakan, Nevin Hanım ile okulun bahar siparişini konuşacak.
Üç soru. Selin bu durumu Hakan'a ne zaman söylemeli: bugün mü, bir sprint daha bekleyip toparlanıp toparlanmadığına bakarak mı? Hakan'a hangi seçenekleri götürmeli? Ve Emre'nin önerisi, "Diğer ekipten bir kişiyi iki aylığına alalım", tarihi kurtarır mı?
Tahmini Stakeholder'a Anlatmak
Tahmini iletmenin zor tarafı sayı değil, sayının etrafındaki bilgi. İlk yazıda gördüğümüz gibi, aralık ve varsayım cümleden ayrılınca tahmin bir taahhüde dönüşür. Bookstore ekibi bu yüzden her release için tek sayfalık bir tahmin notu tutuyor ve her güncellemede aynı notu yeniliyor. Notun bölümleri şunlar:
| Bölüm | İçerik | 4 Ocak notundan |
|---|---|---|
| Karar | Bu tahmine hangi karar bağlı? | Hakan cuma günü okula 1 Mart için ne söyleyecek? |
| Aralık ve güven | Tek tarih değil, aralık ve güven seviyesi | Tam kapsam: 12 Mart ile 9 Nisan arası, en olası 26 Mart |
| Kapsam katmanları | Sabit tarihe neyin kesin, neyin olası sığdığı | Must kalemleri ortalama senaryoda 1 Mart'a sığıyor, kötü senaryoda 5 point eksik kalıyor |
| Varsayımlar | Tahminin dayandığı koşullar | Velocity son 10 sprint'e benzer kalacak; kapsam büyümesi duracak |
| Güveni düşüren riskler | Register'daki en büyük kalemler | ERP (muhasebe yazılımı) raporu artık düşük; onay akışının geri gelmesi |
| Değişiklik | Bir önceki nottan bu yana ne değişti, neden? | Velocity 3 sprint'tir ortalamanın altında; kapsam 12 point arttı |
| Sonraki güncelleme | Tahmin ne zaman yeniden verilecek? | 15 Ocak, Sprint 27 sonu |
"Değişiklik" satırı notun en önemli kısmı. Bir stakeholder'ın tahmine güvenmesini sağlayan şey tahminin hiç değişmemesi değil, değiştiğinde nedeninin açıkça görünmesidir. "Tarih kaydı" cümlesi tek başına güven kaybettirir; "tarih kaydı, çünkü kapsam %9 büyüdü ve yılbaşı sprint'i düşük geçti" cümlesi ise kararın tartışılabileceği bir zemin verir.
Takip: Her Sprint Sonunda Güncelleme
Notun sayıları, dördüncü yazıdaki planın gerçekleşenle güncellenmiş hali. Yeni sınıf, Release 1'in sprint sonuçlarını tutuyor ve dördüncü yazıdaki VelocityRange ile SprintEnd metotlarını yeniden kullanıyor:
namespace Bookstore.Planning;
public sealed record SprintResult(int Number, int DonePoints, int ScopeAtEnd);
public static class ReleaseUpdate
{
// Kurumsal Sipariş Release 1: başladığından beri her sprint sonu
static readonly SprintResult[] Actuals =
[
new(24, 19, 133),
new(25, 17, 138),
new(26, 11, 142),
];
const int PlannedScope = 130;
const int MustAtStart = 96;
public static void Run()
{
// Plan (ortalama 20) ile gerçekleşenin burn-up karşılaştırması
Console.WriteLine("Sprint planned done scope");
int done = 0;
for (int i = 0; i < Actuals.Length; i++)
{
done += Actuals[i].DonePoints;
Console.WriteLine($" {Actuals[i].Number,4} {20 * (i + 1),7} {done,4} {Actuals[i].ScopeAtEnd,5}");
}
int scope = Actuals[^1].ScopeAtEnd;
int remaining = scope - done;
double growth = (double)(scope - PlannedScope) / Actuals.Length;
Console.WriteLine($"Remaining {remaining} points, scope growth {growth:F1} points/sprint");
// Velocity aralığını yeni sprint'lerle güncelle: yine son 10 sprint (burada yalnız Velocity kullanılıyor)
Sprint[] last10 = [.. SprintHistory.Last10.Concat(Actuals.Select(a => new Sprint(a.Number, [a.DonePoints]))).TakeLast(10)];
(double low, double mean, double high) = ReleasePlan.VelocityRange(last10);
Console.WriteLine($"Velocity range now (S{last10[0].Number}-S{last10[^1].Number}): worst-3 {low:F1}, mean {mean:F1}, best-3 {high:F1}");
int used = Actuals.Length;
Console.WriteLine();
Console.WriteLine("Option A, keep full scope:");
foreach ((string name, double v) in new[] { ("worst-3", low), ("mean", mean), ("best-3", high) })
{
int flat = (int)Math.Ceiling(remaining / v);
int growing = (int)Math.Ceiling(remaining / (v - growth));
Console.WriteLine($" {name,-8} {ReleasePlan.SprintEnd(used + flat):yyyy-MM-dd} (if scope keeps growing: {ReleasePlan.SprintEnd(used + growing):yyyy-MM-dd})");
}
// B: 1 Mart'a kadar yalnız Must; yeni çıkan regülasyon işleri de Must
int sprintsLeft = Enumerable.Range(1, 20).Last(n => ReleasePlan.SprintEnd(n) < ReleasePlan.Target) - used;
int mustRemaining = MustAtStart + (scope - PlannedScope) - done;
Console.WriteLine();
Console.WriteLine($"Option B, Must only by {ReleasePlan.Target:yyyy-MM-dd}: {mustRemaining} points in {sprintsLeft} sprints");
foreach ((string name, double v) in new[] { ("worst-3", low), ("mean", mean), ("best-3", high) })
Console.WriteLine($" {name,-8} capacity ~{sprintsLeft * v:F0} -> {(sprintsLeft * v >= mustRemaining ? "fits" : "does not fit")}");
}
}
Program.cs'e case "update": ReleaseUpdate.Run(); break; satırını ekleyip çalıştırıyoruz:
dotnet run --project src/Bookstore.Planning -- update
Sprint planned done scope
24 20 19 133
25 40 36 138
26 60 47 142
Remaining 95 points, scope growth 4.0 points/sprint
Velocity range now (S17-S26): worst-3 14.0, mean 18.5, best-3 22.3
Option A, keep full scope:
worst-3 2027-04-09 (if scope keeps growing: 2027-05-21)
mean 2027-03-26 (if scope keeps growing: 2027-04-09)
best-3 2027-03-12 (if scope keeps growing: 2027-03-26)
Option B, Must only by 2027-03-01: 61 points in 4 sprints
worst-3 capacity ~56 -> does not fit
mean capacity ~74 -> fits
best-3 capacity ~89 -> fits
Çıktıyı Okumak
İlk tablo burn-up'ın sayısal hali. Planlanan 60 point'e karşılık 47 point bitti; aradaki 13 point'in yarısından fazlası yılbaşı sprint'inden. Kapsam ise sprint başına ortalama 4 point büyüdü. Dördüncü yazının grafiğinde bu, gerçek noktaların yelpazenin alt kenarına yapışması ve pembe kapsam çizgisinin yukarı kalkması demek. İki çizgiyi ayrı tutmanın faydası burada görülüyor: gecikmenin yarısı ekibin hızından, yarısı kapsamın büyümesinden geliyor ve iki nedenin çaresi farklı.
Velocity aralığı da güncellendi. Son 10 sprint artık Sprint 17 ile 26 arası ve Sprint 26'nın 11'i en kötü üçe girdi: aralık 14,0, 18,5 ve 22,3. Yılbaşı sprint'inin aralığın alt ucunu bu kadar aşağı çekmesi tartışılabilir; izin kaynaklı tek seferlik bir düşüşü aralığa katmamak da savunulabilir bir seçim. Ama bunu bilinçli yapmak ve notta yazmak gerekiyor.
A seçeneği, kapsamı korumak: kalan 95 point, ortalama senaryoda 26 Mart'ta, kötü senaryoda 9 Nisan'da bitiyor. Kapsam aynı hızla büyümeye devam ederse tarihler iki hafta daha kayıyor; kötü senaryoda mayıs sonuna gidiyor. 1 Mart artık tam kapsam için hiçbir senaryoda tutmuyor.
B seçeneği, 1 Mart'a yalnızca Must kapsamıyla çıkmak. Must kalemleri başta 96 point'ti; yeni çıkan fatura alanları ve onay metni yasal zorunluluk olduğu için onlar da Must. Kalan Must işi 61 point ve 1 Mart'tan önce 4 sprint var. Ortalama velocity ile yaklaşık 74 point sığıyor, kötü senaryoda 56. Yani Must kapsamı ortalama senaryoda sığıyor, kötü senaryoda kabaca bir story eksik kalıyor. Bu, dördüncü yazıdaki MoSCoW uyarısının sonucu: Must payı yüksek tutulduğunda esneklik ilk kayışta tükeniyor.
Planın Kaydığını Erken Fark Etmek
Bookstore'da kayma üçüncü sprint'te net görüldü, ama sinyaller daha önce vardı. Erken fark etmek için bakılacak yerler belli. Velocity'nin üst üste iki-üç sprint aralığın ortasının altında kalması tek başına alarm değildir, ama gerçek noktaların burn-up yelpazesinin alt kenarına doğru kayması bir eğilimdir. Kapsam çizgisinin her sprint yükselmesi, requirement'ların hâlâ netleşmediğini, yani ilk yazıdaki koninin beklenen hızda daralmadığını gösterir. Risk register'dan çıkan risklerin issue olarak plana girmesi kapasiteyi doğrudan azaltır. Sprint içinde de sinyal var: uzun süredir "In Progress"te duran item'lar ve "neredeyse bitti" denen ama Done'a girmeyen story'ler. Item'ların yaşını ve WIP'i (aynı anda üzerinde çalışılan iş sayısı) izlemek Kanban serisinin "Flow Metric'leri: WIP, Throughput, Work Item Age ve Cycle Time" yazısının konusu.
Bu sinyallerin hiçbiri tek başına "plan kaydı" demek değil. Ama her biri tahmin notunun "Değişiklik" satırına yazılmayı hak ediyor. Notu her sprint sonunda güncelleyen bir ekip için kayma bir sürpriz değil, notta birkaç haftadır görünen bir eğilimin sonucu olur.
Kayınca Seçenekler ve Kararın Sahibi
Plan kaydığında seçenekler sınırlı: kapsamı azaltmak, tarihi kaydırmak, kişi eklemek ya da kaliteden ve sürdürülebilir hızdan feragat etmek. Sonuncusu bir seçenek değil, bir borçtur; aşağıda döneceğiz.
Kişi eklemek ilk bakışta en makul seçenek gibi görünür. Fred Brooks 1975'te The Mythical Man-Month kitabında, IBM'in (ABD'li bilgisayar şirketi) OS/360 işletim sistemi projesindeki deneyimine dayanarak bunun tersini gözlemledi: gecikmiş bir yazılım projesine kişi eklemek onu daha da geciktirir. Bunun iki nedeni var. İlki öğrenme süresi: yeni gelen kişi kodu, domain'i ve ekibin alışkanlıklarını öğrenene kadar üretken olmaz ve onu en çok bilen kişiler, yani Emre ve Ayşe, ona zaman ayırmak zorunda kalır. İlk haftalarda ekibin net kapasitesi artmaz, düşer. İkincisi iletişim: n kişilik bir ekipte iletişim kanalı sayısı n(n − 1) / 2'dir. İki geliştirici ve bir Product Owner'dan oluşan üç kişilik ekipte 3 kanal var; dördüncü kişiyle 6'ya çıkıyor. Brooks bir sınırı da kendisi belirtir: birbirinden bağımsız bölünebilen işlerde kişi eklemek işe yarar. Bookstore'da sekiz hafta kala, fiyatlandırma ve doğrulama kodunun içine gelen bir kişi büyük ihtimalle 1 Mart'a katkı yapamaz; ama birbirinden bağımsız bir iş, mesela sipariş listesini dışa aktarma, ona verilebilir.
Kapsam ve tarih kararlarının sahibi ise ekip değildir. Scrum'da Product Backlog'un içeriği ve sırası Product Owner'ın sorumluluğundadır; Selin, Must kalemlerinden hangisinin ilk haftaya kalabileceğine karar verir. Okula verilecek söz ise iş tarafının kararıdır; Hakan ve Selin birlikte verir. Ekibin işi, seçenekleri ve her birinin olasılığını açıkça ortaya koymak. Ekip "1 Mart'a yetiştiririz" diyerek bu kararı kendi başına verirse, kararın bedelini de fazla mesaiyle kendisi öder.
Bookstore'da karar şöyle çıkıyor. Selin, B seçeneğini seçip Must listesinden 5 point'lik "kampüslere göre teslimat noktası" story'sinin geçici bir çözümünü konuşuyor: ilk iki hafta teslimat noktasını Gül siparişe not olarak ekleyecek. Böylece kalan Must işi 56 point'e iniyor ve kötü senaryonun kapasitesine ucu ucuna sığıyor. Should kalemleri 26 Mart'ta ikinci bir sürümle gelecek. Hakan cuma günü Nevin Hanım'a bunu, tarihleriyle ve geçici çözümüyle anlatacak.
Kötü Haberi Erken Vermek
Selin'in ilk sorusunun cevabı: bugün. Bir sprint daha beklemenin cazibesi anlaşılır; belki Sprint 27 iyi geçer ve kimse kaymayı fark etmez. Ama bekleyerek kazanılan şey yalnızca iki hafta sessizlik, kaybedilen şey ise seçenekler. 4 Ocak'ta Hakan'ın okulla konuşmak için sekiz haftası, Selin'in kapsamı yeniden düzenlemek için dört sprint'i var. Aynı haber 25 Şubat'taki Sprint Review'da verilseydi, kalan tek seçenek tarihi kaydırmak ve okula son dakikada bunu söylemek olurdu.
Kötü haberi verirken üç şey işe yarıyor. İlki haberi seçeneklerle birlikte getirmek: "Plan kaydı" yerine "Plan kaydı; iki seçeneğimiz var ve şunu öneriyoruz." İkincisi nedeni sayıyla söylemek: kapsam ne kadar büyüdü, velocity ne kadar düştü. Üçüncüsü bir sonraki güncellemenin tarihini vermek; stakeholder'ın aradaki sessizliği endişeyle doldurmasını önler. Kötü haberin erken verilmesi, ekibin kendisini korumasından çok, kararı verecek kişiye karar vermek için zaman tanımasıdır.
Tahmin Kültürü: Cezalandırmak Değil, Öğrenmek
Bu serinin bütün hesapları ekibin kendi geçmişine dayanıyor: ilk yazıdaki feature tablosu, üçüncü yazıdaki epic oranları, dördüncü yazıdaki velocity. Bu veriler ancak tahminler dürüst verilirse işe yarar. Tahmini tutturamayan geliştiricinin sorgulandığı bir yerde tahminler ya şişer ya da hiç verilmez; ikisinde de geçmiş veri anlamını kaybeder.
Tahmin ile gerçekleşeni karşılaştırmanın doğru yeri retrospektif. Release 1 bittiğinde Bookstore ekibi şu sorulara bakacak: velocity aralığı gerçekleşeni ne sıklıkla kapsadı, kapsam neden %9 büyüdü, pre-mortem listesinde olmayan hangi sorun çıktı? Amaç kimin yanıldığını bulmak değil, bir sonraki tahminin girdilerini düzeltmek: belki referans sınıfının çarpanı büyümeli, belki story bölmede regülasyon işleri daha erken konuşulmalı. Retrospektif formatları Scrum serisinin "Sprint Review ve Sprint Retrospective: Ürünü ve Süreci Inspect Etmek" yazısında.
Üretimde Dikkat
Güncellemeyi bir takvime bağla. Bookstore'un tahmin notu her Sprint Review'da güncelleniyor ve aynı gün Hakan'a gidiyor. Güncellemenin kendisi iyi haberle kötü haberi ayırmıyor; not her iki hafta aynı biçimde geliyor. Böylece kötü haberin olduğu güncelleme bir alarm değil, sıradaki bir not oluyor.
Geçici çözümleri plana yaz. Gül'ün teslimat noktasını elle eklemesi bir kapsam kararı; ne kadar süreceği ve kaldırılacağı tarih de backlog'da bir story olarak durmalı. Aksi halde geçici çözüm kalıcı olur ve Requirements Analysis serisinde çözmeye çalıştığımız elle giriş sorunu başka bir yerde yeniden doğar.
Yeni tahmini eskisinin üzerine yazma. Tahmin notlarının tarihçesi, retrospektifte "ne zaman ne biliyorduk?" sorusunun tek cevabı. Bir sayfada tarih sırasıyla tutulan notlar yeterli.
Sık Düşülen Tuzaklar
- Fazla mesaiyle tahmini tutturmak: kısa bir süre işe yarayabilir, ama uzadıkça hata ve yorgunluk artar; Agile Manifesto'nun prensiplerinden biri de ekibin süresiz koruyabileceği bir hızda çalışmasıdır. Ayrıca bir sonraki tahmin, gerçekte hiç olmayan bir velocity'ye dayanır.
- Kötü haberi demo gününe saklamak: haber geciktikçe karar verecek kişinin seçenekleri azalır.
- Kişi eklemeyi varsayılan çözüm saymak: öğrenme süresi ve iletişim yükü, geç kalmış bir projede kapasiteyi önce düşürür.
- Kararı ekibin tek başına vermesi: kapsam ve tarih iş tarafının kararıdır; ekip seçenekleri ve olasılıklarını sunar.
- Geçici çözümü unutmak: elle yapılan iş kalıcı hale gelir ve maliyeti görünmez olur.
- Tahmin hatasını kişiye bağlamak: cezalandırılan tahminler şişer ve geçmiş veri işe yaramaz hale gelir.
Küçük Bir Deneme
★☆☆ Üzerinde çalıştığın bir iş için yukarıdaki tablodaki yedi bölümle tek sayfalık bir tahmin notu yaz. Hangi bölümü doldurmakta zorlandın? O bölüm büyük ihtimalle tahminin en zayıf yeri.
★★☆ ReleaseUpdate'te Sprint 26'yı yılbaşı nedeniyle velocity aralığının dışında bırakan bir seçenek ekle ve tarihleri yeniden hesapla. Notun "Varsayımlar" satırında bu kararı nasıl açıklardın?
★★★ ReleaseUpdate'e, kapsam büyümesini doğrusal sabit bir sayı yerine geçmiş üç sprint'teki büyümelerden örneklem alan bir Monte Carlo ekle. B seçeneğinde Must kapsamının 1 Mart'a sığma olasılığı kaç çıkıyor?
Bu yazıyla Estimation ve Planning serisi bitiyor: tahminin neden yanıldığından, nasıl tahmin edileceğine, tahminin plana, riske ve sonunda bir konuşmaya nasıl dönüştüğüne kadar geldik. Burada kullandığımız burn-up, velocity ve risk listesini bir araçta tutmak, raporlarını almak ve ekiple paylaşmak ise Jira ve Proje Araçları serisinin konusu.