Section 7.2. Smells in Deliverables


7.2. Smells in Deliverables

7.2.1. Fragile Code (Touching It Breaks It)

Symptoms

One of the first signs that might indicate software rot is when fixing one defect causes another. This is a sign of design debt that you have accrued because you are not refactoring the source code along the way.

Possible Process Innovations
Source Code Refactoring

Obviously, cleaning up the code will help prevent (or repair) software rot. When the design debt is large enough to cause fragile code, the bigger question is why the team is not refactoring with each change. The first thing to do is to ensure that the team has training and tools to support their refactoring efforts. In addition, refactoring has to be treated as a valuable activity that the engineers should undertake as a part of their regular development activities. Over time, engineers will begin to see refactoring as part of every task, and it will be included in the task estimates used in planning. Note that this means that engineers should not be pressured to give the lowest possible estimate because that will never include refactoring.

Automated Unit Tests

The existence of unit tests can help this situation in two ways. First, the presence of the tests gives engineers the confidence to make refactorings. As long as the tests pass, we can refactor without worrying about introducing defects. Second, as the percentage of our system that is covered by the tests increases, the probability that fixing one defect will cause another decreases. In many ways, the presence of the tests prevents software rot.

Open Workspace and Pair Programming

Sometimes incomplete understanding of exactly what the software is supposed to do is the cause of defects. Both of these techniques can promote communication and help detect these misunderstandings before they result in the creation of a defect.

Team Ownership of Code

As more people understand and work on each part of the system, the interactions within the software will be clearer, and the design will benefit from many perspectives. In addition, when the entire team works on all of the code, everyone's work is visible, and that motivates people to write clean code.

7.2.2. High Defect Rate

Symptoms

A high defect rate is clearly unacceptable. However, agile organizations have a very low tolerance for defects, so "high" is a relative term. In some organizations, the existence of even one defect is too high a defect rate. These organizations believe in "No Broken Windows."[3] James Q. Wilson and George Kelling developed the "Broken Windows Theory" in crime prevention. This theory stated that if a window was broken and not quickly repaired, it would embolden people to break more windows and escalate to more serious crimes. Rudolf Giuliani extended this in New York City. He believed that when people see the window in disrepair, they decide that the state of their neighborhood isn't important, and as a result, the state of the neighborhood degrades.

In The Pragmatic Programmer[1], broken windows are defined to be bad designs, wrong decisions, or poor code (not just customer-visible defects). Leaving even one broken window in your system gives the impression that quality doesn't matter, and the software begins to rot. No broken windows can be tolerated.

In fact, agile organizations have such a low number of defects that they do not require defect-tracking systems. Instead of tracking them, they just fix them. If you have more defects than you can repair in an iteration, you have a problem.

Possible Process Innovations
Pair Programming

One aspect of pair programming is that code review is occurring as the code is written. This is more effective (faster feedback) than traditional code reviews and can result in higher-quality code with fewer defects.

Automated Unit Tests

The unit tests not only help catch defects in the code being written but also ensure that previously written code has not been broken by the modifications. The result is a defense against the injection of new defects when trying to fix existing defects.

Source Code Refactoring

There is no better defense against defects than high-quality code. Because iterative development without refactoring raises design debt, be sure that everyone on the team is well versed in these techniques and that sufficient time is allotted to prevent software rot.

Code Presentations

If a small portion of your system contains a higher density of defects, a code presentation of it can help the team understand it better and develop strategies for improving its robustness.

Code Metrics

Source code refactoring and code presentations can be targeted to portions of the system that are likely to have defects by using standard code complexity metrics.

Test-driven Development

Taking testing one step further, test-driven development strengthens the design thought processes while resulting in fully tested code. It is not a simple strategy to learn, but it is very powerful.

Sprint Review

The knowledge that they will be demonstrating the functionality at the end of the iteration can motivate the team members to give priority to defect prevention.

Root Cause Analysis

If the team is uncertain about the source of the defects, root cause analysis can help you isolate the portion of your process where the defects are being injected.

7.2.3. Code Doesn't Match Design Document

Symptoms

In plan-driven development, the design document is supposed to provide a source of reference throughout the development of the system. However, as the implementation of the system progresses, the code usually diverges from the design in the design document. The result is a document that is no longer useful unless effort is expended to ensure that the changes made in the code are also made in the document.

One possible cause of this is that you are producing documentation that is not as valuable as the time it takes to create it. If that is the case, you should look at Section 7.1.3, "Unused Documentation."

Possible Process Innovations

The general strategy in these innovations is to replace traditional documentation with something that is closer to the code but that continues to provide the high-level view the documentation has traditionally provided. Putting the documentation closer to the code increases the likelihood that it will continue to match the system as it evolves.

To give these strategies some perspective, consider the argument Reeves makes in "What is Software Design?"[2]. He argues that ultimately, the source code is the design in that it is the instructions that we give to the compiler to tell it how to construct the system in the same way that bridge plans are the design that is given to the bridge builders to tell them how to construct the bridge. Although the code is sufficient documentation for the compiler, engineers generally need to see a higher level of abstraction to understand the high-level architecture of the system. The goal with these process innovations is to extract that higher-level understanding from something that is a part of the code.

Generation of Design from Code

If you think that your design documentation is valuable, but it ends up out-of-date anyway, the problem may be rooted in the ease (or lack thereof) of updating that documentation. If the documentation is "far" from the code (in a separate document and/or a very different format), it might be difficult for the engineers to translate the change they want to make in the code to the correct change in the documentation. In this case, embedding comments that allow the documentation to be generated from the code can replace structural documentation (which explains the classes and their methods).

Automated Unit Tests

You can put behavioral documentation "near" the code by building automated unit tests. These tests encode how the designer intended that code to be used. Therefore, the tests provide valuable insight when an engineer needs to write new code that interfaces with existing code. If she wonders, "How would I do X?" she finds the tests that encode that behavior and sees exactly the sequence of calls the designer expected her to use. Running these tests with regularity (possibly as a part of Continuous Integration) ensures that they are kept up-to-date with the code.

Test-driven Development

Extreme Programming has taken the concept of automated unit tests a step further to allow it to completely replace detailed design. XPers advocate writing the unit tests before you write the code. This makes you think about "How will the class be used" as opposed to "What does this class need to do," which can make your code much cleaner. In TDD, you write a test before you write the code that makes the test pass, and that ensures a very tight feedback loop during coding.

Closed Circuit Engineering

Even when engineers value the documentation, it is often difficult to update. Often, change control is more limiting on documentation than it is for code, and the tools that are used to update the design can be cumbersome. These can be hindrances to updating the document.

Many CASE tools support the ability to generate UML models from code and vice versa. If you follow the coding standard that allows this, it can be an effective way to update the design model. This can be automated to occur at regular intervals, which can alleviate the need for each engineer to work with the CASE tool, thereby eliminating change control and learning curve concerns.

Code Presentations, Open Workspace, and Pair Programming

Finally, the best way to promote communication among developers is an open workspace. If you believe that the code developed doesn't match the design because developers are not aware of what everyone is doing to the system, this is a great strategy to try.

7.2.4. Feature Creep or Feature Sleep

Symptoms

There is no bigger waste than spending an iteration building a feature that does not match the customer's expectations. If the team builds what the customer requested and then the customer changes his mind, it's not a problem. However, if the customer doesn't get what he asked for in the first place, essentially no progress has been made. Similarly, it is important that we meet as many of the customer's expectations as possible. Delivering exactly what was expected (no more and no less) builds a relationship of trust with the customer that will keep the customer satisfied in the long term.

Possible Process Innovations
Planning Game

The planning game is designed to promote communication between the team and the customer and to flesh out the details of the functionality to be included in the upcoming iteration. If you are using the planning game and still do not develop functionality that the customer is expecting, add more detail to the stories. You may need to go as far as drawing pictures or diagrams of what the customer will see.

Sprint Planning Meetings

Like the planning game, the first portion of the sprint planning meeting contains a discussion with the customer about the functionality to be included in the iteration. Again, adding details to the backlog items can clarify the customer's requirements. However, do not spend time adding details to items that are low in the backlog list because they are likely to change before they come up for implementation.

Onsite Customer or Customer Surrogate

Agile planning attempts to rapidly flesh out the details of a feature. However, as development of the feature progresses, the developers often have questions about exactly what the customer wants. Having an onsite customer allows the developer to get answers to those questions quickly so that development can continue. If an onsite customer is not a viable option, designating one personoften the product ownerto answer those questions can work well. This customer surrogate must be responsible for reflecting the customer's needs and should interact regularly with the customer about these issues.

Set Based Decision Making

Often during planning, there is some disagreement about the best way to provide some required functionality. In these cases, it is wise to allow the team to start implementation on a few promising alternatives to flesh out their details. This can help the customer see the alternatives and often results in an even better solution that takes the strengths of each of the alternatives.