Designing a system is about creating the internal structure and behavior of a system that's robust, extensible, and
high-quality. Good design improves quality and makes a system easier to maintain and extend, while a poor design can
significantly raise the cost of producing and maintaining the software.
Design is an abstraction of the code that presents the system from a perspective that makes it easier to address the
structure and behavior of the software. This can be done through viewing the code, but it's more difficult and less
effective to address structural and behavioral issues this way. Design can be visual models, simple sketches, text
descriptions, etc. The critical element of design is that it describes how different elements of the system interact to
fulfill the requirements.
The amount of design that's formally documented and maintained will vary depending on the criticality of the design and
how much of the design needs to be communicated to future team members. At a minimum, all architecturally significant
design elements should be documented and kept up-to-date with the implementation. These are critical aspects of the
system that are necessary for the understanding and maintenance of the software. Other important or complex structure
and behavior may be maintained as well. And some contracts may require that the entire design is throughly documented.
On many projects there will probably be aspects of the design that are only documented for the purpose of creating a
solution or walking through how certain behavior will be realized. It may not be worth the overhead of maintaining this
information as the design is transformed through refactoring and other influences. However, it may be useful to archive
the initial decisions, whiteboard images, or files so they can be referenced in the future if necessary. These can be
viewed as "old meeting minutes" that are stored for potential future reference. They may not reflect the current
design, but they may still provide useful insight.
Multiple Passes
The design will be revisited many times throughout an iterative lifecycle and even within an iteration. Each time some
design activity is being performed, it will be performed in the context of a specific goal. The goal might be to
identify a notional set of participants in a collaboration that can be exercised to realize the behavior required (an
analysis pass). The goal might be in the identification of some coarse-grained elements that are required to act out
some scenario (an architectural pass). Then a pass might be done within one of those components to identify the
elements within that will collaborate together to perform the behavior required (a more detailed design pass).
Design might be performed in a particular context such as database context, user-interface context, or perhaps the
context of how some existing library will be applied. In these cases the design steps will be performed just to make
and communicate decisions regarding that context
Avoid analysis paralysis. Avoid refining, extending, and improving the design beyond a minimal version that suffices to
cover the needs of the requirements within the architecture. Design should be done in small chunks, proven via
implementation, improved via refactoring, and integrated into the baseline to provide value to the stakeholders.
Design versus Architecture
Design is a real thing, the construction of the system’s structure and behavior. Architecture [link to concept on
Software Architecture] defines principles, contexts, and constraints on the system’s construction. Architecture is
described in architecture artifacts, but it’s realized as design (visual or otherwise) and implementation.
One way to look at architecture is that it helps to make the entire design more cohesive with itself by balancing the
needs of the entire system. Design tends to focus on one area at a time. Architecture helps assure the design is
consistent and appropriate to the goals of the system. For instance, there may be constraints placed on most of the
design to support the performance of one part of the system, such as improving access to a legacy system. Failure to
conform to those constraints in the design may cause the system to fail to meet the performance requirements of the
legacy system access. Conforming to the architecture assures that all the goals of the system are met by balancing
competing technical issues.
Quality of Design
You Arn't Going to Need It
The YAGNI principle is a good general approach to design. While designs should be robust enough to modify, re-use, and
maintain, it should also be as simple as possible. One of the ways to keep it simple is to make few assumptions about
what the design's going to need in the future. Don't assume you'll need something until you know you need it, then do a
good job of adding it. Add what's needed for the current requirement or iteration. Refactor the design as necessary
when more functionality needs to be added or the design must be made more complex to deal with new circumstances.
Coupling and Cohesion
Two of the most fundamental principles of design are coupling and cohesion. A good design contains elements that have
high cohesion and low coupling. High cohesion means that a single element, such as a class or subsystem, is composed of
parts that are closely related or work closely together to fulfill some purpose. Low coupling means that the elements
of a system have a minimum of dependencies on each other. A single element such as a subsystem should be easily
replaceable by another subsystem that provides similar behavior.
For example, in a payroll system an Employee class would have high cohesion if it contained elements and functions
such as Name, Tax ID Number, and Monthly Salary. At first, it may seem as if the Calculate Paycheck functional would
also be cohesive. But when you consider that hourly employees must be paid overtime, sales people must have commission
calculated for them, etc, the function is less related to Employee and should probably be its own class or
subsystem.
An example of low coupling would be if the Calculate Paycheck subsystem can be easily replaced by third party that may
be more robust and offer more features.
Coupling and cohesion are so important to be aware of because they arise in so many design principles and design
strategies such as patterns.
Open-Closed Principle
Elements in the design should be “open” for extension but “closed” for modification. The goal of this principle is to
create software than can be extended without changing code [MEY97]. This is because every change to software runs the risk of introducing bugs
into code that’s already correct. It also allows functionality to be re-used without having to know the details of the
implementation, reducing the time it takes to create something new. Keeping this principle in mind helps make a design
more maintainable.
|