Story Point ve Relative Estimation: Planning Poker, T-shirt Sizing ve Story Point'in Tartışmalı Mirası
Story point işe yarar çünkü işleri birbiriyle karşılaştırmak süre tahmin etmekten kolaydır; zarar verdiği yer, point'in saate çevrildiği, ekipler arasında karşılaştırıldığı ya da performans hedefi yapıldığı yerdir. Planning poker, t-shirt sizing ve #NoEstimates'i Bookstore ekibinin 10 sprint'lik verisiyle görüyoruz: point ile story sayısı 0,81 korelasyonlu, ama yeni epic'in story'leri büyük olduğu için sayma yöntemi iyimser kalıyor.
Sürüm notu: Bu yazı Scrum Guide 2020'yi ve Ron Jeffries'in Story Points Revisited (2019) yazısını esas alır; hesap örneği .NET 10 ile derlenip çalıştırılmıştır.
Önce Tahmin Et
19 Kasım perşembe, refinement toplantısı. Product Owner Selin, sitedeki serilerin ortak örnek uygulaması Bookstore'un (bir online kitapçı) Kurumsal Sipariş story'lerini tek tek okuyor. Sıra şu story'de: "Satın alma sorumlusu olarak kitap listemi yapıştırıp her satırın doğrulama sonucunu görmek istiyorum." Emre ile Ayşe kartlarını aynı anda çeviriyor. Emre'nin kartında 3, Ayşe'ninkinde 8 yazıyor.
Bir an sessizlik oluyor. Selin "Ortası 5, 5 yazalım mı?" diye soruyor. Sence ortalamayı almak doğru mu? İki sayı arasındaki farkın nedeni ne olabilir: biri işi yanlış mı anlamış, yoksa ikisi farklı işleri mi düşünüyor? Bir de şunu düşün: bu 8, takvimde ne kadar süreye karşılık geliyor? Dört gün mü, iki hafta mı, yoksa sorunun kendisi mi yanlış?
Neden Göreli Tahmin?
İnsanlar mutlak büyüklükleri tahmin etmekte kötü, karşılaştırmakta ise oldukça iyidir. Bir binanın yüksekliğini metre olarak söylemek zordur; ama yanındaki binanın ondan iki kat yüksek olduğunu hemen görürsün. Yazılımda da "bu story kaç saat sürer?" sorusu kesintileri, review'ı, kimin yapacağını ve o haftanın toplantılarını bir sayıya sıkıştırmanı ister. "Bu story, geçen ay yaptığımız kupon story'sinden büyük mü küçük mü?" sorusu ise sadece işin kendisine bakar.
Relative estimation bu yüzden işin büyüklüğünü süreden ayırır. Ekip story'leri birbirine göre sıralar, süreyi ise sonradan geçmiş veriden çıkarır: ekip bir sprint'te ortalama kaç birim iş bitiriyor? İlk yazıda gördüğümüz iyimserliğin bir kısmı da böylece devre dışı kalır, çünkü kimse "ben bunu üç günde yaparım" demez; geçmiş hız zaten kesintileri ve unutulan işleri içinde taşır.
Story Point Nereden Geldi?
Story point'in hikâyesini en iyi anlatan, onu ilk kullananlardan Ron Jeffries'in 2019'da yazdığı Story Points Revisited yazısı. Jeffries'in anlattığına göre XP (Extreme Programming; Kent Beck'in 1990'ların sonunda tarif ettiği, test ve sürekli entegrasyon gibi mühendislik pratiklerine dayanan yöntem) ekipleri önce story'leri zamanla, sonra "ideal gün" ile tahmin ediyordu. İdeal gün, bir çiftin kimse rahatsız etmeseydi işi kaç günde bitireceğiydi. Gerçek takvime geçmek için bir yük çarpanı kullanılıyordu ve bu çarpan genelde 3 civarındaydı: bir ideal günlük iş üç gerçek gün sürüyordu.
Sorun, işin dışındakilerin bu çarpanı anlamamasıydı. "Bir günlük iş neden üç gün sürüyor?" sorusu sürekli geri geliyordu. Ekip de ideal güne "point" demeye başladı; birim ortadan kalkınca soru da kalktı. Jeffries bu point'lerin asıl amacının bir iterasyona ne kadar iş alınacağına karar vermek olduğunu yazar. Yazının en çok alıntılanan cümlesi de ondan: "I like to say that I may have invented story points, and if I did, I'm sorry now." (Ron Jeffries, Story Points Revisited, 2019). Jeffries bugün mümkün olan yerde story point'i, hatta story tahminini tamamen bırakmayı, bunun yerine en değerli işi bulup story'leri küçük dilimlere bölmeyi öneriyor.
Point'in "icat" tarihi kesin değil; Jeffries de "may have" diyerek bunu açık bırakıyor. Kesin olan şu: point, süreden kaçmak için değil, süre tahminlerinin işin dışındakiler tarafından yanlış okunmasından kaçmak için doğdu. Bu, bugünkü bozulmaların neredeyse hepsinin, point'i yeniden süreye çevirmeye çalışmaktan çıkmasını da açıklıyor.
Planning Poker
Planning poker'ı ilk kez James Grenning 2002'de kısa bir makalede anlattı. Çözmeye çalıştığı sorun tanıdık: release planning toplantısında iki kişi bir story'yi yirmi dakika tartışıyor, odanın geri kalanı kopuyor ve sonunda tahmin hiç değişmiyor. Grenning'in çözümünde müşteri story'yi okur, gerekirse kısa bir açıklama yapılır, her programcı tahminini kimseye göstermeden bir karta yazar ve kartlar aynı anda açılır. Herkes aynı sayıyı yazdıysa tartışmaya gerek yoktur, sonraki story'ye geçilir. Sayılar farklıysa tartışmayı en uçtaki tahminleri verenler açar ve ekip uzlaşmaya çalışır. Uzlaşma çıkmazsa Grenning'in önerisi rahat: story'yi ertele, böl ya da düşük tahmini al; sonuçta yüzlerce story'den biri.
Grenning'in desteinde 1, 2, 3, 5, 7 ve 10 ideal gün ile bir de sonsuz kartı vardı. İki haftadan büyük story için sonsuz kartı oynanıyor ve müşteriden story'yi bölmesi isteniyordu. Teknik, Mike Cohn'un 2005 tarihli Agile Estimating and Planning kitabıyla yaygınlaştı; bugün yaygın destelerde 0, 1, 2, 3, 5, 8, 13, 20, 40 ve 100 gibi değerler ve "anlamadım" demek için bir soru işareti kartı bulunur.
Ölçeğin aralıklı olmasının nedeni basit: iş büyüdükçe tahminin hassasiyeti düşer. 3 ile 4 arasındaki farkı hissedebilirsin, 13 ile 14 arasındakini hissedemezsin. Grenning de kendi desteindeki boşluklar için aynı şeyi söyler. Ölçek, ekibi gereksiz bir hassasiyet tartışmasından kurtarır.
Ama planning poker'ın asıl değeri sayı değil, kartlar açıldığı andaki farktır. Bookstore'daki 3 ile 8'e dönelim. Ayşe nedenini anlatıyor: okulların listelerinde ISBN (kitapların uluslararası standart numarası) bazen eski 10 haneli ISBN-10, bazen 13 haneli ISBN-13 olarak karışık geliyor, aynı kitap iki satırda tekrar ediyor ve listelerin bir kısmında ISBN hiç yok, sadece kitap adı var. Emre ise yalnızca geçerli ISBN-13 içeren temiz bir listeyi düşünmüş. İkisi de haklı; farklı işleri tahmin ediyorlar. Ortalamayı alıp 5 yazmak bu bilgiyi kaybettirirdi. Ekip story'yi üçe bölüyor: ISBN-13 listesini yapıştırıp doğrulamak 3, ISBN-10 dönüşümü ve tekrar eden satırları birleştirmek 2, ISBN'siz satırları kitap adıyla eşleştirmek 8. Sonuncusu hâlâ büyük ve belirsiz; onunla üçüncü yazıda uğraşacağız.
T-shirt Sizing ve Affinity Estimation
Her durumda sayı gerekmez. T-shirt sizing'de story'lere XS (çok küçük), S, M, L, XL (çok büyük) gibi bedenler verilir. Erken aşamada, story map'teki büyük parçaların kabaca karşılaştırılması için uygundur; kimse bir L'nin tam olarak kaç point olduğunu tartışmaz. Bedenleri sonradan sayıya çevirmek gerekirse ekip bunu bilinçli bir kararla yapmalı, yoksa bedenler sessizce point'e dönüşür.
Affinity estimation ise çok sayıda story'yi hızlıca boyutlandırmak için kullanılır. Story kartları masaya ya da bir board'a dizilir, ekip konuşmadan kartları küçükten büyüğe doğru sıralar; bir kartı başka bir yere taşımak isteyen taşır, kart sürekli yer değiştiriyorsa o story konuşulmak üzere kenara alınır. Sonra sıralı dizi gruplara ayrılır ve her gruba bir boyut verilir. Kurumsal Sipariş'in 30 story'sini planning poker ile tek tek konuşmak bir öğleden sonra sürer; affinity ile yarım saatte kaba bir boyutlandırma çıkar, poker yalnızca tartışmalı story'lere kalır.
Her iki tekniğin de bir referansa ihtiyacı var. Bookstore ekibi bitmiş story'lerden bir referans seti tutuyor: 1 point için "kitap detayına yayınevi alanını eklemek", 3 point için "sepette kupon kodu girişi", 5 point için "iade talebi formu", 8 point için "kargo firmasının takip numarasını almak". Yeni bir story bu dördüyle karşılaştırılıyor. Referans set olmadan point'in anlamı sprint'ten sprint'e kayar; ekip farkında olmadan aynı işe giderek daha büyük sayılar verir.
Scrum Guide ve Story Point'in Bozulmaları
Scrum Guide 2020 story point'ten, velocity'den, hatta "estimation" kelimesinden hiç söz etmez. Product Backlog refinement'ı anlatırken item'lara açıklama, sıra ve boyut gibi ayrıntıların eklendiğini söyler ve boyutlandırmanın işi yapacak Developers'ın sorumluluğunda olduğunu belirtir. Product Owner seçenekleri anlamalarına yardım ederek etkileyebilir ama sayıyı veren o değildir. Sprint Planning'de ise Developers'ın geçmiş performanslarını, önlerindeki kapasiteyi ve Definition of Done'ı ne kadar iyi bilirse forecast'lerinden o kadar emin olacağını yazar. Yani Scrum boyutlandırmayı ister, ama yöntemi ekibe bırakır. Velocity'nin Scrum içinde neyi gösterip neyi göstermediği Scrum serisinin "Scrum'da Metrikler: Velocity, Burndown ve Sprint Goal Başarısı Neyi Gösterir, Neyi Göstermez?" yazısında.
Story point'in tartışmalı mirası, bu boşluğa yerleşen bozulmalardan geliyor. Üç tanesi çok yaygın:
Point'i saate çevirmek. "1 point = 4 saat" kuralı konduğu anda point'in bütün faydası kaybolur: artık yine süre tahmin ediliyordur, sadece adı farklıdır. Üstelik ilk yazıdaki iyimserlik geri döner. Point'in süreye çevrilmesi gerekiyorsa bu story düzeyinde değil, ekip düzeyinde ve geçmiş velocity üzerinden yapılır.
Ekipler arası karşılaştırmak. Point'ler her ekibin kendi referans setine göre anlamlıdır. Bir ekibin 40 point'i başka bir ekibin 20 point'inden fazla iş demek değildir. Ekipleri velocity ile karşılaştıran yönetim, kısa sürede şişen point'lerle karşılaşır.
Point'i performans ölçütü yapmak. Velocity artışı hedef olunca ekip point'leri büyütür; sayı artar, teslim edilen değer artmaz. Bu, Goodhart yasasının (bir ölçüm hedef hâline geldiğinde iyi bir ölçüm olmaktan çıkar) bilinen bir örneği ve Scrum serisinin metrik yazısında ayrıntısıyla var.
#NoEstimates: Saymak Yeterli mi?
#NoEstimates etiketini 2012'de Woody Zuill başlattı; Vasco Duarte da konuyu aynı adlı kitabında geliştirdi. Adı yanıltıcı: savunucuları tahmini tamamen yasaklamıyor. Önerileri şu: story'leri yaklaşık aynı büyüklüğe gelene kadar böl, sonra point toplamak yerine story say. Bir sprint'te kaç story bittiği, yani throughput, zaten point toplamıyla aynı şeyi söylüyorsa tahmin oturumlarına harcanan zamandan tasarruf edilir.
Bu bir iddia; Bookstore ekibinin verisiyle kontrol edebiliriz. Son 10 sprint'te Done'a giren her story'nin point'i kayıtlı. Önce bu veriyi ve Kurumsal Sipariş Release 1'in planning poker sonrası 30 story'sini bir sınıfa koyuyoruz:
namespace Bookstore.Planning;
public sealed record Sprint(int Number, int[] DonePoints)
{
public int Velocity => DonePoints.Sum();
public int Throughput => DonePoints.Length;
}
public static class SprintHistory
{
// Bookstore ekibi, son 10 sprint (2 hafta): Done'a giren her story'nin point'i
public static readonly Sprint[] Last10 =
[
new(14, [5, 3, 8, 2, 3]),
new(15, [3, 5, 2, 5, 2]),
new(16, [8, 3, 5, 3, 2, 3]),
new(17, [5, 5, 3, 3, 2, 1]),
new(18, [8, 3, 3]),
new(19, [5, 3, 3, 5, 2, 3, 1]),
new(20, [3, 5, 8, 2, 2]),
new(21, [5, 3, 5, 2, 3, 5]),
new(22, [8, 5, 3, 2]),
new(23, [3, 5, 3, 2, 5, 3, 1]),
];
// Kurumsal Sipariş Release 1: planning poker sonrası 30 story
public static readonly int[] Release1 =
[
1, 2, 2, 2, 2, 2, 3, 3, 3, 3,
3, 3, 3, 3, 5, 5, 5, 5, 5, 5,
5, 5, 5, 5, 5, 8, 8, 8, 8, 8,
];
}
Sonra aynı soruyu iki yoldan cevaplıyoruz: Release 1 point toplamıyla kaç sprint sürer, story sayısıyla kaç sprint sürer?
namespace Bookstore.Planning;
public static class PointsVsCount
{
public static void Run()
{
Sprint[] sprints = SprintHistory.Last10;
double[] velocity = [.. sprints.Select(s => (double)s.Velocity)];
double[] throughput = [.. sprints.Select(s => (double)s.Throughput)];
Console.WriteLine("Sprint points stories");
foreach (Sprint s in sprints)
Console.WriteLine($" {s.Number,4} {s.Velocity,6} {s.Throughput,7}");
Console.WriteLine($" mean {velocity.Average(),6:F1} {throughput.Average(),7:F1}");
Console.WriteLine($"Pearson r (points vs stories) = {Correlation(velocity, throughput):F2}");
// Aynı soru iki yoldan: Release 1 kaç sprint sürer?
int[] backlog = SprintHistory.Release1;
Console.WriteLine();
Console.WriteLine($"Release 1: {backlog.Length} stories, {backlog.Sum()} points");
Console.WriteLine($" by points : {backlog.Sum() / velocity.Average():F1} sprints");
Console.WriteLine($" by count : {backlog.Length / throughput.Average():F1} sprints");
// Sayma yöntemi, story boyut dağılımı geçmişe benziyorsa işe yarar
int[] done = [.. sprints.SelectMany(s => s.DonePoints)];
Console.WriteLine();
Console.WriteLine($"Average story size: history {done.Average():F2}, Release 1 {backlog.Average():F2}");
foreach (int size in new[] { 1, 2, 3, 5, 8 })
Console.WriteLine($" {size} pt: history {Share(done, size),3:F0}% Release 1 {Share(backlog, size),3:F0}%");
}
static double Share(int[] stories, int size) => stories.Count(p => p == size) * 100.0 / stories.Length;
static double Correlation(double[] x, double[] y)
{
double mx = x.Average(), my = y.Average();
double cov = x.Zip(y, (a, b) => (a - mx) * (b - my)).Sum();
double sx = Math.Sqrt(x.Sum(a => (a - mx) * (a - mx)));
double sy = Math.Sqrt(y.Sum(b => (b - my) * (b - my)));
return cov / (sx * sy);
}
}
Program.cs'teki switch'e case "count": PointsVsCount.Run(); break; satırını ekleyip çalıştırıyoruz:
dotnet run --project src/Bookstore.Planning -- count
Sprint points stories
14 21 5
15 17 5
16 24 6
17 19 6
18 14 3
19 22 7
20 20 5
21 23 6
22 18 4
23 22 7
mean 20.0 5.4
Pearson r (points vs stories) = 0.81
Release 1: 30 stories, 130 points
by points : 6.5 sprints
by count : 5.6 sprints
Average story size: history 3.70, Release 1 4.33
1 pt: history 6% Release 1 3%
2 pt: history 20% Release 1 17%
3 pt: history 37% Release 1 27%
5 pt: history 28% Release 1 37%
8 pt: history 9% Release 1 17%
Sprint başına point ile story sayısı arasındaki Pearson korelasyonu 0,81. Point'in fazla olduğu sprint'te story sayısı da genelde fazla; en belirgin örnek Sprint 18: üç story, 14 point. On sprint küçük bir örneklem olduğu için bu sayının kendisi de kesin değil, ama yön açık: bu ekipte story saymak, point toplamanın söylediğinin büyük kısmını söylüyor.
Release 1 tahmininde ise iki yöntem ayrışıyor: point'e göre 6,5 sprint, sayıya göre 5,6 sprint. Fark, son satırlarda. Geçmişte ortalama story 3,70 point'ken Release 1'in ortalaması 4,33; 8 point'lik story'lerin payı %9'dan %17'ye çıkmış. Kurumsal Sipariş'in story'leri ekibin alıştığından büyük. Sayma yöntemi geçmişteki story büyüklüğü dağılımının sürdüğünü varsayar; bu varsayım bozulduğunda iyimser bir tarih verir. #NoEstimates'in "önce böl" şartı tam olarak bunun için var. Ekip beş tane 8 point'lik story'yi ikiye bölerse sayı ve point yöntemleri yine yakınlaşır.
Ne zaman mantıklı? Story'leri düzenli olarak küçük dilimlere bölebilen, geçmiş throughput verisi olan ve tarih sorusunu Monte Carlo ile cevaplayabilen bir ekip için tahmin oturumları büyük ölçüde gereksizdir. Kanban serisinin "Service Level Expectation ve Monte Carlo" yazısındaki forecast tam olarak bu veriyle çalışıyor. Yeni bir ekip, alışılmadık büyüklükte bir epic ya da bölünmesi zor teknik bir iş söz konusuysa, boyut konuşması hâlâ değerli bilgi üretir.
Üretimde Dikkat
Bug ve teknik işler için baştan bir kural koy ve ona uy. Bazı ekipler bug'lara point vermez ve velocity'yi yalnızca yeni değer üreten işlerle ölçer; bu durumda bug'lar kapasiteyi düşürür ve velocity bunu gösterir. Bazıları her işe point verir; o zaman velocity daha kararlı olur ama yeni özellik hızını olduğundan yüksek gösterir. İkisi de savunulabilir, ama ikisini karıştırmak savunulamaz: bir sprint'te bug'lara point verip bir sonrakinde vermezsen velocity grafiği hiçbir şey anlatmaz.
Referans seti canlı tut. Her birkaç sprint'te bir, bitmiş story'lerden yeni örnekler seç ve eskimiş olanları çıkar. Ekibe yeni biri katıldığında planning poker'ın ilk turlarında referans seti açıkça göster; yoksa yeni gelen kendi eski ekibinin ölçeğini getirir.
Planning poker'ı zamanla sınırla. Bir story'de iki tur sonunda uzlaşma yoksa bu, story'nin yeterince anlaşılmadığının işaretidir. Tartışmayı uzatmak yerine story'yi böl ya da bir sonraki refinement'a kadar soruların cevabını bul.
Sık Düşülen Tuzaklar
- Ortalamayı almak: 3 ile 8'in ortası 5'tir ama iki kişinin farklı işleri düşündüğü bilgisi kaybolur; önce farkın nedenini konuş.
- En kıdemlinin sayısına çapalanmak: kartlar aynı anda açılmazsa ya da en kıdemli kişi önce konuşursa geri kalanlar onun sayısına yaklaşır.
- Point'i saate çevirmek: story düzeyinde yapılan her dönüşüm, relative estimation'ın faydasını siler.
- Bug ve teknik işlerde tutarsızlık: bir sprint point verip bir sprint vermemek velocity'yi anlamsız kılar.
- Velocity'yi ekipler arasında karşılaştırmak ya da hedef yapmak: point'ler şişer, teslim edilen iş değişmez.
- Sayma yöntemini bölme yapmadan kullanmak: story büyüklükleri geçmişten farklıysa throughput tahmini iyimser kalır.
Küçük Bir Deneme
★☆☆ Ekibinin son on bitmiş story'sinden dört tanesini referans set olarak seç (1, 3, 5 ve 8 point için birer tane). Bir sonraki refinement'ta yeni story'leri yalnızca bu dördüyle karşılaştırarak boyutlandır.
★★☆ SprintHistory.Release1 dizisindeki beş tane 8'lik story'yi 5 ve 3 olarak böl ve programı yeniden çalıştır. Sayıya göre ve point'e göre tahmin birbirine ne kadar yaklaştı?
★★★ PointsVsCount'a, son 10 sprint yerine kayan bir pencereyle (ör. son 6 sprint) korelasyonu hesaplayan bir döngü ekle. Korelasyon zaman içinde değişiyor mu? Değişiyorsa ekibin bölme alışkanlığında ne değişmiş olabilir?
Point'ler ve sayılar, birbirine benzeyen çok sayıda story için iyi çalışıyor. Peki ISBN'siz satırları kitap adıyla eşleştirmek gibi, ekibin daha önce hiç yapmadığı ve tek başına büyük belirsizlik taşıyan bir iş nasıl tahmin edilir? Bir sonraki yazıda point dışındaki yöntemlere bakıyoruz: work breakdown, three-point ve PERT (Program Evaluation and Review Technique; üç tahminin ağırlıklı ortalamasına dayanan yöntem), Wideband Delphi ve reference class forecasting.