Team Velocity


Team Velocity = Story Points pro Sprint.
Wir hatten einige Sprints hinter uns, nun war der Velocity-Report interessant. Hier sieht man wie viele Story Points das Team in den Sprints geschafft hat. Nun ergab sich zwar ein wirklichkeitsnaher Mittelwert von 34 Story Points. Aber es fanden sich auch Werte von 5 bis 82. Woher kommen diese Schwankungen im Velocity Report?
1. PBIs laufen über mehrere Sprints.
Ja wir haben sie! Diese Product Backlog Items (PBIs), die von einem Sprint zum nächsten "schlüpfen" und nie so richtig fertig werden. Das klassische "Everything in Progress" Problem. Es fehlt halt nur noch ein Review mit der Fachabteilung oder ein Test einer Daten-Schnittstelle. Aber die Ressourcen außerhalb des Teams sind nicht verfügbar, krank, im Urlaub oder mit anderen "Prio 1 Themen" beschäftigt. Wir können dann das Product Backlog Items (PBIs) nicht vollständig abschließen. Es wandert in den nächsten Sprint. Wird das PBI schließlich auf "Done" gesetzt, wertet TFS die Story Point vollständig zum aktuellen Sprint, obwohl der Großteil der Arbeit in einem vorigen Sprint verrichtet wurde.
Fazit: Blockieren Ressourcen außerhalb des Team den Fortschritt muss man leider damit leben
2. Reporting Problem: Story Points tauchen im Folgesprint auf.
Ein ähnliches Problem liegt hier vor. Auch hier entscheidet der Tag an dem das PBI auf "Done" gesetzt wurde, für welchen Sprint dessen Story Points zählen. Oft wurden die Story Points erst im Folgesprint angezeigt (rote Pfeile). Wir hatten das PBI aber rechtzeitig fertiggestellt! Was war passiert?


Den letzen Daily Scrum eines Sprints hielten wir immer am Vormittag des Planungstages für den Folgesprint ab. Hier haben wir oft noch offenen Punkte auf "done" gesetzt (meist irgendwelche Branching- ,Build- oder Infrastruktur-Aufgaben). Und das war das Problem. Die PBIs zählten dann schon zum nächsten Sprint, dessen Zeitintervall schon begonnen hatte. Wir haben das Problem dadurch gelöst, dass die Planung eines Folgesprints noch im Zeitintervall des aktuellen Sprints stattfindet. Dann kann man auch noch am letzen Tag Punkte auf "done" setzen, ohne dass das Reporting beleidigt ist…
Fazit: Story Points werden zu genau dem Sprint-Zeitintervall gezählt in dem der Status des PBI auf "Done" gesetzt wurde.

Vom Traum durch präzise Arbeitsplanung keine Ressourcen zu vergeuden


Wir nutzen TFS und die Projektvorlage ScrumForTeamSystem. Unser Taskboard für die Daily Scrums ist also digital. Wir treffen uns täglich rund um einen 24" Monitor und schieben unsere digitalen Aufgabenzettel übers Taskboard. Die Burndown Charts des Sprints wird daraus automatich ermittelt.
Neulich hatte ich mit einem Kollegen, der Scrum nicht kennt, eine interessante Diskussion. Der Anstoß der Diskussion war ein Blick auf unser aktuelles und vergangene Burndown Charts.
Die Fragestellung: "Warum weicht den Euer Fortschritt in den Burndown Charts so stark vom Plan ab?" und "Warum plant ihr die Sprints nicht so, dass die Ist-Kurve auf der Plan-Kurve liegt?"

Oben sind drei Beispiele von Burndown Charts aufgezeigt. Stellt die rote Kurve eine falsche Planung dar? "Vertrödelt" das Team gar die Zeit und bringt nichts fertig?
Was würde es denn bedeuten ein Team exakt auf die grüne Kurve hin zu trimmen? Wir Entwickler würden in in unseren Aufwandsabschätzungen dazu einfach genügend Puffer einbauen. Wir wollen dann ja auch ganz sicher gehen, dass wir im Zeitraster der Plankurve bleiben. Nun kann sein, dass sie diesen Puffer benötigen. Wahrscheinlicher ist es aber, dass sie ihn nicht benötigen. Dies bedeutet, dass in einer grünen Kurve eine Menge Zeitreserven stecken. In einer Roten aber keine.
Fazit: Allzu glatte Burndown Charts können nur mit genügend Zeitreserven erreicht werden.


Kombinationen und Maskierungen mit Enums und Flags


Das Beispiel unten zeigt, wie Flags eingesetzt werden können um bitweise mit Kombinationen arbeiten zu können. Mehrere Flags können mit einem OR-Operator verknüpft werden (Siehe Zeile 14). Eine Kombination kann über einen AND-Operator aufgelöst werden (Siehe z. B. Zeile 19).

1 [Flags()]
2 public enum Usage
3 {
4 Private = 1,
5 Commercial = 2,
6 Education = 4,
7 Training = 8
8 }
9
10 class Program
11 {
12 static void Main(string[] args)
13 {
14 TheUsage(Usage.Commercial | Usage.Education);
15 }
16
17 static void TheUsage(Usage usage)
18 {
19 bool isPrivate = (Usage.Private & usage) == Usage.Private; // false
20 bool isCommercial = (Usage.Commercial & usage) == Usage.Commercial; // true
21 bool isEducation = (Usage.Education & usage) == Usage.Education; // true
22 bool isTraining = (Usage.Training & usage) == Usage.Training; // false
23 }
24 }


Ich liebe es.

Schlankes ASPX: ASHX!


Aspx Seiten sind unschlagbar beim Entwerfen von Web Formularen. Möchte man aber z. B. nur ein dynamisches Image erzeugen oder eine Datei zum Download anbieten, benötigt man aber den "Overhead" den die Web Formulare mitbringen (Page Events, Server Side Tags, etc. ) nicht.
Hierfür gibt es Generic Handlers.
Diese haben die Dateiendung ASHX:

Dieses Beispiel schreibt einen vertikal verlaufenden Text als jpeg, was beispielsweise für Tabellenüberschriften verwendet werden könnte.


1 <%@ WebHandler Language="C#" Class="SampleHandler" %>
2 using System;
3 using System.Web;
4 using System.Drawing;
5 using System.Drawing.Imaging;
6 using System.Drawing.Drawing2D;
7
8 public class Handler : IHttpHandler
9 {
10
11 public void ProcessRequest(HttpContext context)
12 {
13
14 string text = context.Request.QueryString["text"];
15
16 System.IO.MemoryStream memStream = new System.IO.MemoryStream();
17 Bitmap bitmap = new Bitmap(100, 20, PixelFormat.Format32bppRgb);
18
19 Graphics graphic = Graphics.FromImage(bitmap);
20
21 graphic.SmoothingMode = SmoothingMode.HighSpeed;
22 graphic.Clear(Color.White);
23 graphic.DrawString(text, new Font("Arial", 12), Brushes.Black, 0, 0);
24
25 bitmap.RotateFlip(RotateFlipType.Rotate270FlipNone);
26 bitmap.Save(memStream, ImageFormat.Jpeg);
27
28 context.Response.ContentType = "image/jpeg";
29 context.Response.BinaryWrite(memStream.ToArray());
30 context.Response.End();
31
32 }
33
34 public bool IsReusable
35 {
36 get
37 {
38 return false;
39 }
40 }
41}


Das Resultat des Beispiels ist ein Image. Und all das ohne die Page Events und den Page LifeCycle von ASPX.