Small Development Task
Product Backlog Items, even when sized to fit in a development iteration might still be too large to let you maintain the integration cadence you want. You want to integrate each Task Branch quickly. This pattern describes a way to approach splitting a backlog item into development tasks to keep the work on the task branch short and code robust?
Working in Increments Can Be Hard
You want to integrate changes frequently to maintain a healthy Main Line. While the definition of “frequently” can vary by team and context, for many teams, it means “at least once a day.”
A Product Backlog Item could be small enough to be completed within your desired integration time, but in many cases, a PBI will involve more work than that.
It can sometimes be difficult to identify units of work related to a backlog item that reflect useful, “ready to merge” functionality:
- Smaller units of work may not reflect complete functionality but are quicker to review and integrate.
- A larger task might provide some end-user value but will take longer to complete and review.
- You want to integrate consistent units of work that won’t break existing functionality or reveal partial features unexpectedly.
Even when teams are cross-functional and include “full stack” developers, some stories might require work from multiple team members. For example, a story with mobile, web, and backend components might involve three developers. Coupling development tasks adds schedule and integration risk:
- work that might be parallel becomes serial
- misunderstandings aren’t found until a downstream group integrates with an upstream part, leading to rework. While you may still discover issues during integration, you can find them sooner if work can be done in parallel with some understanding of assumptions.
Integrating a partial implementation of a Product Backlog Item can enable integration and collaboration between developers working on the Product Backlog Items. Decoupling tasks to enable parallel work can involve disposable scaffolding, which can seem like waste.
Smaller, frequent merges help to avoid integration surprises, but each merge to the Main Line adds overhead, particularly for Code Review. When tasks are coupled, each task adds coordination overhead. When tasks are larger, dependencies can slow development.
A merge carries some overhead, though larger merges have more overhead than smaller ones.
When you develop using Test-Driven Development and/or Unit Testing, the tests typically cover small units of work, even if the units don’t represent a complete feature.
You want to balance delivery speed with process overhead, giving you the ability to deliver working software at high frequency.
Small Pieces Loosely Joined
** Split Each Product Backlog Item into Small Development Tasks that can be completed and integrated in a day or less. Design to allow for incremental integration and/or feature hiding.**
Small Development Tasks are coherent units of work that can be completed quickly and merged into the Main Line without breaking anything. A Small Development Task is usually associated with a Task Branch. If a Product Backlog Item cannot be completed by a developer in the desired integration time, plan to break it into a set of tasks that:
- Are completable in a day or less by a developer (or pair)
- Have associated Unit Tests.
- Can be integrated into the Main Line without breaking existing functionality or creating risk.
Working in small steps can improve quality {Broza, 2024 #243160}:
Small, safe steps. Proceed in tiny steps that are easy to test and undo. This principle takes the ideas of breaking work down, making reversible decisions, and getting rapid feedback to a micro level. It allows you to quickly realize what doesn’t work and to then change direction. Test-driven development implements this principle too.
The team should plan these tasks in a way that facilitates collaboration. While, Ideally, Product Backlog Items are independent, Small Development Tasks may have sequence dependencies. Strive to develop mechanisms such as contract tests and interface definitions that allow others to move forward while a task is in progress.
By breaking down work into smaller parts, you can {Berczuk #158708}:
- Evaluate your progress in a definitive way. as it is often easier to define “done” for a smaller task.
- Get feedback from your colleagues before you dive into a problem.
- Share effort if any of the work can be done in parallel.
- Simplify updates and merges, as the changes to the Codeline will all be small at any time.
Additionally, since smaller tasks are easier to estimate and track, you can:
- Improve the reliability of your iteration commitment
- Identify, daily, if your goal is at risk, and act accordingly (change scope, gather help, update expectations)
The act of trying to break backlog items into Small Development Tasks can also validate your engineering approach {Sutherland and Coplien, 2019 #38181}:
Keep in mind that the main purpose of bringing the team together to refine the backlog may be to get the team to start thinking about how to implement the envisioned features. Refinement is part of a learning process.
Planning for Small Development Tasks means that your team needs to do some basic design work during planning and identify intermediate goals, which will provide more confidence in their ability to meet goals.
Task Breakdown Approaches
Breaking down work need not be complex, though doing it well requires some design thought. Some natural boundaries in the development process can help you break down tasks:
- Consider what tests you write as (or before) you code. Each passing unit test could be a commit point, and the complete test suite could be an integration point.
- Decide what you want to accomplish before leaving work each day so that you end the day with a sense of accomplishment.
- Identify interfaces that you could stub out to get to a commit point more quickly; the first task might be to create the implementation skeleton, and filling in each stub with a real implementation could be an additional task.
For example, consider an app that has mobile, web, and backend components. You could:
- Build web and mobile interfaces using stubbed data that meets the contract with the backend (this tests the utility of the data and user interfaces)
- Implement a stubbed endpoint that delivers stub data (perhaps with some variation). This lets clients test connectivity and interactions. Clients can then verify their integration with simple end-to-end tests.
- Build the real backend endpoint that includes, for example, database changes. At this point, you will have something that you can demonstrate to a stakeholder with realistic functionality,
Not all post-merge integrations will go smoothly, especially if the intermediate tests were incomplete, but you can work to improve on this.
When you have a small task that implements only part of a feature, you want to be mindful that the work does not interfere with the running system. There are (at least) 3 basic strategies to approach this:
- “Private Access” or “Bottom Up”: Write code that is not accessible to external users of the system. For example, add a service layer that you can unit test or access by upstream clients but don’t expose to web handlers and other external interfaces. This makes more sense when adding new code than when changing existing code.
- Hide the code: Using a Feature Flag or similar mechanism so that the new code paths are accessible to code running with the proper configuration. Similarly, you can use Versioned Interfaces or endpoints so that the code is only available to upper layers in a development configuration.
Add test cases that ensure that code is not accessible from an external interface unless the correct flags are set. Be sure to apply appropriate security approaches to ensure that partial implementations do not expose vulnerabilities.
Cautions
- While smaller, independent tasks are good, be mindful of tasks so small as to cause churn.
- In some cases, it’s better to start building than over-designing interfaces upfront.
- Delivering in increments of Small Development Tasks might seem slower at the start until the team develops a new mindset and testing and integration frameworks to support this approach.
- While a “Task” is often associated with an item in an issue tracking system, it need not be. It can be a checklist on a whiteboard or similar. The goal of the story is to decompose work into small commits, not to add process overhead.
Caveats
There is a perspective that one should prefer small “tracer bullet” stories that cut through the system over tasks {Cohn, 2006 #222473}:
Don’t split a large story into tasks. Instead, try to find a way to fire a tracer bullet through the story.
If you can find an end-to-end story that can be completed in a short period of time, this is ideal, though when you have different people working at different parts of the stack, you may still have different code commits, perhaps touching different repositories.
Next Steps
- Defining what’s done: Unit Test
- Limiting the visibility of work that isn’t ready for users: Feature Flag