Software development teams are often faced with requirements to add new features to an existing code base. For a number of reasons, this sort of work can entail a lot of risk. Customers for such requirements often paint only a broad outline of what they desire, leaving the team to work out the implementation details. People on the team may have different interpretations of what is required. And breaking down the aspects of the feature into pieces that can be coded and tested efficiently often poses a significant challenge. As testers and quality assurance professionals, it is important to be aware of common mistakes in managing the process. By looking at requirements from the perspective of all team members and considering the design, these mistakes can be avoided.
Breaking it down with the customer As an example, consider a courseware application that manages the curriculum and attendance for college classes. The customer wants to add a feature such that instructors' pay will be calculated based on their attendance:
- in each class
- in each week
- in each semester
- for each course
If an instructor does not attend a class, the instructor is not paid for that class. If an instructor substitutes for another instructor, then the substitute is paid for the class and the original instructor is not.
From the customer's point of view, the feature presents an apparently simple situation: the attendance data already exists, the records for who substitutes for whom already exist; all that seems to remain is to assign a rate of pay, and the calculations should become a fairly simple matter.
But because of the way software is designed, there are aspects of the new feature of which the customer is not aware. In order to answer the requirement, the development team needs to explore with the customer the issues that are not apparent. For example, instructors may be enrolled in classes in capacities other than teaching. Besides being a teacher, an instructor may also be an auditor or even a student. These modes of attendance should not be paid. Different sections may have different rates of pay, so instructors may be paid more for some classes than for others. Some semesters may last for different lengths. Summer semesters typically have fewer weeks than spring and fall semesters, but instructors should be paid the same amount for teaching the same class regardless of the semester in which the class occurs.
The critical abilities of software testers are helpful when working out the conceptual details of a new feature. Software testers have the habit of thinking about edge cases, unexamined assumptions, and unanticipated consequences of particular aspects of the software.
Breaking it down with the software development team
There is a classic approach to requirements management in software known as "functional decomposition." In the early days of software development, before the widespread adoption of object-oriented languages and architectures, functional decomposition was considered a best practice. The idea of functional decomposition is to take an overarching business requirement like "implement pay for attendance" and to break down the large-scale feature into functional areas, and then to break down those areas into actions, or functions, that may then be implemented in code. The classic illustration of functional decomposition of requirements is a flowchart, where certain actions and decisions lead to particular states. Good object-oriented design does not implement functions, but instead shares information among classes over interfaces.
In software development teams in which business analysis activities take place in isolation from the rest of the team, this approach to requirements management is unfortunately still very much alive. To those without access to the code base, or without a background in object-oriented design, it seems logical to describe a complex business requirement in terms of a flowchart. In functional decomposition, functions result in particular states, and those states engender particular consequences in the data, with the result that instructors get paid. And in fact, functional decomposition may not be an anti-pattern when applied to programming done in a procedural style. But while procedural coding is increasingly rare, functional decomposition lingers on, creating confusion and causing poor code in object-oriented code bases. As Ben Kovitz notes on the Functional Decomposition page of the c2 wiki, "The big danger of Functional Decomposition is the belief that it is effective even for planning the design of things that you do not understand well enough to be sure of a given decomposition."
Breaking it down in the code base
Functional decomposition of requirements results in confusion, poor code, and poor results in an object-oriented code base. For this reason, it is critical that those who understand the architecture of the existing code base be a part of the requirements breakdown activities. Those people on the team may or may not have the word "architect" in their job title. A senior programmer might be a good choice, or a subset of the developers who work in various different parts of the code base, or possibly a tester who has a clear grasp of the whole system. Regardless of title, it is critically important to have technical staff on hand to help break down requirements into pieces that may be coded and tested efficiently.
Avoiding a breakdown
The design of an object-oriented code base should minimize two aspects of code architecture: duplication of code; and significant dependencies among disparate areas in the code base, known as "tight coupling." These aspects of poor design are called "cross-cutting concerns," because they represent functions in the system that are important in multiple code areas of the system. Functional decomposition of requirements lends itself to creating cross-cutting concerns, but good object-oriented design minimizes cross-cutting concerns. Breaking down big requirements into pieces that map closely to the underlying object architecture of the system reduces cross-cutting concerns, and has three other important advantages.
First, writing the code for such pieces minimizes conflicts as programmers create the pieces. Because the pieces are aspects of isolated areas of the object model, cross-cutting concerns are minimized. The constituent pieces of the overarching requirement may be implemented on their own schedules with minimal disruption to other pieces of the overarching requirement.
Second, interactions between the pieces may be accomplished with well-designed interfaces, as opposed to the "spaghetti code" that typically results when functional decomposition of requirements is applied to object-oriented code.
Finally, and not least important, requirements broken down into areas that map to the architecture of the underlying system provide a basis for good testing. Because the scope of each requirements piece is relegated to a particular area of the architecture, charters for testing of individual aspects of the overarching requirement tend to be clear and well defined.
Interfaces between the code areas also tend to be well-defined. Interfaces between subsystems, even well-designed interfaces, are an area of increased risk, because the subsystems on each side of the interface must share an understanding of the data they exchange. Given well-designed interfaces, testers have a clear sense of where the areas of most risk in the application will be concentrated.
Ultimately this is an argument for a whole-team approach to managing requirements, particularly for large features. When the customer, the business analysts, the architects, the developers and the testers are all engaged in the process of defining requirements, the team will minimize risk and maximize the value to the customer.