Lean thinking comes from innovation in statistical process control. The idea is that honing techniques leads to measurably better results. Lean identifies eight “wastes”, activities that subtract value from a process. They are:
- wasted talent
- wasted inventory
- wasted motion of people
- wasted time (waiting)
- wasted transportation of goods
- defects
- overproduction
- overprocessing
From the perspective of a software delivery project, some of these wastes don’t seem to apply. In this series of posts let’s focus on each in turn, starting with waiting and defects.
Waiting and Defects
I hate waiting.
— Inigo Montoya, The Princess Bride
When people think of lean processes, the waste of waiting is usually what they have in mind. In software delivery processes, it can manifest in a number of forms. In software delivery, it turns out that techniques that reduce waiting can also reduce defects. Defects occur when software doesn’t behave as intended.
When a delivery team member joins the team, inefficiencies in the onboarding process can introduce waste. If they are also joining the firm, the costs steepen. Every moment that the new person spends not adding value to the product is waste from this perspective. Thus, it’s important for the new person to be integrated into the team as quickly as possible. I recommend paired programming to minimize this kind of waste. Shadow a person who’s role you’ll be filling. See what they do and don’t do, and learn the new codebase like you would a foreign language, through immersion.
Okay, so all of the team is onboard. The next step is to look at your product delivery process for bottlenecks. In a lean process context, a bottleneck is a part of the process where flow is constricted. While there may be a number of inefficiencies in a system, the most severe bottleneck will constrict the entire system. Troubleshooting bottlenecks is beyond the scope of this post, but one of my favorite guides to the process is the Universal Troubleshooter’s Guide.
One of the many examples given is one of a bicycle, with the goal of improving your commute time of two hours. If you have a $75 coaster break bike, what keeps you from going faster — the bottleneck of the system — is having just one gear, which is poorly suited to hills.
For $150, you might get a mountain bike with multiple speeds that gives you a 20% faster commute. Having removed the first bottleneck, the next limitation is wind buffeting from an inferior riding posture. A road bike for $300 can solve that bottleneck, which offers specialized tires and a better riding posture, yielding a 17% commute time improvement.
However, there is a point of diminishing returns. The next step up would be a mid-grade road bike, which overcomes the limitations of the entry-level components, but it only offers a 5% improvement for double the cost at $600. Other approaches like training or riding clothes would likely yield better results, which implies that at this point bike components aren’t the bottleneck.
With the bike example in mind, think about your software delivery process. From idea to requirements to design to implementation to testing to customer validation, where is the point that work tends to pile up? That point is likely to be the bottleneck. Lean process has a lot of good guidance on finding and removing bottlenecks, and it’s worth exploring even if you are using another agile methodology like scrum. Once you identify the bottleneck, there are techniques that can increase delivery speed at each stage:
Stage | Techniques |
---|---|
Idea to requirements | Backlog grooming, domain-specific language (DSL), working agreements |
Requirements to design | Prototyping, behavior-driven development (BDD), domain-driven design (DDD), unified modeling language (UML) |
Design to implementation | Paired programming, continuous integration (CI), version-control system (VCS), test-first development (TDD) |
Implementation to delivery | Automated acceptance testing, continuous deployment |
Notes: “Paired programming” is usually “pair programming” though I prefer “paired” because paired is a better adjectival descriptive of the activity than the verb “to pair”. Similarly, TDD is usually “test-driven” but I prefer “test-first” because it more accurately sums up the philosophy of writing software guided by tests.
Backlog grooming allows a product owner to engage a delivery team during story creation. These sessions offer teams a separation between communicating what the work is and deciding how to accomplish the work. Product owner can use the feedback on the newly-drafted stories to refine the stories, address the team’s questions and concerns, and use the team’s rough size estimates for release planning. Without backlog grooming, these “what” and “how” discussions both happen during iteration planning. I’ve found mixing the two discussions to be inefficient, because the variations in design and other aspects of the “how” are much larger if team members have different concepts of what’s being requested, and thus it takes longer to reach concensus.
Product owners and teams need a common trade language to bridge the gap between business requirements and technical requirements, and the best tool I’ve found for this is behavior-driven development or BDD. I prefer Cucumber as a framework, which forms a domain-specific language (DSL) or jargon for communicating requirements. Each feature and requirement are expressed in a certain format, which programmers can easily map to automated tests.
This idea of a DSL can be extended further to the problem domain, which brings up a number of concepts from domain-driven design, especially ubiquitous language. It’s very helpful if the business experts use terms the same way as the technical staff. It helps them both understand the problem space better. While it’s true that domain-driven design includes a number of tactical technical patterns, it also contains a number of strategic approaches for modeling domains that are of use to both business experts and delivery technicians.
The same holds true of technical diagrams. Since there are so many ways to visually communicate information, it can take time and effort to communicate how symbols are being used. Use of a common standard like the unified modeling language (UML) can alleviate this. For people who know the language, the individual symbols fade into the background and the focus because the message they intend to convey.
Just as a ubiquitous language and UML can help smooth communications, working agreements can help teams make decisions ahead of time. Common working agreements include a definition of done and a definition of ready. For example, as part of a definition of done, if all team members accept that a story isn’t ready to code until the acceptance criteria are defined — the things the product owner expects to be true when the story is ready for acceptance — then that’s one decision that doesn’t need to be made with every story. The conversation shifts from “hey, do we need acceptance criteria on this one?” to “how will we show you that we’re done with this story”. Rehashing decisions over and over is a form of waste. Teams should only revisit these agreements once in a great while or when something changes.
Prototypes are a 21st Century version of the saying, “a picture is worth a thousand words”. When exploring new applications, new technical infrastructure or new approaches, prototypes can be very useful. They offer an environment for engineers to learn how best to leverage technologies to achieve a goal. Prototypes can also be useful to business experts, who can get some tactile feedback on their proposed workflows. I know teams that use prototypes even for established applications to verify their understanding of the requirements.
When working in a language that is as abstract as a computer programming language, even the most expressive code can be challenging. Paired programming is the most direct way for one delivery team member to communicate low-level technical implementation details with another one. Although it can be slower to implement a feature using paired programming than with a solitary programmer, studies have shown that the code produced by a pair contains fewer defects and that the majority of defects are found during code reviews. For a pair of programmers, code reviews occur naturally and continuously. Because mistakes are least expensive to fix while the code is being written, the overall time to produce quality code is reduced by paired programming.
Sitting next to another developer isn’t a luxury everyone can enjoy. I know a number of developers who pair remotely using a virtual network computing (VNC) tool like WinVNC and a telephone or get a second opinion with a screen-sharing tool like join.me or Skype. Many developers don’t see it paired programming as a luxury, but as a burden. For those uncomfortable with the concept, a weaker alternative that provides many of the benefits is an interactive code review in a conference room or via software as above. For distributed teams, or teams in a meeting-heavy work environment, a source control tool like Git with support for pull requests can provide a forum for asynchronous code reviews.
Continuous integration (CI) describes a practical approach of coordinating the work of multiple developers through the use of a common code location. It’s advantageous when that code location can track changes, which is why most developers make use of a version control system (VCS) like Subversion or Git. Originally made to help coders collaborate, use of VCS has expanded to book authors and even lawmakers.
While a VCS can help contributors understand the evolution of a work product, it doesn’t include the ability to know whether the work product changes are productive. A CI tool like Jenkins or TeamCity automates the process of watching for changes and taking action, usually building the software and running automated tests. However, a CI server can be used for other tasks, like compiling code quality reports and building documentation as well.
One of the more popular uses is for continuous deployment, in which build artifacts that pass automated tests are automatically deployed to a test environment for manual inspection. Or they set up deployment at the push of a button, so that long-running tests won’t be disturbed by an untimely deployment. And, in a world where it’s easy to deploy changes or one where there’s a ready failover environment, companies can simply automatically deploy to production, knowing that they can easily revert if need be.
Testing and Defects Reduction
Testing does not improve speed to market, a key reason why delivery teams sometimes sacrifice testing. In most cases, though, testing is an important discipline for agile teams, because quality code will improve chances for longevity in the market. Here, we are talking about two primary kinds of testing: unit testing and automated acceptance testing. Both kinds of testing are about delivering the right thing to customer, not about delivering it faster.
Test-first development is a tactical software development approach that ensures the programmer is the first user of the code being written. Each unit of code is exercised by I find that it quickly exposes potential design issues early, especially when it comes to consuming objects.
In writing these “unit” tests, I sometimes find myself writing boilerplate code or having to always use certain objects in conjunction, both of which can indicate beneficial refactoring. I sometimes find that I’m contemplating using an object in a new or different way, which can indicate a design flaw. I let my intuition be my guide — there is a difference between not writing tests because they are uninteresting (property getters and setters come to mind) and not writing tests because they are hard to orchestrate. I aim for 80% code coverage by tests, which gives me the benefits of TDD without imposing a dogmatic requirement to write uninteresting tests.
Acceptance testing is closely related to unit tests, but for many, it’s a separate discipline. Not only do developers want to know that their new code is working correctly, they want to make sure it integrates with existing code or systems. These tests have a different audience, so they should be considered separately. Whole books have been written on this subject, please see them if you want strategies for agile testing, but it’s worth noting that troubles found during deployment or integration are still less expensive to fix than after shipment to the customer. Accordingly, it’s worth some effort to reduce defect escape rates with acceptance testing. For people who practice BDD, their Cucumber specifications can easily be implemented as acceptance tests.
While tools like FitNesse and Cucumber can help with acceptance testing, a CI tool can be used to run them automatically. This allows software development teams to develop a “car dashboard” of their code health, letting them know whether it’s safe to proceed or time to pull into a service station. Many teams create working agreements for how to handle failing builds or broken automated tests.
Sometimes defects occur because a piece of software is working but is hard to use. User experience can help alleviate common data entry mistakes like clicking on the wrong button when two are placed too close together, use of confusing labels, obscured functionality hidden in configuration menus and the like.
Next installment, we’ll examine wasted talent.