This site is currently a working draft of the ITABoK 3.0. Release date is planned for summer of 2021. In the meantime please utilize the current ITABoK version 2.0
The ability to design—to create something where nothing was before—is the most important skill of an architect. As Grady Booch put it so elegantly: “All architecture is design, but not all design is architecture. Architecture represents the significant design decisions that shape the form and function of a system, where significant is measured by the cost of change.”1 Put another way: All architecture is design, but not all design is architecture; it’s architecture if you have to start over to fix it.
Design isn’t easy. As Maarten Boasson put it, “Designing software is not very different from designing any other complex structure: Few people are good at it; no single recipe always produces a good product; and the more people involved, the smaller the probability of success.”2 Not very encouraging, but accurate. We aim to take some of the mystery out of the topic. Hopefully, you will learn something along the way and become more confident expressing yourself as a designer.
Design happens no matter how you organize your projects. If it doesn’t happen in its own “phase,” it happens when you’re elbow-deep in the code and encounter an unexpected condition you need to handle. Whenever it happens, it needs to be done deliberately. The documents that result from “big up-front” design are not the design, they are a representation of it. The design is the thinking that goes into creating those documents. If that thinking results directly in code, great, so long as the thinking happens.
In this article, we explore the nature of design, not how to do it. We will look at the factors in the problem being solved and the domain in which it exists that influence and constrain our designs. We will also see how decisions we make that don’t seem to be related to the design can also influence and constrain our design in the end.
What is Design?
Design is both a noun and a verb. “Design” is the verb-form of architecture, although there is debate about this (we design an architecture for a structure, we don’t architect a structure). Design is deliberate, that is we purposefully set out to create a design that works safely, is economical, and elegant, in that order. Design has become more important because software has become more and more complex, because we ask more of it and because the environment in which is runs is more complex. Jack Mostow suggests that the purpose of design is to create a solution that:
- “Satisfies a given purpose
- Conforms to limitations of the target medium
- Meets implicit or explicit requirements on performance and resource usage
- Satisfies implicit or explicit design criteria on the form of the artifact
- Satisfies restrictions on the design process itself, such as its length or cost, or the tools available for doing the design”3
These goals are very often in conflict, handing the designer a complex series of tradeoffs to optimize. Designing anything is almost always an optimization problem but is especially true in software.
The act of design is an experiment, and the results of that act are an hypothesis4. The hypothesis states that if you build the software according to the design, and run it in the specified environment, you will get a set of expected results. The purpose of software testing is to refute that hypothesis and cause the software to fail. Like any hypothesis, you cannot prove it correct; you can only prove it wrong. Failure thus plays a huge role in any successful design effort; you design, analyze, and test until you can think of no new ways for the design to fail.
Design and analysis are different. Analysis uses deductive reasoning to dismantle a structure (or problem) into it constituent components to better understand it. Design uses inductive reasoning to create a new structure where none existed before. They are complimentary, and the designer is constantly switching between them throughout the process.
What Influences Design?
In bridge design, the nature of the crossing, and the length of the span in particular, have the most influence on the resulting design. Together, the different aspects of the crossing form the environment or context for the bridge and can be considered non-functional requirements. They are non-functional because they are not defined by the purpose of the bridge, the type of traffic it will carry, but only by the world in which the bridge will exist. As the span lengthens, the types of bridges you can build become more constrained, to the point that for spans over 1000 feet, you basically have three options.
So it is with software systems. The context in which the system will live has more to say about the design than do the functional requirements. The main influences over the design are5:
- The shape of the problem
- The number of users
- The number of time zones they cover
- How data is generated and used
- Availability requirements
- Scale and scalability
- Required performance levels
- Volatility in the problem domain
- Security requirements
- Programming style
- Design approach
Of these, the shape of the problem has by far the most influence. For decades, developers have described software problems as falling into one of three categories: business, scientific, and real-time. It turns out there are good reasons for these particular categories, and we have Tom DeMarco to thank for the explanation6. Tom showed that all software can be described in terms of three dimensions:
- Data: A partitioned view of what the system remembers
- Function: A partitioned view of what the system does
- Behavior: A partitioned view of the behavioral states that characterize the system
We have since taken to using “control” as the name for the behavior dimension which is represented by the combined state machines of the entities which participate in it.
In many systems, one dimension clearly dominates the others, and this domination led to our three categories, but all three dimensions are always present. Business software is dominated by the data dimension and is data-strong. Scientific software is dominated by the function dimension and is function-strong. Real-time software is dominated by the control dimension and is control-strong. The dominant dimension provides the basic shape of your problem, to the point that each category corresponds to a design pattern that governs the entire shape of your solution5.
We need to talk about the last two influences on the list, programming style and design approach. The three main programming styles, structured or procedural, object-oriented, and functional, will influence your design by leading you to make certain decisions and constraining you from making others5. The choice of programming style is often made for you by the culture of your workplace or past history. Your own history as a developer has a significant influence over your future designs.
The design approach describes the way you go about identifying the significant abstractions that become components in your design7. Each of the main design approaches, including data-driven, event-driven, and responsibility-driven, among others, lead to slightly different sets of abstractions. No single approach works in every case, and in most cases, no single approach is sufficient. A combination of design approaches, or multiple passes through the problem domain with different approaches lead to the most complete designs.
The Four Domains of Design
George Yuan was the first to write about designing the problem domain8. While his purpose was to create a complete object-oriented design method, his notions of design domains turned out to be the most useful contribution. In particular, Yuan described four domains that all designs must describe:
- Problem domain: Contains abstractions that appear in the real world and have an influence over the designed solution whether or not they become design components. The notion that the problem domain is “designed” versus “analyzed” is unique to Yuan’s work, but makes sense. There are many situations in the problem domain where choices can be made that affect subsequent work. Approaching the problem domain as a design activity, with a lot of analysis thrown in, can lead to a better result.
- Application domain: A subset of the abstractions in the problem domain that will become actual design components in the solution. The application domain is determined by literally drawing a line around abstractions in the problem domain to include some and exclude others. This line forms the application boundary and creates a host of requirements for monitoring, detecting, and reacting to events that occur outside the boundary.
- Application-specific domain: Contains abstractions created to specifically support the application being designed. Abstractions such as views and controllers created to separate concerns into layers fall into this domain.
- Application-generic domain: Contains abstractions able to support multiple applications. Code libraries and utilities fall into this domain, whether created for this solution or acquired (purchased or Open Source).
During the design, you can sometimes move functionality from one domain to another, most often from the application-specific to the application-generic domain. One of your design goals should be to write as little code as possible to get the job done, and using application-generic abstractions go a long ways towards achieving that goal.
Patterns and Pattern Languages
First introduced to building architecture in 1977 by Christopher Alexander9, patterns and pattern languages have become two of the go-to tools for software designers, even those who don’t design object-oriented software. A pattern is a design for a small portion of a solution that appears over and over across applications. A pattern can range from high level designs for an entire application, such as the Pipes-and-Filters pattern for function-strong software10 to low level design patterns and idioms, such as the Singleton to ensure that only one copy of certain data can ever exist11. Patterns have proven useful enough to become central to the learning of design of all things, from architecture to code-level details.
A pattern language is a list of patterns at various levels of detail that will or might be included in the design. To create a pattern language for a particular solution, the designer browses pattern catalogs and selects those that solve or help solve issues that are known to be coming. Patterns get added to and removed from this list as the project progresses. Time spent browsing pattern catalogs is time well spent as you often find solutions you recognize you will need but hadn’t considered. The first pattern on any list is the one that matches the shape of the problem at the highest level5.
What Makes a Design “Good”?
Design is a sequence of choices. Design decisions become much easier and more reliably made when the design activity is guided by a set of design principles and decisions are made using well-defined criteria that have measures to support them. The principles and criteria described here are not meant to be applied to every project, or even applied to any project as “thou-shalt” directives. Rather, when a design decision is encountered, use the principles and criteria to guide your decision making.
The most complete set of design principles was developed in the context of object-oriented programming but apply equally well to other programming styles12. Many of these principles have been around since the 1980s and have proven themselves in practice. The last two are the result of more than one finding in a project post-mortem. Here’s the list:
- Single Responsibility
- Liskov Substitution
- Interface Segregation
- Dependency Inversion
- One Copy of Data
- Keep Business Logic in its Place
Design criteria define characteristics of a design that, when compared to another design for the same solution, is better because it has more of, or less of, that characteristic. In some cases, such as cohesion, more is better. In others, such as complexity, less is better. A set of defined criteria, and measures to evaluate them objectively, provide a big step from ad hoc design to actual engineering. These criteria are not meant to create project goals, that is, target values for the designer to hit. They are instead intended to assist local design decisions when deciding between two or more alternatives. They can also assist in the choice of design patterns to include in the pattern language for the project. Over time, the following criteria have proven useful7:
Systems thinking is a point of view. The designer approaches the solution as a working system of components. A system is the product of the interaction of its parts, not just the sum of the collection of parts. A pile of car parts is not a car until they are assembled into a system. Just as members of a team can amplify their individual abilities when part of a team, components often behave differently when part of a system than when observed individually. This cross-system feedback is as important to understand as the internals of any particular component when designing a system of any kind. Six key tools for systems thinking are13:
- Feedback Loops
- Systems Mapping
Where systems thinking is a point of view, design thinking is a discrete process for doing design and consists of five steps repeated multiple times14:
The process seeks to bring together what is desired of a solution with what is technically possible and economically feasible. The main contribution of design thinking is a set of tools to accompany the steps. The astute observer will recognize the observe-hypothesize-experiment-repeat cycle of the scientific method and the core definitions of design and engineering as disciplines.
From Design to Engineering
In most modern software projects, it is not sufficient to be a good designer. You must also engineer your software as you struggle with competing goals, requirements, and constraints. Henry Petroski makes the distinction clear: “What distinguishes the engineer from the technician [or designer] is largely the ability to formulate and carry out the detailed calculations of forces and deflections, concentrations and flows, voltages and currents, that are required to test a proposed design on paper with regard to failure criteria.”15 Applied to software, the difference between engineering it and “merely” designing it is the ability to test the proposed design against various criteria, including failure modes, before code exists to test. This extra effort doesn’t add much difficulty to the task, nor does it take very long. Over time, you learn to apply it to only those design decisions you haven’t made before or don’t understand well.
The ever-increasing complexity of software, the difficulty of the tasks it is being asked to perform, and the risk of failure make moving from design to engineering an imperative. Design will still exist as described here, but it will be accompanied by new techniques and tools that allow us to test a design directly, without having to wait for code to test7.
2 Boasson, Maarten, The Artistry of Software Architecture. IEEE Software, November, 1995, 12(6), 13-16.
3 Mostow, Jack, Towards Better Models of the Design Process. AI Magazine, 1985, 6(1)
4 Petroski, Henry To Engineering is Human: The Role of Failure in Design, St. Martin’s Press, New York, New York: 1985.
5 Whitmire, Scott A., Engineer Your Software!, Synthesis Lectures on Algorithms and Software in Engineering, Andreas Spanias (Series Ed.), Morgan & Claypool, San Rafael, CA: 2021.
6 DeMarco, Tom, Controlling Software Projects. Yourdon Press , Englewood Cliffs, New Jersey:1982.
7 Whitmire, Scott A., Object Oriented Design Measurement. John Wiley & Sons, New York, New York: 1997.
8 Yuan, George, A Depth-First Process Model for Object-Oriented Development with Improved OOA/OOD Notations. Report on Object-Oriented Analysis and Design, 2(1), May/June, 1995, 23-37.
9 Alexander, Chrostopher, Ishikawa, Sara, Silverstein, MMurray, Jacobson, Max, Fiksdahl-King, Ingrid, & Angel, Shlomo, A Pattern Language: Towns, Bulidings, Construction. Oxford University Press, New York, New York: 1977.
10 Buschmann, Frank, Meunier, Regine, Rohnert, Hans, Sommerlad, Peter, & Stal, Michael, Pattern-Oriented Software Architecture: A System of Patterns (Vol. 1), John Wiley & Sons, New York, New York: 1996.
11 Gamma, Erich, Helm, Richard, Johnson, Ralph, & Vlissides, John , Design Patterns: Elements of Reusuable Object-Oriented Software, Addison-Wesley, Reading, Massachusetts: 1995.
12 Martin, Robert C., Clean Architecture: A Craftsman’s Guide to Software Structure and Design. Prentice Hall, New York, New York: 2018.
13 Acaroglu, Leyla, “Tools for Systems Thinkers: The 6 Fundamental Concepts of Systems Thinking”, Disruptive Design Blog, https://medium.com/disruptive-design/tools-for-systems-thinkers-the-6-fundamental-concepts-of-systems-thinking-379cdac3dc6a, September 7, 2017, accessed May 31, 2021.
14 Brown, Tim, Design Thinking. Harvard Business Review, June 2008.
15 Petroski, Henry, Invention by Design: How Engineers Get from Thought to Thing, Harvard University Press, Camebridge, MA: 1996.