“These days we do not program software module by module; we program software feature by feature.”
As user stories rise to the top of the release plan, meaning that they will be implemented soon, they often need to be split. After all, if implementing a particular user story will take longer than the length of the iteration, there’s no choice but to split the story into two or more stories. Learning how to see ways to split stories is not a particularly difficult skill to acquire, but it does take practice and experience. The more different user stories you’ve split in the past, the easier it becomes. With that in mind, this chapter offers advice on splitting stories by providing a number of examples. From these examples, a number of guidelines are distilled that can be used for splitting other stories.
The advice in this chapter can be used any time a story needs to be split. However, the guidelines are specifically targeted at the user stories or features that seem difficult to split.
There are times when it may be necessary to split a user story into multiple, smaller parts. First, a user story should be split when it is too large to fit within a single iteration. Sometimes a user story won’t fit in an iteration because it is bigger than a full iteration. Clearly, a story in this situation needs to be split. Alternatively, a story may be small enough to fit within an iteration, but it won’t fit within the iteration being planned because there isn’t enough room left. The team may feel they will have time to develop a portion of a story in the iteration but not the entire story.
Second, it can be useful to split a large user story (an epic) if a more accurate estimate is necessary. For example, one of my clients was contemplating new features that would provide enhanced access into their system to customer service representatives (CSRs) who worked at other companies. The first question the product owner had to answer was whether these features were worth pursuing. Rather than writing a bunch of individual user stories, she wrote one large story and described the vision behind that story to the team. They estimated it as seventy story points. That was good enough for her to know she wanted to add these features. She knew there was a great deal of uncertainty in the estimate, but even if the estimate were off by 100% these features were still worth doing. If seventy story points had put her on the border of whether or not to include the CSR features in the release, she could have chosen to split the large story and have the team estimate the multiple smaller stories.
One of the best ways to split a large user story is by the data that will be supported. For example, on a recent project the team was developing a product to collect financial information. They started with one epic user story: “As a user, I can enter my balance sheet information.” A balance sheet in this case could include a great many fields. At the highest level, it includes assets and liabilities. Assets included such items as cash, securities, real estate, automobiles, loans, and so on. The system was such that the user could interact with this balance sheet at various levels of detail. A user could enter a single value representing all of his assets. Or he could enter slightly more detailed information (a total value of all loans, for example) or much more detailed information (an itemization of all loans). Considering the number of fields on the screen and the interactions among them, this was far more than the team felt they could complete in a single two-week iteration.
The team split this story by the type of data that the user could enter. Their first story was “As a user, I can enter summary balance sheet data.” This story was very small (almost too small), as it covered only creating the basic form and two fields: one for assets and one for liabilities. Their next story was “As a user, I can enter categorized balance sheet data.” This story covered the next level of detail (such as cash, securities, real estate, loans, and so on). When this story was implemented, there would be two dozen input fields on the screen. Their next story covered data validation, “As a user, I want the values I enter to be validated so that I don’t make any mistakes.” They discussed what this meant and agreed that positive and negative amounts would be allowed, that decimals could be entered but that amounts would be automatically rounded to the nearest dollar, and so on.
Their next user story was “As a user, I can enter detailed loan information.” This story would allow a user to enter up to 100 loans (this number was discussed, agreed to, and noted as a condition of satisfaction on the story card). This story was larger than it sounds because it addressed a number of user interface issues, such as how new rows of loan data would be displayed on the screen. The story was much larger than the others that had been split out from the original story. However, even this story was much smaller than the original story because this one called for supporting detailed data only for loans. The loan story was used as a pattern for many other user stories that were split from the original, such as “As a user, I can enter detailed information about my real estate holdings” and “As a user, I can enter detailed information about my cash holdings, including checking and savings accounts.”
By splitting the initial story in this way, the team created about a dozen user stories from the initial one. Each of the new stories was now well within the size they could complete in a two-week iteration. This leads to our first guideline:
Split large stories along the boundaries of the data supported by the story.
Splitting a user story along data boundaries is a very useful approach and one you should definitely have in your bag of tricks. As another example, a few years ago I was working with a team developing an automated fax subsystem. The team was faced with some large user stories about how the system could be configured. The stories were made much smaller by splitting support for U.S. and international phone numbers.
In some cases a large story can be made much smaller by removing the handling of exceptional or error conditions from the main story. Suppose you are working on a system to process loan repayments and have this user story: “As a borrower, I want to pay off my loan.” When the team discusses this story, the product owner points out that if the borrower inadvertently sends a check for more than the outstanding loan amount, a refund check has to be printed and mailed back to the borrower. She adds that this applies only for amounts over ¤2. This story could be split by writing the following stories:
• As a borrower, I want to pay off my loan. Note: Allow overpayments.
• As a borrower, if I accidentally repay too much, I get a refund if it’s over ¤2.
I worked with a team recently that was tasked with developing a very complex search screen. There were dozens of fields on the top half of the screen, a middle-tier query builder that assembled formulated database queries based on what had been entered, and then a complex data display grid on the bottom of the screen. All of this work was initially described by a single user story. I had the team split the work into three pieces that were spread across three iterations.
In the first iteration they laid out the basic user interface, including about half of the search criteria fields that were on the top of the screen. They also wrote the portions of the query builder that worked with those fields. The bottom part of the screen was to hold the complex data display grid. This was too much to do in one two-week iteration. So at the end of the first iteration, that portion of the screen displayed a simple message such as “This search found 312 matches.” This certainly wasn’t very useful for a user who wanted to know what those 312 matches contained. However, it represented significant progress and made that progress visible to all project stakeholders.
The second iteration on this project added the data display grid, and the third iteration added the remaining search criteria fields to the top of the screen. These iterations were prioritized this way because there was a lot of uncertainty about how long it would take to develop the data display grid. Removing that uncertainty in the second iteration was deemed better than leaving it until the third. Because the team had already achieved a solid understanding of what was necessary to create the query builder, they considered that work to be low risk. This leads to our next guideline:
Split large stories based on the operations that are performed within the story.
A common approach to doing this is to split a story along the boundaries of the common CRUD operations—Create, Read, Update, and Delete. To see how this works, suppose you are working on the SwimStats system, and the team is ready to develop this story: “As a coach, I can manage the swimmers on my team.” The team talks to the coaches/users and finds out that this means the coach wants to add new swimmers, edit existing data for swimmers, and delete swimmers who have left the team. This initial story can easily be split into three stories:
• As a coach, I can add new swimmers to my team.
• As a coach, I can edit information about swimmers already on my team.
• As a coach, I can delete swimmers who are no longer on my team.
These stories very closely correspond to the Create, Update, and Delete portions of CRUD. Splitting a large story into these three stories is a very common pattern and leads to our next guideline:
Split large stories into separate CRUD operations.
There are many orthogonal or cross-cutting features in a typical application. Security, error-handling, and logging, for example, each cut across all of the other features of an application. A story that is too big to fit in an iteration can often be reduced by isolating it from one or more of these cross-cutting concerns.
For example, many applications contain screens that behave differently depending on the privileges of the current user. If that is too much to develop in a single iteration, develop the screens in one iteration and add support for user-specific privileges in a later iteration.
On a recent project a client needed to split a story that involved searching for data and displaying the results. Each search would span the entire database, but only those results the user had privileges to see were to be displayed. The team’s solution was to ignore this security restriction, and in the first iteration on this feature, users could see the entire result set.
As another example, suppose the team plans to work on a story that says “As a user, I am required to log in with a username and password before using the system.” The team discusses the story and comes up with a list of constraints on the password: It must be at least eight characters, must include at least one digit, must not include any characters repeated three or more times, must be encrypted when transmitted and stored, and so on.
None of this may be particularly time-consuming, but in aggregate it may make the story a little too big to fit in an iteration. This can be resolved by removing the security precautions from the login story and creating nonsecure and secure versions of the story. The second story could list the planned security precautions, such as password length, character restrictions, and so on. You probably would not choose to release the product with only the first, nonsecure story developed, but there could be value in splitting the initial, large story in this way.
This leads to our next guideline:
Consider removing cross-cutting concerns (such as security, logging, error handling, and so on) and creating two versions of the story: one with and one without support for the cross-cutting concern.
In software development we often forget (or ignore) Kernighan and Plauger’s (1974) advice to “Make it work, then make it faster.” A few years ago I was on a project to display charts of stock market prices. Web users would request a chart by the stock’s ticker symbol. Our code would then retrieve the price history of that stock (over any of a variety of time periods from the current day to the last five years) and display a line chart of the stock. Among the conditions of satisfaction associated with that feature were ones covering the accuracy of the line, the handling of missing data, and the performance. To meet the performance target, we would need to cache the most frequently requested charts, regenerating each once per minute. Because caching would be a significant part of the effort to deliver this new feature, we separated it into a new user story and scheduled it into the next iteration. More generally, this same approach can be applied to any nonfunctional requirement, which leads to our next guideline:
Consider splitting a large story by separating the functional and nonfunctional aspects into separate stories.
For example, this approach can be applied if a new feature must consume less than a defined amount of memory, be drawn with fewer than a number of shapes, or use a critical resource for less than a defined amount of time.
Occasionally, one story comprises multiple smaller substories that are of different priority. Suppose a project includes a typical login story: “As a user, I am required to log into the system.” The product owner expresses her conditions of satisfaction for this story, and they include the following:
• If the user enters a valid username and password, she is granted access.
• If the user enters an invalid password three times in a row, she is denied access until she calls customer service.
• If the user is denied access, she is sent an email stating that an attempt was made to use her account.
This story is too big to fit in one iteration, so it must be split. The story can be split by looking for low-priority elements. In this case the product owner would not ship the product if it did not support the core login functionality. She might, however, be willing to release the product without a retry time-out mechanism or without sending an email about the access attempt. This leads to another guideline about splitting stories:
Separate a large story into smaller stories if the smaller stories have different priorities.
Sometimes, we come across a feature that is difficult to split. In these cases, it is tempting to split the feature into its constituent tasks. For most software developers, considering a feature and decomposing it into its constituent tasks is such a habit that we often do it without even being aware of it. Do not, for example, split a story into the following:
• Code the user interface.
• Write the middle tier.
The best way to avoid this temptation is to follow Hunt and Thomas’ advice (1999) to fire a tracer bullet through the system. A tracer bullet travels through all layers of a feature. That may mean delivering a partial user interface, a partial middle tier, and a partial database. Delivering a cohesive subset of all layers of a feature is almost always better than delivering all of one layer. This leads to another guideline:
Don’t split a large story into tasks. Instead, try to find a way to fire a tracer bullet through the story.
Once you’ve split a story and have it at an appropriate size, don’t make things worse by adding work to the story. Often, this comes in the form of the temptation of related changes. We tell ourselves, “While I’m in that code, I might as well take care of these other lingering changes.” It can very possibly be appropriate to fix a bug or address an old issue while working on a separate issue in the same part of the code. However, the priority of doing so needs to be considered in the same manner in which priorities were considered for other features. In other words, which is higher priority: spending half a day fixing a year-old bug or spending the same amount of time on something else? The answer is clearly entirely contextual and becomes this chapter’s final guideline:
Avoid making things worse by adding related changes to an appropriately sized feature unless the related changes are of equivalent priority.
With all of this advice about splitting stories, it may be tempting to think that every user story about to be worked on should be made as small as possible. That’s not the case. For teams working in two-week iterations, splitting features such that each can be done in two to five days or so is appropriate. (Stories may still be estimated in story points, but by the time a team needs to split stories they will know approximately how many story points or ideal days equate to around two to five days.) Stories will need to be a little smaller for one-week iterations and can, but don’t need to, be a little larger for longer iterations. Stories of these approximate sizes flow best through the short iterations of an agile project.
Just as we may need to split large stories, we may need to combine multiple tiny stories. The combined stories are estimated as a whole rather than individually. When possible, try to combine related stories as that will make it easier to prioritize them. It is very common to combine multiple bug reports and treat them as one item.
It can be useful to split a story that does not fit in an iteration, either because it’s too large for any iteration or it’s too big to fit in the time remaining in an iteration being planned. It is also useful to split a large story if you need to provide a more accurate estimate than can be made of the one large story.
A story may be split by the type of data it will support. A story may also be split based on the operations inherent in the story. Splitting stories across the common CRUD operations (Create, Read, Update, Delete) is common. A story may be made smaller by segmenting out any cross-cutting concerns, such as security, logging, error handling, and so on. A story may also be made smaller by ignoring performance targets during the iteration in which the story is made functional. The performance target can be made its own story and satisfied in a later iteration. Many stories describe two or more needs. If these needs are of different priority, split the stories that way.
Avoid splitting a story into the development tasks that will be necessary to implement the feature. Splitting work into its necessary tasks is such a habit for us that it is easy for us to begin splitting user stories that way. Avoid the temptation of making a large story any larger by including related changes that are not necessary for the delivery of the user story.
Finally, remember that sometimes it is appropriate to combine user stories, especially in the case of bug fixes, which may be too small on their own.