Yazılımda Tahmin Neden Yanılır? Estimate, Target ve Commitment Farkı, Cone of Uncertainty ve Planning Fallacy
Tahmin yanılır çünkü çoğu zaman tek sayı olarak, işin en belirsiz anında ve iyimser bir senaryodan verilir; dürüst cevap bir aralık, bir güven seviyesi ve varsayımlardır. Estimate, target ve commitment farkını, cone of uncertainty'yi ve planning fallacy'yi Bookstore ekibinin 12 feature'lık gerçek tahmin geçmişini işleyen C# koduyla görüyoruz.
Sürüm notu: Bu yazı Steve McConnell'in Software Estimation: Demystifying the Black Art (2006) kitabındaki kavramları esas alır; hesap örneği .NET 10 ile derlenip çalıştırılmıştır.
Önce Tahmin Et
16 Kasım pazartesi sabahı. Kurumsal satıştan Hakan, sitedeki serilerin ortak örnek uygulaması Bookstore'un (bir online kitapçı) ekip odasına uğruyor. Nevin Hanım'ın okulu bahar dönemi kitaplarını bu yıl portal üzerinden toplu sipariş olarak vermek istiyor. Requirements Analysis serisinde çıkardığımız Kurumsal Sipariş özelliği için Hakan'ın tek bir sorusu var: "1 Mart'a yetişir mi?"
Emre ekranından başını kaldırmadan cevap veriyor: "İki ay yeter herhalde." Hakan teşekkür edip çıkıyor. Öğleden sonra Nevin Hanım'a giden e-postada şu cümle duruyor: "Kurumsal portalımız ocak sonunda kullanıma hazır olacak."
Devam etmeden önce üç soruyu cevapla. Emre'nin "iki ay"ı bir tahmin miydi, bir hedef miydi, yoksa bir söz mü? Hakan e-postayı gönderdiğinde o cümle neye dönüştü ve sözü kim vermiş oldu? Son olarak: Emre'nin son on iki feature'daki tahminlerinin gerçekte ne kadar tuttuğunu bilseydin, "iki ay" cevabını nasıl düzeltirdin?
Üç Ayrı Kelime: Estimate, Target ve Commitment
McConnell, Software Estimation kitabının ilk bölümünde bu üç kavramı birbirinden ayırır, çünkü tahminle ilgili kavgaların çoğu bunların karışmasından çıkar. Kendi cümlelerimizle:
| Kavram | Ne söyler? | Kimden gelir? | Bookstore'daki karşılığı |
|---|---|---|---|
| Estimate (tahmin) | İşin ne kadar süreceğine dair, belirsizliği içinde taşıyan bir öngörü; doğası gereği aralıktır | İşi yapacak ekip | "Bugünkü bilgimizle 8 ile 14 hafta arası" |
| Target (hedef) | İş dışından gelen, istenen bir sonuç: tarih, bütçe, kapsam | İş birimi, müşteri, pazar | "Okulun bahar siparişi için 1 Mart" |
| Commitment (taahhüt) | Belirli kapsamı belirli tarihte teslim etme sözü; riski kabul eder | Sözü tutacak kişi ya da ekip | "1 Mart'ta liste yapıştırıp sipariş verebilecekler" |
Üçü birbirinden bağımsızdır. Hedef, tahminden önce de vardı; 1 Mart'ı okulun takvimi belirledi, ekibin hızı değil. Taahhüt ise tahminle aynı sayı olmak zorunda değildir. Tahmin "%50 ihtimalle 10 hafta" diyorsa, ekip daha temkinli bir tarih için söz verebilir ya da tarihi sabit tutup kapsamı daraltarak söz verebilir. Önemli olan, sözü verenin tahmini görmüş olması ve riski bilerek alması.
Koridordaki konuşmada üçü birbirine karıştı. Hakan bir hedefin tutup tutmayacağını sordu. Emre hiçbir parçalama yapmadan bir sayı söyledi; bu en iyi ihtimalle kaba bir tahmindi. E-posta ise o sayıyı, onu söyleyen kişinin haberi olmadan bir taahhüde çevirdi. Üstelik "iki ay" e-postada "ocak sonu" olurken yılbaşı haftası ve sprint başlangıcı gibi ayrıntılar da kayboldu.
McConnell'in bu noktadaki önerisi sorunun kendisini düzeltmek: biri tahmin istiyorsa, çoğu zaman asıl sorduğu "bu hedefe yetişebilir miyiz?" sorusudur. Bu soruya cevap bir sayı değil, bir plandır: "1 Mart'a hangi kapsamla yetişebiliriz ve bundan ne kadar eminiz?" Hakan'ın asıl ihtiyacı da buydu.
Cone of Uncertainty
İkinci önemli kavram, tahminin projenin hangi anında yapıldığına göre ne kadar yanılabileceği. Fikrin kökü yazılımın dışında, mühendislik ve inşaat maliyet tahminlerinde. Barry Boehm 1981'de Software Engineering Economics kitabında bunu yazılıma uyarladı; "cone of uncertainty" adını McConnell 1997'de Software Project Survival Guide kitabında koydu.

Construx'un yayımladığı McConnell makalesindeki değerlerle: ilk fikir aşamasında (initial concept) yapılan bir tahmin gerçekleşenin 0,25 katı ile 4 katı arasında olabilir; yani en düşük ve en yüksek uç arasında 16 kat fark var. Ürün tanımı onaylandığında (approved product definition) aralık 0,5x–2x'e, requirement'lar tamamlandığında 0,67x–1,5x'e, kullanıcı arayüzü tasarımı bittiğinde 0,8x–1,25x'e iner.
Bu grafiği okurken üç ayrıntı çoğu zaman atlanır. Birincisi, koni en iyi durumu gösterir. McConnell'e göre bu aralıklar yetenekli tahmincilerin elde ettiği doğruluktur; daha kötüsü kolayca mümkündür, daha iyisi ise ancak şansla olur. İkincisi, koni zamanla kendiliğinden daralmaz. Daralmasını sağlayan şey belirsizliği ortadan kaldıran kararlardır: ürünün neyi yapıp neyi yapmayacağına karar vermek, requirement'ları netleştirmek, ekranları tasarlamak. Bu kararlar alınmazsa McConnell'in deyişiyle koni yerine proje sonuna kadar dağılmayan bir bulut oluşur. Üçüncüsü, daralmanın büyük kısmı projenin takvim süresinin ilk %20–30'unda olur; McConnell anlamlı taahhütlerin de bu noktadan sonra verilebileceğini söyler.
Iterative çalışan ekipler için tablo biraz farklı. Her iterasyonun başında requirement'lar netleştiriliyorsa, her iterasyon kendi küçük konisinden geçer: iki haftalık bir sprint'te birkaç gün içinde aralık daralır. Bunun bedeli uzun vadeli öngörü: requirement'ları sprint sprint netleştiren bir ekip, üç ay sonrasının kapsamı ve tarihi hakkında daha az şey söyleyebilir.
Kurumsal Sipariş bugün nerede? Requirements Analysis serisinde story map çıkarıldı ve ilk release'in iskeleti seçildi. Ama teslimat noktalarının ayrıntısı, faturanın muhasebe tarafına nasıl gideceği ve kurumsal indirim kurallarının tamamı henüz konuşulmadı. Bu, onaylanmış ürün tanımına yakın bir yer. Emre'nin 8 haftası bu noktada 4 ile 16 hafta arasında bir şey demek.
Kod: Koni ve Ekibin Kendi Geçmişi
Koninin oranları sektör genelinden gelir. Ekibin kendi geçmişi ise daha somut bir şey söyler: biz tahmin ettiğimizde genelde ne kadar yanılıyoruz? Bookstore ekibi son on iki feature için ilk tahmini ve gerçekleşen iş gününü bir tabloda tutmuş; gecikmenin büyük kısmını hangi işin unutulduğunu da yanına not etmiş. Bu seri boyunca bütün hesapları tek bir console uygulamasında yapacağız:
dotnet new console -n Bookstore.Planning -o src/Bookstore.Planning
Percentile için bütün seride kullanacağımız küçük bir yardımcı:
namespace Bookstore.Planning;
public static class Stats
{
// Nearest-rank percentile; liste sıralı olmalı
public static T Percentile<T>(IReadOnlyList<T> sorted, double p)
{
int rank = (int)Math.Ceiling(p / 100.0 * sorted.Count);
return sorted[Math.Clamp(rank, 1, sorted.Count) - 1];
}
}
Koninin aralıkları ve ekibin geçmişi:
namespace Bookstore.Planning;
public sealed record ConePhase(string Milestone, double Low, double High);
public sealed record PastFeature(string Name, int EstimatedDays, int ActualDays, string Forgotten)
{
public double Ratio => (double)ActualDays / EstimatedDays;
}
public static class Cone
{
// McConnell (2006): yetenekli tahmincilerin en iyi durum aralıkları
static readonly ConePhase[] Phases =
[
new("Initial concept", 0.25, 4.0),
new("Approved product definition", 0.5, 2.0),
new("Requirements complete", 0.67, 1.5),
new("UI design complete", 0.8, 1.25),
];
// Bookstore: son 12 feature, tahmin edilen ve gerçekleşen iş günü
static readonly PastFeature[] History =
[
new("GiftCards", 15, 24, "payment provider sandbox"),
new("Coupons", 10, 13, "code review rounds"),
new("Returns", 20, 31, "data migration"),
new("StockReservation", 12, 15, "load test"),
new("ShipmentTracking", 8, 7, "-"),
new("CampaignPricing", 10, 16, "data migration"),
new("InvoicePdf", 5, 9, "deployment"),
new("SearchRelevance", 15, 18, "code review rounds"),
new("CartReminder", 6, 6, "-"),
new("AuthorPages", 4, 5, "deployment"),
new("ThreeDSecure", 12, 25, "payment provider sandbox"),
new("AdminReports", 10, 14, "load test"),
];
public static void Run()
{
const double gutWeeks = 8; // Emre'nin ilk cevabı: "iki ay yeter"
Console.WriteLine($"Gut estimate: {gutWeeks} weeks");
foreach (ConePhase phase in Phases)
Console.WriteLine($" {phase.Milestone,-28} {gutWeeks * phase.Low,5:F1} - {gutWeeks * phase.High,5:F1} weeks (spread {phase.High / phase.Low:F1}x)");
List<double> ratios = [.. History.Select(f => f.Ratio).Order()];
int overran = History.Count(f => f.ActualDays > f.EstimatedDays);
Console.WriteLine();
Console.WriteLine($"Last {History.Length} features: {overran} overran, total {History.Sum(f => f.EstimatedDays)} estimated vs {History.Sum(f => f.ActualDays)} actual days");
Console.WriteLine($"actual / estimate p10 = {Stats.Percentile(ratios, 10):F2} p50 = {Stats.Percentile(ratios, 50):F2} p90 = {Stats.Percentile(ratios, 90):F2}");
// Kendi geçmişimizle düzeltilmiş aralık: p10..p90 = %80
double low = gutWeeks * Stats.Percentile(ratios, 10);
double high = gutWeeks * Stats.Percentile(ratios, 90);
Console.WriteLine($"80% range for an {gutWeeks}-week gut estimate: {low:F1} - {high:F1} weeks");
Console.WriteLine();
Console.WriteLine("Forgotten work behind the overruns:");
foreach (IGrouping<string, PastFeature> g in History.Where(f => f.Forgotten != "-")
.GroupBy(f => f.Forgotten).OrderByDescending(g => g.Sum(f => f.ActualDays - f.EstimatedDays)))
Console.WriteLine($" {g.Key,-26} {g.Count()} features, +{g.Sum(f => f.ActualDays - f.EstimatedDays)} days");
}
}
Program.cs yalnızca komutu ilgili sınıfa yönlendiriyor; sonraki yazılarda buraya birer satır ekleyeceğiz:
using System.Globalization;
using Bookstore.Planning;
CultureInfo.CurrentCulture = CultureInfo.InvariantCulture;
switch (args.FirstOrDefault())
{
case "cone": Cone.Run(); break;
default: Console.WriteLine("Usage: dotnet run --project src/Bookstore.Planning -- cone"); break;
}
Çalıştırıyoruz:
dotnet run --project src/Bookstore.Planning -- cone
Gut estimate: 8 weeks
Initial concept 2.0 - 32.0 weeks (spread 16.0x)
Approved product definition 4.0 - 16.0 weeks (spread 4.0x)
Requirements complete 5.4 - 12.0 weeks (spread 2.2x)
UI design complete 6.4 - 10.0 weeks (spread 1.6x)
Last 12 features: 10 overran, total 127 estimated vs 183 actual days
actual / estimate p10 = 1.00 p50 = 1.30 p90 = 1.80
80% range for an 8-week gut estimate: 8.0 - 14.4 weeks
Forgotten work behind the overruns:
payment provider sandbox 2 features, +22 days
data migration 2 features, +17 days
load test 2 features, +7 days
code review rounds 2 features, +6 days
deployment 2 features, +5 days
Çıktıyı Okumak
İlk blok koninin kendisi. Aynı 8 haftalık cevap, projenin ilk fikir aşamasında 2 ile 32 hafta, ürün tanımı onaylandığında 4 ile 16 hafta, requirement'lar tamamlandığında 5,4 ile 12 hafta arası anlamına geliyor. Sayının kendisi değişmiyor; değişen, o sayının ne kadar güvenilir olduğu.
İkinci blok daha rahatsız edici. Son on iki feature'ın onunda gerçekleşen süre tahmini geçmiş; toplamda 127 gün tahmin edilmiş, 183 gün sürmüş. Bu %44 fazla demek. Ortanca oran 1,30: tipik bir feature tahmininden %30 uzun sürüyor. En iyi %10'luk dilim bile tahminin altında değil, tam üstünde bitiyor (p10 = 1,00). Yani bu ekibin geçmişinde "tahminden erken bitti" neredeyse hiç yok.
Bu oranları Emre'nin cevabına uygularsak %80'lik aralık 8,0 ile 14,4 hafta arası çıkıyor. Bugünden başlayarak 11 Ocak ile 24 Şubat arası. Nevin Hanım'a yazılan "ocak sonu" bu aralığın en iyimser yarısında kalıyor, 1 Mart ise aralığın hemen dışında. Bir uyarı da gerekli: geçmişteki tahminler en azından story'lere bölünmüş işler için verilmişti, Emre'ninki ise koridorda verildi. Gerçek belirsizlik büyük ihtimalle bu aralıktan da geniş.
Üçüncü blok gecikmenin nereden geldiğini gösteriyor. En büyük kalem ödeme sağlayıcısının test ortamı: iki feature'da toplam 22 gün. Onu veri migration'ı, load test, code review turları ve deploy işleri izliyor. Bunların hiçbiri sürpriz değil; hepsi tahmin anında bilinebilirdi ama listeye girmedi. Kurumsal Sipariş için bu listenin karşılığı belli: muhasebe sistemine giden fatura raporu, mevcut kurum müşterilerinin verisinin taşınması ve 500 satırlık bir sipariş listesinin performansı.
Neden Hep İyimseriz?
Bu sapma Bookstore ekibine özgü değil. Daniel Kahneman ve Amos Tversky 1979'da planning fallacy adını verdikleri eğilimi tarif ettiler: insanlar kendi işlerinin süresini tahmin ederken, benzer işlerin geçmişte daha uzun sürdüğünü bilseler bile iyimser bir senaryo üzerinden düşünür. Roger Buehler, Dale Griffin ve Michael Ross'un 1994 tarihli çalışmasında 37 psikoloji öğrencisinden bitirme tezlerini ne zaman tamamlayacaklarını tahmin etmeleri istendi. Ortalama tahmin 33,9 gündü, gerçekleşen ortalama 55,5 gün oldu ve öğrencilerin yalnızca %30 kadarı kendi tahmin ettiği sürede bitirdi. Daha çarpıcısı, öğrencilerin "her şey ters giderse" diye verdikleri en kötü durum tahmininin ortalaması bile 48,6 gündü; gerçekleşen bundan da uzundu.
Bunun birkaç nedeni var. İlki, tahmin yaparken işin içinden bakmamız: adımları tek tek düşünür, her birinin sorunsuz geçtiği senaryoyu canlandırırız. Kesintiler, hastalık, başka ekibin gecikmesi bu senaryoda yer almaz. İkincisi anchoring: konuşmada ilk geçen sayı sonraki tahminleri kendine doğru çeker. Hakan'ın sorusunda "1 Mart" zaten vardı ve Emre'nin cevabı o tarihin rahatça içinde kaldı. Magne Jørgensen ve arkadaşlarının laboratuvar deneyleri, müşterinin beklediği süre gibi işle ilgisi olmayan bilgilerin bile tahminleri belirgin biçimde kaydırabildiğini gösterdi. Jørgensen ve Stein Grimstad'ın 46 yazılım şirketiyle yaptığı 2011 tarihli saha deneyinde bu etki yine görüldü ama çok daha zayıftı. Yani anchoring gerçek ama tek açıklama değil.
Üçüncü neden unutulan işler. Tahmin genellikle "kodu yazmak" için yapılır. Test verisi hazırlamak, code review'da gelen düzeltmeler, migration, deploy, monitoring, dokümantasyon ve başka ekiplerle yapılan toplantılar tahminde ayrı bir satır olarak yer almaz, ama takvimde yer kaplar. Yukarıdaki çıktıda gecikmenin tamamı bu tür işlerden geliyor.
Tahmin Neye Yarar?
Tahmin yapmanın da bir maliyeti var: ekibin zamanı. Martin Fowler 2013 tarihli PurposeOfEstimation yazısında tahmini ne iyi ne kötü sayar; ona göre tahmin, önemli bir karar vermene yardım ettiği ölçüde değerlidir. Fowler'ın saydığı kararlar kaynak ayırmak (iki seçenekten hangisini yapalım), ekipler arası koordinasyon (depo ekibi teslimat noktası alanını ne zaman eklemeli) ve değişiklik karşısında planı yeniden değerlendirmek. Tahmin oturumlarının tasarım tartışmasını tetiklemesini de bir yan fayda olarak sayar.
Bookstore'da bu tahminden çıkacak kararlar açık. Hakan, Nevin Hanım'a 1 Mart için ne söyleyeceğine karar verecek. Selin, Product Owner olarak, 1 Mart'a hangi kapsamın gireceğine ve Kurumsal Sipariş'in öteki işlerin önüne geçip geçmeyeceğine karar verecek. Bu kararların hiçbiri tek bir sayıya ihtiyaç duymuyor; hepsi bir aralığa ve o aralığı neyin daralttığına ihtiyaç duyuyor.
Fowler'ın diğer tarafı da önemli: hiçbir karar tahmine bağlı değilse, ayrıntılı tahmin israftır. Ekip işleri zaten öncelik sırasıyla yapıyorsa ve kimse bir tarihe göre plan yapmıyorsa, her story'yi saat saat tahmin etmenin kimseye faydası yok. Bu fikre ikinci yazıda #NoEstimates başlığında döneceğiz.
Tek Sayı Değil: Aralık ve Güven Seviyesi
Dürüst bir tahmin üç parçadan oluşur: bir aralık, o aralığın güven seviyesi ve aralığı belirleyen varsayımlar. Emre'nin Hakan'a vermesi gereken cevap kabaca şu olurdu: "Bugünkü bilgimizle %80 ihtimalle 8 ile 14 hafta, yani ocak ortası ile şubat sonu arası. Bu aralık bizim geçmiş tahmin hatalarımızdan geliyor ve muhasebe raporunun nasıl olacağını henüz bilmiyoruz. Story'leri iki hafta içinde boyutlandırıp aralığı daraltacağız."
%80 güvenin anlamı şu: bu tarzda verilen beş tahminden biri aralığın dışında kalacak. Bu bir zayıflık değil; aralığı daha geniş vererek %95 güvene çıkabilirsin ama o zaman aralık işe yaramayacak kadar genişler. Hangi güven seviyesinin seçileceği kararın türüne bağlı: okula verilecek bir söz için %85–90 istenir; iç planlama için %50 bile yeterli olabilir.
Belirsizliği gizlemenin en yaygın yolu ped eklemek, yani tahmine kimseye söylemeden bir pay koymak. "İki ay diyeyim, aslında altı hafta, iki hafta da yedek." Ped görünmediği için yönetilemez: kimse onun neyin karşılığı olduğunu bilmez ve takvim sıkıştığında ilk silinen o olur. Görünür bir aralık ise aynı bilgiyi açıkça taşır. Yedek payını görünür bir plan kalemine çevirmeyi dördüncü yazıda release planı üzerinde göreceğiz.
Taahhüt için doğru an ise koninin daraldığı an. Ekip, story'leri iki haftada boyutlandırıp muhasebe raporunun formatını netleştirdiğinde, Hakan'a "1 Mart'ta şu kapsam kesin, şu kapsam büyük ihtimalle" diyebilecek. O zamana kadar Nevin Hanım'a verilebilecek dürüst söz, "4 Aralık'ta size kesin tarih ve kapsam bildireceğiz" cümlesidir.
Üretimde Dikkat
Tahminleri kaydet. Bu yazıdaki hesabın tamamı tek bir tabloya dayanıyor: ilk tahmin, gerçekleşen süre ve gecikmenin nedeni. Bu tablo yoksa ekibin kendi hatası hakkında söyleyebileceği tek şey "genelde biraz gecikiyoruz" olur. Tabloyu bir spreadsheet'te ya da issue tracker'da özel alanlarla tutabilirsin; Jira'daki karşılığını Jira ve Proje Araçları serisinde göreceğiz. Önemli olan, tahminin hangi gün, kim tarafından ve hangi bilgiyle verildiğinin de yanında durması. Requirement'lar tamamlanmadan verilen bir tahminle tamamlandıktan sonra verilen bir tahmin aynı satırda karşılaştırılmamalı.
Tahmini yazılı paylaşırken aralık ve varsayım cümleden ayrılmasın. "Ocak sonu" yerine "ocak ortası ile şubat sonu arası, muhasebe formatı netleşince daralacak" yazmak e-postayı uzatır ama yanlış bir sözü önler. Bir de tahminin ne zaman güncelleneceğini baştan söyle: koninin daralacağı her kilometre taşı, yeniden tahmin için doğal bir an.
Sık Düşülen Tuzaklar
- Koridor tahmini sözleşmeye dönüşür: parçalamadan söylenen bir sayı, söyleyenin haberi olmadan e-postaya, sunuma ya da sözleşmeye girer.
- Hedefi tahmin gibi kullanmak: "1 Mart'a yetişmeli" cümlesi, ekibin tahminini o tarihe uyduracak şekilde küçültmesine yol açar.
- Gizli ped eklemek: belirsizlik sayının içine saklanır, kimse yönetemez ve sıkışınca ilk o silinir.
- Unutulan işleri tahmin dışı bırakmak: test, review, migration ve deploy her feature'da var; tahminde de satırı olmalı.
- Tahmini bir performans hedefine çevirmek: tahmini tutturamayan geliştiricinin sorgulandığı yerde tahminler ya şişer ya da hiç verilmez.
Küçük Bir Deneme
★☆☆ Son beş işinin ilk tahminini ve gerçekleşen süresini Cone.cs'teki History dizisine yaz ve programı çalıştır. Kaç tanesinde tahmini aştın, ortanca oranın kaç?
★★☆ Hakan'ın Nevin Hanım'a yazdığı e-postayı yeniden yaz: tahmini, hedefi ve taahhüdü ayrı cümlelerde ver; aralığı, güven seviyesini ve kesin tarihin ne zaman bildirileceğini ekle.
★★★ Cone sınıfına, aynı işin requirement'lar tamamlandığında yeniden tahmin edildiği durumu ekle: koninin aralığını ve geçmiş oranlardan çıkan aralığı ayrı ayrı yazdır. İki aralığın kesişimini almak sana neden yanlış bir güven verebilir?
Emre'nin koridordaki cevabı tek kişinin, parçalanmamış bir iş için verdiği bir sayıydı. Bir sonraki yazıda ekip Kurumsal Sipariş'in story'lerini birlikte boyutlandırıyor: story point, planning poker, t-shirt sizing ve story point'in neden bu kadar tartışmalı olduğu.