This article is adapted from a team presentation I delivered at a previous job, with specific business details generalized for broader relevance. The goal of the presentation was to introduce the team to the concepts of Continuous Delivery and breaking up work effectively.
While the definition of Agile Software Development and Continuous Delivery and its evidence-based benefits have already been explained by a multitude of books, presentations and websites, this article will focus on some technical and procedural enablers of incremental software delivery.
The value of incremental delivery
Delivering code in an incremental way is beneficial for the end users of the software, the business, and the team that builds the product. It leads to faster time to market, whereby users of the product benefit earlier from improvements in the software, and improved learning & feedback, whereby teams can adjust their plans based on what worked and what did not, saving time and effort. Related to the previous point, an incremental approach reduces the cost of mistakes, either technical or product-based. Just imagine the costs of working for months on a feature that no one wanted or the consequences of a nasty bug when rolling out new code in a big-bang release.
How to release software in increments?
To effectively reap the benefits of incremental delivery, it’s essential to understand that not all increments are created equal in terms of the value they add.
Some effective prioritization techniques are:
Prioritization based on business value involves dissecting a feature into smaller parts and delivering the ones that provide the most immediate benefit to users or the business. It’s a pragmatic way of ensuring that non-essential parts of a feature do not add unnecessary complexity and delays.
Prioritization based on read/write concerns involves distinguishing between parts of the feature that deal with data retrieval (read) and those that involve data modification or entry (write). By tackling these separately, teams can focus on delivering a coherent user experience in stages, ensuring each aspect functions optimally before moving on to the next.
Prioritization by functional and non-functional requirements may also be a good strategy. Functional requirements are the must-have features that fulfill the core purpose of the software, while non-functional requirements are about how the system operates, such as its reliability, scalability, and security. Addressing these separately allows teams to focus on delivering core functionality first, followed by enhancements that improve the overall quality and user experience of the product.
However, not all methods of breaking down work are advisable. One common pitfall is building the data model and persistence layer first, without aligning it with user-facing features. This can create a misalignment with actual user needs and business objectives, leading to wasted time and resources. A better strategy is a user-centered approach that integrates back-end and front-end development, ensuring that each increment is not only technically sound but also delivers real value to the end user.
Example: An e-commerce application
Imagine a team working on an e-commerce application. The feature in question is to enhance the user experience by implementing a personalized product recommendation system.
In the first iteration, the team rolls out a basic version of the recommendation system using an algorithm based on purchase history. This initial, unsophisticated release quickly provides value to users by showing relevant products, and allows the team to measure if the likelihood of new purchases increases (prioritization by business value).
The next increment may focus on the ‘write’ aspect, where the system starts to incorporate user feedback into the recommendation algorithm. Users can now like or dislike products, and these inputs are used to refine future recommendations (separation by read/write concerns).
Finally, once the recommendation system works correctly and delivers value, the team moves on to non-functional aspects like optimizing the system’s response time and ensuring scalability. This ensures that as the user base grows, the system remains responsive and reliable.
Technical Enablers for Incremental Software Delivery
Incremental software release isn’t just a matter of process and planning; it also depends on certain technical practices and tools that facilitate this approach.
Feature Flags: Feature flags allow developers to merge code into the main branch without releasing it to all users immediately. This means changes can be tested in production, rolled out to a subset of users, and easily turned off if issues arise. This flexibility is crucial for testing new features in real-world conditions without disrupting the entire user base.
Atomic Commits and Stacked Diffs: The concept of atomic commits—making each commit a self-contained change with a single focus—enhances clarity and reversibility. Stacked diffs, where each set of changes builds upon the previous one, further support this by allowing for incremental changes to be reviewed and integrated systematically. This approach makes it easier to manage and track changes, especially in large and complex projects.
Trunk Based Development (TBD): Trunk Based Development is a key practice in supporting incremental releases. In TBD, all developers work on a single branch (the ‘trunk’), with very short-lived or no feature branches. This approach minimizes the complexity of merging and integrating changes, encouraging frequent and small updates to the codebase. By consistently integrating small changes into the trunk, the software remains in a state where it can be released at any time, facilitating a quick release cycle.
CI/CD Pipelines: Continuous Integration and Continuous Delivery (CI/CD) pipelines automate the process of testing, compiling and deploying software, ensuring that new code changes can be released reliably. A well-designed CI/CD pipeline reduces manual errors, speeds up the release process, and ensures that the software is always in a releasable state.
Spikes: In agile software development, a spike is some throwaway code used to explore the feasibility of a technical approach in a quick and low-risk way. Spikes are often used to better understand a requirement, improve estimations or experiment with new technologies in a controlled manner.
Pair/Mob Programming: In the context of incremental releases, this help in quickly identifying and resolving issues, ensuring that each increment is robust and well-crafted. It also helps to bring the team together towards one single goal, preventing the loss of focus caused by multitasking and context switches.
Testing: Testing provides the safety net required to release increments with confidence. Used in combination with a reliable CI/CD pipeline, automated tests at various levels (unit, integration, system, etc.) ensure that new changes do not break existing functionality.
Canary Deployments and Blue-Green Deployments: These are advanced techniques, as they may require significant investment at the infrastructure and monitoring level. In essence, they are used to roll out a new version of the application to a small subset of users before making it available to everybody. Both approaches allow teams to test the impact of changes in a production environment with minimal risk.
A final note about company culture
The successful implementation of incremental software delivery techniques is deeply influenced by a company’s culture.
In organizations where there’s a strong aversion to risk, adopting agile and incremental methodologies can be challenging due to a fear of failure or a lack of knowledge about what’s possible. To overcome this, educating leaders about the benefits of agile development and building up trust through small, controlled experiments is crucial.
For example, companies that wish to control their release schedule, due to industry regulations or any other kind of policy can blend agile methodologies with traditional release practices using feature flags.