Experienced project managers and developers understand the value of translating software requirements into robust designs and rational project plans. These steps are necessary whether the next release represents one percent or one hundred percent of the final product. This article explores some approaches for bridging the gap between requirements development and a successful product release. In particular, we will look at several ways in which requirements influence designs and code (Figure 1).
From Requirements to Designs and Code
The boundary between requirements and design is not a sharp line, but keep your specifications free from implementation bias except when you have a compelling reason to intentionally constrain the design. Requirements specification should concentrate on describing the intended external system behaviors. Ideally, the descriptions of what the system is intended to do should not be slanted by design considerations. Practically speaking, many projects contain design constraints from prior products, and backwards compatibility is a frequent requirement. Because of this, a requirements specification almost always contains some design information. However, the specification should not contain inadvertent design. Include designers or developers in requirements reviews to make sure the requirements can serve as a solid foundation for design.
A product’s requirements, quality attributes, and user characteristics drive its architecture. Studying a proposed architecture provides a different perspective that helps to verify the requirements and tune their precision, as does prototyping. Both methods use the following thought process: “If I understand the requirements correctly, then this approach is a good way to satisfy them. Now that I have a preliminary architecture (or a prototype) in hand, does it help me understand the requirements better?”
Architecture is especially critical for systems that include both software and hardware components and for complex software-only systems. An essential requirements analysis step is to allocate the high-level system requirements to the various subsystems and components. A business analyst, systems engineer, or architect decomposes the system requirements into functional requirements for both the software and the hardware subsystems. Traceability information lets the development team track where each requirement is addressed in the design.
Poor allocation decisions can result in the software being expected to perform functions that should have been assigned to hardware components (or the converse), poor performance, or the inability to replace one component easily with an improved version. On one project, the hardware engineer blatantly told my group that he expected our software to overcome all limitations of his hardware design! Although software is more malleable than hardware, engineers shouldn’t use software’s flexibility as a reason to skimp on hardware design. Take a system engineering approach to make optimal decisions about which capabilities each system component will satisfy within its imposed constraints.
Allocation of system capabilities to subsystems and components must be done from the top down. Consider a DVD player, which includes motors to open and close the disk tray and to spin the disk, an optical subsystem to read the data on the disk, a multifunction remote control, image rendering software, and more (Figure 2). The subsystems interact to control the behaviors that result when, say, the user presses a button on the remote control to open the disk tray while the disk is playing. The system requirements drive the architecture design for such complex products, and the architecture influences the requirements allocation.
Software design receives short shrift on some projects, yet the time spent on design is an excellent investment. A variety of software designs will satisfy most products’ requirements. These designs will vary in their performance, efficiency, robustness, and the technical methods employed. If you leap directly from requirements into code, you’re essentially designing the software on the fly. You come up with a design, but not necessarily with the best design or even a really good design. Refactoring the code can improve the design, but an ounce of design is worth a pound of refactoring. Thinking about design alternatives will also help to ensure that developers respect any stated design constraints.
Thinking about design can even make your life simpler. I once worked on a project that simulated the behavior of a photographic system with eight computational processes. After working hard on requirements analysis, we were eager to start coding. Instead, we took the time to create a design model, thinking about how we would build a solution instead of trying to understand the problem. We quickly realized that three of the steps in the simulation used identical computational algorithms, three more used another set, and the remaining two steps shared a third set. The design perspective simplified the problem from eight complex calculations to just three. Had we begun coding immediately after requirements analysis, we doubtless would have noticed the code repetition at some point. We saved a lot of time by detecting these simplifications early on. It’s more efficient to revise design models than to rewrite code.
As with requirements, excellent designs are the result of iteration. Make multiple passes through the design to refine your initial concepts as you gain information and generate additional ideas. Shortcomings in design lead to products that are difficult to maintain and extend and which don’t satisfy the customer’s performance, usability, and reliability objectives. The time you spend translating requirements into designs is an excellent investment in building high-quality, robust software products.
You need not develop a complete, detailed design for the entire product before you begin implementation. However, design each component before you code it. Design planning is of most benefit to very difficult projects, systems having many internal component interfaces and interactions, and projects staffed with inexperienced developers. All kinds of projects will benefit from the following actions:
- Develop a solid architecture of subsystems and components that will hold up during enhancement.
- Identify the key object classes or functional modules you need to build, defining their interfaces, responsibilities, and collaborations with other units.
- For parallel-processing systems, understand the planned execution threads or allocation of functionality to concurrent processes.
- Define each code unit’s intended functionality, following the sound design principles of strong cohesion, loose coupling, and information hiding (McConnell 1993).
- Make sure that your design accommodates all the functional requirements and doesn’t contain unnecessary functionality.
- Ensure that the design accommodates exceptional conditions that can arise.
- Ensure that the design will achieve performance, robustness, reliability, and other stated quality goals.
As developers translate requirements into designs and code, they’ll encounter points of ambiguity and confusion. Ideally, developers can route these issues back to customers or analysts for resolution. If an issue can’t be resolved immediately, any assumptions, guesses, or interpretations that a developer makes should be documented and reviewed with customer representatives. If you encounter many such problems, the requirements weren’t sufficiently clear or adequately detailed before they were passed to the developers. Review the remaining requirements with a developer or two and tune them up before continuing with construction. Also, revisit your requirements validation processes to see how the low-quality requirements made it into the developers’ hands.
From Requirements to Success
I recently encountered a project in which a contract development team came on board to implement an application for which an earlier team had developed the requirements. The new team took one look at the dozen three-inch binders of requirements, shuddered in horror, and began coding. They didn’t refer to the SRS during construction. Instead, they built what they thought they were supposed to, based on an incomplete and inaccurate understanding of the intended system. Not surprisingly, this project encountered a lot of problems. The prospect of trying to understand such a huge volume of even excellent requirements is certainly daunting, but ignoring them is a decisive step toward project failure. It is faster to read the requirements, however extensive, than to build the wrong system and then build it again. It’s even faster to engage the development team early in the project, so they can participate in the requirements work and perform early prototyping.
The ultimate deliverable from a software development project isn’t an SRS; it’s a system that meets the customers’ needs and expectations. The requirements are an essential step on the path from business need to satisfied customers. If you don’t base your plans, designs, and tests on a foundation of high-quality requirements, you’re likely to waste a great deal of effort trying to deliver an excellent product. Don’t become too overly controlled by your requirements processes, though. There’s no point in spending so much time generating unnecessary documents and holding ritualized meetings that no software ever gets written and the project is canceled. Strive for a sensible balance between rigorous specification and off-the-top-of-the-head coding that will reduce to an acceptable level the risk of building the wrong product.