Overview:
This concept outlines important principles that should be taken into account when considering the design of a system.
Main Description:
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. 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.
Check Items:
Is the design understandable?
Is the design organized in a way that team members can easily find the information that they're looking for?
Is the design as simple as it can be, while still fulfilling the objectives of the design and giving sufficient direction to implementers?
Is the design neither too simple nor too advanced? The design sophistication should be appropriate to the experience level of other team members and technical stakeholders. This applies to both the concept and the representation of the design.
Does the design express what the designer intends to express?
Is the design consistent?
Does the design follow any design standards?
Does the design apply other idioms consistently?
Are the names of the design elements consistent and easy to interpret?
Does any part of the design contradict another part of it in such a way that puts the project at risk?
Is the design maintainable?
Is the design structured well enough to be maintained?
Is the design set up to appropriately accommodate expected changes? The design should not be overdone to handle any possible change, just reasonably expected changes.
Have redundant areas of the design been removed so that the implementation does not contain redundant code?
Is the design traceable?
Is it clear how the design elements relate to the requirements? This does not need to involve a heavyweight traceability strategy, but is there some way to figure out what part of the design supports a particular requirement?
It what portions of the implementation support each design element clear?
Does the design reflect the architectural objectives of the system?
Does the design conform to the architecture as specified?
Does it apply the architectural patterns appropriately?
Are Architectural Mechanisms used appropriately? Are they applied in all applicable circumstances?
Are the design elements modular?
Do the design elements have high internal cohesion? Does the degree of interaction within the unit demonstrate that all of the internal parts belong together?
Do the design elements have low coupling? Is there minimal interdependence between design elements? When design elements depend upon one another, is this done as simply as possible and in such a way that the client element will not be affected by changes to the internal parts of the supplier element?
Are the design elements defined with abstract interfaces in ways that changes can be made to the internal implementation without affecting client design elements?
Does each design element represent a clearly defined abstraction?
- Can the system be implemented from the information in the design?
- Has sufficient detail been included to direct the implementation?
- Does the design constrain the implementation only as much as necessary? Does the design allow freedom for the implementer to implement it appropriately?
- Is the design feasible? Is it a design that can be reasonably implemented by the team by using the technologies selected within the timeframe of the project?
- Does the design provide enough information for developer testing?
- Does the design provide enough information for developer test design? Are the expected behavior and constraints on the methods clear?
- Are the collaborations between design elements clear enough to create integration tests?
- Does the design describe the system at the appropriate level of abstraction?
- Does the design describe the system at the appropriate level of abstraction given the objectives? This usually means that the system is described at several different levels of abstraction and from different perspectives.
- Does the design support a coarse-grained perspective of the system?
- Can the design be understood as a set of higher-order subsystems?
- Are the subsystem dependencies documented?
- Are interfaces clearly defined for each subsystem? Is each subsystem designed so that its services can be accessed through the interface without a need to access internal parts?
- Is each subsystem designed so that someone can work within one part without having to understand the internal parts of the other elements?
- Packages and Organization
- Is the package partitioning logical and consistent? Does it make sense to team members and stakeholders?
- Do package names accurately describe the contents of the package and the role that they play in the architecture? Do they follow naming conventions?
- Do public packages and interfaces provide a logically cohesive set of services?
- Are all the contents of a package listed? Are the classes within a package cohesive?
- Do package dependencies correspond to the dependencies of the contained classes?
- Are there packages or classes within a package that can be separated into an independent or sub-package?
- Views
- Does each diagram help the designer reason about the design or communicate key design decisions to the team?
- Are the relationships between diagrams clear when several diagrams are used to describe behavior?
- Is it easy to navigate between related diagrams?
- Does each diagram focus on a relevant perspective? For instance, does a set of diagrams show a single class and its direct relationships, rather than using one or two diagrams to show all classes?
- Is each diagram complete, yet minimal? Does it show everything relevant to that view and nothing more?
- Are the diagrams tidy and easy to interpret, with a minimum of clutter?
- UML
- Does the visual model conform to UML standards so that all stakeholders can understand the model over time? (See the OMG UML Resource Page for more information.)
- Does the visual model conform to project- or organization-specific modeling standards?
- Is the visual model internally consistent? For instance, if an object diagram shows a relationship between objects, does a corresponding relationship exist between the appropriate classes?
- Does the name of each class clearly reflect the role that it plays?
- Does each class offer the required behavior?
- Is there at least one realization association defined for each interface? The realization may represent a third-party implementation of the subsystem.
- Are there dependency associations from each subsystem to the interfaces that it uses?
- Is each operation in a subsystem interface described in a sequence diagram or at least mapped directly to an operation in a class?
- Does each class represent a single, well-defined abstraction?
- Are generalization relationships used only to inherit definitions, not behavior (implementation)? In other words, is behavior shared through the use of association, aggregation, and containment relationships rather than generalization?
- Are parent classes in generalization relationships abstract? Are the "leaf" classes in a generalization hierarchy the only concrete classes?
- Are stereotypes used consistently and meaningfully?
- Do state charts exist for classes with complex or restrictive state changes?
- Do relationships have descriptive role or association names (one or the other, but not both) and correct multiplicities?
- Are relationships between classes unidirectional whenever possible?
- Non-UML Visual Modeling
- Are the semantics of the visual modeling language clearly defined, documented, and accessible to team members? The semantics should be meaningful to people who use the model.
- Can the semantics of the modeling language be understood over time? Is the language documented well enough that team members can understand the model long after design decisions have been made?
- Are team members and stakeholders trained in the modeling language being used?
- Does the visual model conform to the semantics of the visual modeling language? In other words, are the meanings of the symbols in the diagrams consistent across the model and the diagrams?
Template:
This template describes how the design can be organized to be understood from multiple perspectives. It also provides suggestions for how patterns and descriptions of small, reusable interactions can be used to minimize redundancy.
It is important not to think of design as "a document." Design information that is worth keeping for some duration must have a long-lived form. But that form might be as a repository in a visual modeling tool, or as subdirectories of whiteboard diagrams captured with a digital camera, or as an actual document that provides structure for images taken from a myriad of sources.
This template describes the information that should be conveyed. Typically, it works best to convey the information graphically (either with UML or another unambiguous notation), or at least in words, at an abstract level. You can enhance this with code examples, but best not to render the design solely at the code level.
The structure of the design is suggested in this template.
Design structure:
[Describe the design from the highest level. This is commonly done with a diagram that shows a layered architecture.]
Subsystems:
[Sub-system1]
[Describe the design of a portion of the system (a package or component, for instance). The design should capture both static and dynamic perspectives.
When capturing dynamic descriptions of behavior, look for reusable chunks of behavior that you can reference to simplify the design of the use-case realizations.
You can break this section down into lower-level subsections to describe lower-level, encapsulated subsystems.]
Patterns:
[Pattern1]
Overview
[Provide an overview of the pattern in words in some consistent form. The overview of a pattern can include the intent, motivation, and applicability.]
Structure
[Describe the pattern from a static perspective. Include all of the participants and how they relate to one another, and call out the relevant data and behavior.]
Behavior
[Describe the pattern from a dynamic perspective. Walk the reader through how the participants collaborate to support various scenarios.]
Example
[Often, you can convey the nature of the pattern better with an additional concrete example.]
Use-case realizations:
[Realization1]
View of participants
[Describe the participating design elements from a static perspective, giving details such as behavior, relationships, and attributes relevant to this use-case realization.]
Basic scenario
[For the main flow, describe how instances of the design elements collaborate to realize the use case. When using UML, this can be done with collaboration diagrams (sequence or communication).]
Additional scenarios
[For other scenarios that must be described to convey an appropriate amount of information about how the use-case behavior will be realized, describe how instances of the design elements collaborate to realize the use case. When using UML, you can do this with collaboration diagrams (sequence or communication).]