Stephen Balkum

Agile Enthusiast, Leader, and Developer

What metrics to collect in a Scrum Sprint cycle

no comment

I recently joined PureWRX as CTO. PureWRX is a very new company which that means I am building out the technology team and all of the processes that accompany the team. I’m a big believer in the iterative nature of Scrum, so I’d like to comment on the metrics I collect and communicate.

For a little background, I took a ScrumMaster certification course around 2006 and I have been using aspects of Scrum ever since. The metrics I will discuss here I used at my previous employer, BuildASign.com, where I managed all of the software engineering efforts for the past 2+ years. I took the position at BuildASign because I saw an opportunity to focus entirely on the engineering process and culture. What made this opportunity great is that I had 100% buy-in from management. This doesn’t happen everywhere, so I consider myself lucky.

I’m also a huge believer in Team. We rise and fall as a team. So, even though I take credit for instilling this process, it is the team that accomplished the results. I will now switch from first person singular to first person plural.

Points

We measure points. Points are a measure of complexity and amount of work required to complete a task. The sizes are XS, S, M, L, XL, and XXL. Anything else is too small to mention or too large to estimate.

We equate these sizes to numbers to facilitate visualization. Just as light and sound are measured on logarithmic scales, so are the sizes of tasks. The numbers we used were 1, 5, 13, 30, 70, 150. The increasing curve represents an increasing error in the estimate of the work. [An aside: the curve is basically a doubling with a bonus. I mention the comparison to light (lumens) and sound (decibels) because humans can most easily recognize a doubling of intensity. For this reason, the traditionally used Fibonacci series for sizes is insufficient in my opinion.]

The size of a card should not change. In other words, the characteristics that define a M card never change so that a M card today is comparable to a M card next year. However, the expectation is that we will get better and faster at what we do over time. Thus, we expect to see more points accomplished per cycle over time. This is where we set a goal. We will have to get 2-3 cycles of data to establish a baseline and then set improvement goals from that point.

We publish points in two ways:
1) We publish how we did each cycle and look at trends
2) We publish the completed points during a cycle to see how we are progressing on a daily basis.

Accuracy

We measure the accuracy of our estimates. At the end of each cycle, we review all of the tasks and discuss where our estimates were wrong and why. Misses can be positive or negative. We sum the misses and look at the percentage of the total cycle. This gets published for all to see. Again, we will establish a baseline and then set goals. I would expect accuracy around 10% for several cycles.

Stability

Automated testing is the name of the game. Any time a developer changes code in one area of the code, it will impact seemingly unrelated areas of the code. We arrest this problem by writing automated “unit tests” as part of the day to day work. Analysis tools then tell us what percentage of the code has a test around it. This is Code Coverage.

Bugs

Working on code that failed to do what was intended is an indication of a failure in the process early in the work flow. It is also wasted time. While software bugs are inevitable, that doesn’t mean we shouldn’t look to improve the issue. With each cycle, we indicate how many points of the total were spent fixing bugs. This ratio is the bug percentage. We publish this for all to see and expect it to decrease over time.

Trends

Trend numbers are calculated by looking at 6 consecutive values, removing the high and low, and averaging the remaining 4. These trends are published for all to see.

Culture of Quality

By focusing on the areas mentioned above, we establish a Culture of Quality. Everything else we might want will automatically improve over time. Did we release features on time? Our point velocity and accuracy tell us this. Is our site always up and taking orders? Our automated test coverage and focus on low bug counts keep our site going.

The Results

These metrics were used over a 2 year period with an established team and established code base. Much of the first year involved tweaking the team by discovering and removing ineffective members and adding new members. Just like a good sports team, every good team does this continuously.

One can’t help but like the results. The per developer productivity increased 40%. This growth was gradual over entire period. Meanwhile, the time spent on bugs dropped from 50% to 10%. This improvement occurred mostly in the first year as the Culture of Quality took over.

Way to go team!

Azure SDK DFInit fails for usernames with a space

no comment

I was moving some of my dev projects to a new computer today. Usual procedure: pull down source from repository, open in VS and do a sanity build. Well, my Azure based project kicked off the Azure SDK initialization chores and I was greeted by this failure:

It was important to scroll this dialog up to see “The option is not valid: balkum.” It took me a while of fruitless web searches before the issue dawned on me. When I built this computer, I entered “Stephen Balkum” for a user during the Windows installation. The Azure SDK must have queried the system for my username and called DFInit with this command line:

DFInit /user:stephen balkum

and the space in my username confused the command.

To fix the issue, I took a stab in the dark (there is no online help on DFInit), and ran it from a command line. The dialog says /user is optional and will pull my username from the parent process. Worked like a charm.

Upgraded to Windows 8.1, what am I installing?

no comment

I have been running Windows 8.1 Preview since it was released. It is a nice improvement over the original version. Of course, with the upgrade to the official release, I have to reinstall all of my apps since I was running preview. So, I will use this post to document the software I install. I’m sure I will be updating this list a few times over the next couple of weeks. And I’ll reference it when my new laptop arrives mid-November.

Office 365
Visual Studio 2013
Resharper 8
SQL Server 2012
SSMS
Dropbox
(Skydrive already installed)
Chocolatey

Via Chocolatey:
Putty
TortoiseSVN
MongoDB
Inkscape
Paint.Net
Console2
Notepad2
Teamviewer

Thanks, LinkedIn, but no thanks

no comment

LinkedIn has been really pushing their new premium service. I have not subscribed, mostly because it is not a trivial amount of money, but a lot of recruiters have because I am now getting pounded with requests from recruiters. Part of me is flattered. I work hard and I think my career says that.

However, my volume of email just took a measurable tick upward and that is not helpful, LinkedIn.

I also try hard to keep my connections list in LinkedIn true, i.e. each person is someone I know and at least had some meaningful discussions. So, with the last request from a recruiter that I politely declined, I unchecked all of the boxes only to discover I had just added this person as a new connection. Very sneaky, LinkedIn, and I deleted that connection.

I did find that I can request people not contact me about new jobs. I even attached a nice message. Time will tell if this helps and I will be marking future recruiting communications as inappropriate.

I am dissatisfied, LinkedIn.

So You Want to Learn SQL… Final Session

no comment

As mentioned in the previous session, this is the last issue of this series.  Even though I could continue with more rarely used SQL features, I feel the content presented meets the stated goal.  Here are the final homework answers.

Homework Answers

1) List the two-week rolling total quantity by territory for the current period and 3 prior periods by week. The result set should have three columns: Period Start Date, Territory Name, and Total Quantity.

SELECT w.startdate, t.Name, SUM(od.OrderQty) AS Quantity
	FROM Sales.SalesOrderHeader o
	JOIN Sales.SalesOrderDetail od ON od.SalesOrderID = o.SalesOrderID
	JOIN Sales.SalesTerritory t ON t.TerritoryID = o.TerritoryID
	JOIN (
		SELECT	DATEADD(dd, -1, DATEADD(ww, DATEDIFF(ww, 0, '8/1/2008'), 0)) as 'StartDate',
			DATEADD(dd, 13, DATEADD(ww, DATEDIFF(ww, 0, '8/1/2008'), 0)) as 'EndDate'
		UNION ALL
		SELECT	DATEADD(dd, -8, DATEADD(ww, DATEDIFF(ww, 0, '8/1/2008'), 0)),
			DATEADD(dd, 6, DATEADD(ww, DATEDIFF(ww, 0, '8/1/2008'), 0))
		UNION ALL
		SELECT	DATEADD(dd, -15, DATEADD(ww, DATEDIFF(ww, 0, '8/1/2008'), 0)),
			DATEADD(dd, -1, DATEADD(ww, DATEDIFF(ww, 0, '8/1/2008'), 0))
		UNION ALL
		SELECT	DATEADD(dd, -22, DATEADD(ww, DATEDIFF(ww, 0, '8/1/2008'), 0)),
			DATEADD(dd, -8, DATEADD(ww, DATEDIFF(ww, 0, '8/1/2008'), 0))
	) W ON W.StartDate <= o.OrderDate AND w.EndDate > o.OrderDate
	GROUP BY w.startdate, t.Name
	ORDER BY w.StartDate, t.Name

2) List the total sales for each day of the week by territory for July 2008. The columns in the result set should be: Territory, Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday. Do not display the territory if it had zero sales all week.

SELECT	t.Name AS Territory,
		ISNULL(Sunday.Sales, 0) AS Sunday,
		ISNULL(Monday.Sales, 0) AS Monday,
		ISNULL(Tuesday.Sales, 0) AS Tuesday,
		ISNULL(Wednesday.Sales, 0) AS Wednesday,
		ISNULL(Thursday.Sales, 0) AS Thursday,
		ISNULL(Friday.Sales, 0) AS Friday,
		ISNULL(Saturday.Sales, 0) AS Saturday
	FROM Sales.SalesTerritory t
	LEFT JOIN (
		SELECT  o.TerritoryID,
				SUM(o.TotalDue) AS Sales
			FROM Sales.SalesOrderHeader o
			WHERE o.OrderDate >= '7/1/2008' AND o.OrderDate < '8/1/2008'
				AND DATEPART(dw, o.OrderDate) = 1
			GROUP BY o.TerritoryID
	) Sunday ON Sunday.TerritoryID = t.TerritoryID
	LEFT JOIN (
		SELECT  o.TerritoryID,
				SUM(o.TotalDue) AS Sales
			FROM Sales.SalesOrderHeader o
			WHERE o.OrderDate >= '7/1/2008' AND o.OrderDate < '8/1/2008'
				AND DATEPART(dw, o.OrderDate) = 2
			GROUP BY o.TerritoryID
	) Monday ON Monday.TerritoryID = t.TerritoryID
	LEFT JOIN (
		SELECT  o.TerritoryID,
				SUM(o.TotalDue) AS Sales
			FROM Sales.SalesOrderHeader o
			WHERE o.OrderDate >= '7/1/2008' AND o.OrderDate < '8/1/2008'
				AND DATEPART(dw, o.OrderDate) = 3
			GROUP BY o.TerritoryID
	) Tuesday ON Tuesday.TerritoryID = t.TerritoryID
	LEFT JOIN (
		SELECT  o.TerritoryID,
				SUM(o.TotalDue) AS Sales
			FROM Sales.SalesOrderHeader o
			WHERE o.OrderDate >= '7/1/2008' AND o.OrderDate < '8/1/2008'
				AND DATEPART(dw, o.OrderDate) = 4
			GROUP BY o.TerritoryID
	) Wednesday ON Wednesday.TerritoryID = t.TerritoryID
	LEFT JOIN (
		SELECT  o.TerritoryID,
				SUM(o.TotalDue) AS Sales
			FROM Sales.SalesOrderHeader o
			WHERE o.OrderDate >= '7/1/2008' AND o.OrderDate < '8/1/2008'
				AND DATEPART(dw, o.OrderDate) = 5
			GROUP BY o.TerritoryID
	) Thursday ON Thursday.TerritoryID = t.TerritoryID
	LEFT JOIN (
		SELECT  o.TerritoryID,
				SUM(o.TotalDue) AS Sales
			FROM Sales.SalesOrderHeader o
			WHERE o.OrderDate >= '7/1/2008' AND o.OrderDate < '8/1/2008'
				AND DATEPART(dw, o.OrderDate) = 6
			GROUP BY o.TerritoryID
	) Friday ON Friday.TerritoryID = t.TerritoryID
	LEFT JOIN (
		SELECT  o.TerritoryID,
				SUM(o.TotalDue) AS Sales
			FROM Sales.SalesOrderHeader o
			WHERE o.OrderDate >= '7/1/2008' AND o.OrderDate < '8/1/2008'
				AND DATEPART(dw, o.OrderDate) = 7
			GROUP BY o.TerritoryID
	) Saturday ON Saturday.TerritoryID = t.TerritoryID
	WHERE Sunday.Sales IS NOT NULL
		OR Monday.Sales IS NOT NULL
		OR Tuesday.Sales IS NOT NULL
		OR Wednesday.Sales IS NOT NULL
		OR Thursday.Sales IS NOT NULL
		OR Friday.Sales IS NOT NULL
		OR Saturday.Sales IS NOT NULL
	ORDER BY t.Name

or

SELECT t.Name AS Territory,
	SUM(CASE DATEPART(dw, o.OrderDate) WHEN 1 THEN o.TotalDue ELSE 0 END) AS Sunday,
	SUM(CASE DATEPART(dw, o.OrderDate) WHEN 2 THEN o.TotalDue ELSE 0 END) AS Monday,
	SUM(CASE DATEPART(dw, o.OrderDate) WHEN 3 THEN o.TotalDue ELSE 0 END) AS Tuesday,
	SUM(CASE DATEPART(dw, o.OrderDate) WHEN 4 THEN o.TotalDue ELSE 0 END) AS Wednesday,
	SUM(CASE DATEPART(dw, o.OrderDate) WHEN 5 THEN o.TotalDue ELSE 0 END) AS Thursday,
	SUM(CASE DATEPART(dw, o.OrderDate) WHEN 6 THEN o.TotalDue ELSE 0 END) AS Friday,
	SUM(CASE DATEPART(dw, o.OrderDate) WHEN 7 THEN o.TotalDue ELSE 0 END) AS Saturday
	FROM Sales.SalesTerritory t
	JOIN Sales.SalesOrderHeader o ON o.TerritoryID = t.TerritoryID
	WHERE o.OrderDate >= '7/1/2008' AND o.OrderDate < '8/1/2008'
	GROUP BY t.Name
	ORDER BY t.Name