Take control of feature implementation with a refining stage

The most valuable features I've ever created were the ones that had a clear objective for their implementation, and the entire team had complete control over their tasks throughout the development process. A well-executed refining stage is a major factor in the rest of the process running smoothly.

When a developer is skilled at refining stories, they're better able to scope features in a way that captures their value in a reasonable and well-estimated time. It's a difficult skill to acquire because the appropriate level of detail differs between features.

When refinement goes wrong

"Let's see what happens when we get there."

I've heard this comment many times in my career as I've worked on technical refinement, and I've said it myself as well. While this is a perfectly appropriate method for performing some less complex tasks, it is a risky approach.

Since this approach means many decisions must be made on the fly, high-level refinement for particularly complex tasks is bound to fail, resulting in poor results and bugs.

Here's such a story from a few years ago. We were in the implementation phase of a feature that we'd promised to deliver within two weeks from the start. During a refinement phase, the feature didn't look too complex, but when I started to implement it, I saw that we'd had missed some complexity, and I needed to design the proper logic for it while coding.

The logic took longer to complete than I had expected, but I was happy—I had built a scalable solution to the problem in a short amount of time. To make up for lost time, I had to accelerate the rest of my work so that I could complete the full feature on time. How did I manage that? Well, I cut corners. I neglected automated testing, just pushing through and hoping that everything would operate correctly when I was done.

Well, I had overlooked several edge cases while developing the logic, so we had to go through two different feature testing cycles before we got it right, and we ended two weeks behind schedule. Inadequate refining was the root cause of all these cascading events.

Taking control of the tasks

From the developer's perspective, successful refinement should provide clarity about what needs to be developed so that during the implementation, the focus is on the details instead of the big picture. In my experience, being able to focus on one single well-defined task at a time gives me the ability to pay attention to details like code readability.

When a developer needs to clarify feature scope or requirements while writing their code, they need to shift their focus between a zoomed-in view of the details they're currently working on and the big picture overview. This is very taxing for the brain and it leads to a feeling of not being in control of one's own tasks. It's important to be able to determine where to focus during each part of the development process.

A well-executed refinement process has a lot of underlying focus points, and it requires all the team members contribute their expertise to the process. Without proper cooperation, it's not possible to get the best results. In my experience, following these principles during the refinement process, and involving developers in this process from the start, will help your team members each retain control over their tasks during the implementation phase.

1. Correct features create customer value

Capturing customer value is a key motivation for investing time and effort to develop a feature. If the same customer value can be captured better with another feature or a different approach, it may be worth altering the feature's requirements.

Since every team member perceives things from their own unique point of view, developers should be the ones to challenge decisions from a technical standpoint. To make this happen, developers should be involved in the refinement process as early as possible.

To be honest, this is one of the most difficult parts of the process for developers to master. It takes a lot of experience to see alternative technical possibilities and identify the pros and cons early on. It's much easier to note them retrospectively. But with practice, developers can provide a lot of added value early in the refinement process.

2. Reasonably sized features help with constant delivery

The size of a feature has a major impact on how well it captures value and how smoothly the development process runs. In my experience, if the feature is too big, refinement either takes a long time or is done at a high enough level that it cannot be executed successfully.

In general, a feature that takes up to a month to deliver by a single developer is able to capture value effectively. Development at the feature level therefore remains agile when features are small enough to be refined ahead of time.
The challenge about sizing a feature is that the connection between size and content isn't linear. Small parts of the feature can take a significant portion of the time to deliver.

I've faced this when developing an offer system for various types of products. Implementing the offer flow (input data to output data) takes around half the time, and configuring different products for it takes the other half. This means that delivering the first product is significantly more time-consuming than any following products.

There are a variety of ways to divide up the initial time-consuming work, and developers are the best people to recommend them. For example, the first feature could include only what is absolutely necessary by hard coding values, omitting validations, or deferring error handling until later.

3. Align the team with feature scoping and acceptance criteria

Scoping features and documenting the scope in acceptance criteria makes it easier for the entire team to understand the details of the expected delivery. The goal of this phase is to get everyone on the same page about what will be included and excluded from the feature, making it easier to decide whether to address any edge cases. The result for good acceptance criteria is a better focus on the correct details for the implementation and testing phase.

For example, in the previous offer system example, scoping would be crucial to ensure that we deliver only the parts that are needed for each product to work. Defining borders gives the developer a better understanding of where to focus on each step.

Scoping should be done collaboratively within the team, because each team member has expertise to offer about what is feasible to scope in and out. If developers haven't yet completely invested themselves in the feature, this is the step where all unknowns should be addressed.

Acceptance criteria are a method of documenting the feature scope by specifying the key requirements that must be completed before a feature is ready. These serve as a contract between team members to ensure that everyone is on the same page about the feature.

4. Split the feature into tasks that solve a single problem

Once we have a well-defined feature with strong acceptance criteria, we can divide it into smaller tasks that allow developers to work on a single problem at a time and enable parallel execution between developers.

In my experience, the more problems I try to handle at once, the more likely I am to make mistakes. Focusing on a single problem at a time improves the quality of my code. In our offer system example, a single task may be number formatter functionality. Separating this from other tasks would allow us to focus more on functionality outside of the happy path and specific use cases.

Splitting is more than just having a list of tasks to complete. Since the feature we're building is rarely identical to one we've built previously, there are also unanswered questions about how best to execute each part of it. Organizing new problems into individual tasks clarifies what needs to be done and allows us to plan for each problem.

A few years back, I had a feature that involved complicated form validation. Splitting the complex issues into separate tasks provided a specific problem to address, which I was able to work out with a small proof of concept during the refining stage. The implementation process became much more comfortable, because I already knew how things should be done and I could focus on details like how to make tests scalable.

Control leads to better code

There is a clear contrast between a good refinement process and a fast one. When tasks focus on a single problem at a time, and developers have a firm idea of how to address each problem, the implementation phase is completed in the developer's hands.

This also changes how developers are able to enjoy their work. Instead of finding their satisfaction in resolving ad-hoc challenges during implementation, when refinement is done correctly, developers feel good about being in control of the work and being able to focus on the specifics in each step. This may not create the same bursts of happiness that solving an unexpected problem does, but it does result in a more stable, positive environment during the whole development process.