Bootstrap

Agile Practices for Software Success

Agile Practices for Software Success
Photo by Pixabay from Pexels

In this post, I'll share a few ideas and practices I've come to rely on while improving my agility as a software developer and becoming a more effective teammate and leader within agile teams.

Increments, Iterations, and Fidelity

The agile principles promote processes prioritizing early and continuous software delivery in the face of changing and often ambiguous customer requirements.

This is typically achieved through an iterative and incremental approach that enables the team to refine and evolve the software over time as they receive feedback or changes in requirements to meet customer needs.

Iterative development means that software is developed and released in phases or iterations.

Incremental development means that software is developed in slices or individual subsets of functionality called increments.

Iterative and Incremental Development

Increments and iterations naturally complement one another to help achieve the right level of fidelity in the software. Fidelity refers to the level of precision and detail of the software, meaning a high-fidelity solution has a very polished and refined design, functionality, and user experience.

Agile Fidelity
Agile Fidelity

We're making a mockery of the Mona Lisa here. But keeping with this example, we can see how incremental and iterative development results in a product with varying degrees of fidelity. If necessary, the low-fidelity parts can be enhanced in future iterations, and new features (increments) can be added with low-fidelity and refined the same way over time.

From Karl Scotland's great post on agile fidelity:

An Agile approach combines the incremental and iterative approach by building each feature, one by one, at a low fidelity, and then both gradually adding features and increasing their fidelity until the right combination is achieved. Full fidelity is not always necessary.

Thinking in increments and iterations and the level of fidelity we're trying to achieve can enable more flexible technical designs by forcing us to consider early, low-fidelity requirements and their long-term, more refined versions. This can help reveal the proper abstractions, decoupling points, and critical modules early in development.

Finally, fidelity does not reflect the value of a given feature or function within the product. As Karl Scotland mentions, "Full fidelity is not always necessary.".

Get notified on new posts

Straight from me, no spam, no bullshit. Frequent, helpful, email-only content.

Ship Thin, Integrated Slices of Functionality

Commonly referred to as "vertical slicing", this technique is very effective when correctly applied and can significantly increase the velocity, frequency, and quality of your software delivery.

The idea is to build and launch thin vertical slices of product functionality that are fully integrated across application or system layers. The key here is providing just enough UI, business logic, database, or API integration to deliver runnable software from end to end.

This concept can naturally influence your architectural approach with ideas like Vertical Slice Architecture that move away from traditional, horizontally-layered architectures like clean, onion, etc., in favor of a more tailored approach that considers each distinct request or use case (slice) of the system and groups all the concerns for it from front-end to backend.

Some benefits of this approach:

⏲️ Faster Delivery— Thin and limited functional slices are much quicker to build and release than larger, more fully featured pieces of software. This means working software can be delivered in weeks instead of months.

💎 Value-Focused - Slices typically represent high-value features and functionality, ensuring the team is focused on the right priorities to maximize product or customer value.

📣 Early and Continuous Feedback - Faster development and release cycles mean functional slices can be validated by QA, product, and users early and often. This helps keep defects under control and allows the team to adapt the software as features are validated continuously.

🔌 Fewer Integration Pains and Risks - As slices are incrementally added and integrated from end-to-end with things like databases, external APIs, and the user interface, issues and constraints surface early on, and optimal integrations can be factored into the design as the software evolves. This is much less risky and cheaper to address than a "wait and see" approach where major components are hooked up later or at the end 😱 of the project. This usually leads to a considerable amount of painful work for the team to refactor existing code and the assumptions about how components would interact. It also introduces more technical debt and sub-optimal code while the team scrambles to get things connected by any means necessary.

Keep Things Simple

The KISS principle reminds us that most systems work best if kept simple and to avoid unnecessary complexity by making simplicity a key goal of our software design. Similar to themes found in the previous points, this helps emphasize doing only what is necessary and keeps the team focused on delivering the highest-priority features and business value.

Sure, it's a lot more interesting to build part of your backend using some fancy distributed microservices, but if a monolith API will fulfill your requirements now and for the foreseeable future of the product, this approach is much faster and cheaper to implement upfront and maintain over the long run.

Maintenance and extensibility are important reasons to strive for simplicity. Once a project hits a certain maturity point (i.e., beta release or initial user testing), the team spends far more time reading and analyzing the code than they do writing new swaths of it. Code that is overly complex and over-engineered designs are complicated for other developers to understand, fix, and extend properly. This makes changes to fix bugs and add new features risky and time-consuming.

Simplicity: the art of maximizing the amount of work not done - is essential

Automation

Automation improves efficiency and quality by removing inconsistencies, errors, and delays that inevitably occur when we repeatedly do things by hand.

🛠️ Build and Release Pipelines - It should go without saying, but the ability to build and distribute your software automatically as new work is added is a fundamental part of any successful project. There's no shortage of quality cloud CI/CD platforms like GitLab, Azure DevOps, CircleCI, and GitHub Actions (which this site was deployed with) that can help you get off and running on your automation journey. Before the advent of many of those fancy cloud-based services, many on-prem CI/CD solutions were built with Jenkins.

🌩️🖥️ Infrastructure - Managing cloud infrastructure can be automated on providers like AWS, Azure, and GCP through an Infrastructure as Code (IaC) process typically using Terraform (alternatively OpenTofu — an open-source fork of Terraform). IaC allows your infrastructure to be deployed repeatedly and reliably by applying configuration code controlled through a repository and familiar development workflow similar to your regular application code.

Keep Things Running

Contrary to traditional project management metrics and the obsessive monitoring of burndown charts (standard in many "agile" projects), the true measure of progress is the team's continuous delivery of functional software.

We should maintain stable builds and deployments to ensure the team and software advance together. It enables us to react faster to requirement changes and user feedback while continuously improving the software and delivering tangible value.

Concepts like vertical feature slicing, simplicity/focus on core business value, and an adequate level of automation can improve your chances of maintaining working software as the project grows in size and complexity and under typical scheduling and resource pressure.

Working software is the primary measure of progress

Communication

This is a friendly reminder from the Agile Manifesto that face-to-face communication (or camera-to-camera) is the most effective method of sharing information within the team.

Clean and expressive code is vital for developers to understand regions of the codebase they didn't author. In most cases, the ratio of time developers spend reading code versus writing it is well over 10 to 1. Complex spaghetti code greatly hinders agility and a developer's ability to efficiently add new features or fix bugs in foreign areas of the project.

An adequate level of documentation also improves agility. Technical design documentation provides a reference for other developers to understand the reasoning behind critical design decisions and their technical underpinnings. Well-maintained onboarding documentation and project readmes enable newcomers to get on board quickly and provide a positive first impression of the project.

Get notified on new posts

Straight from me, no spam, no bullshit. Frequent, helpful, email-only content.

Get notified on new posts
X

Straight from me, no spam, no bullshit. Frequent, helpful, email-only content.