Chapter 21 Decide What Matters
One of the most important elements of good software design is separating what matters from what doesn’t matter. Structure software systems around the things that matter. For the things that don’t matter as much, try to minimize their impact on the rest of the system. Things that matter should be emphasized and made more obvious; things that don’t matter should be hidden as much as possible.
Many of the ideas in the preceding chapters have at their heart the notion of separating what matters from what doesn’t. For example, this is what we do when designing abstractions. The interface of a module reflects what matters to users of that module; things that don’t matter to the module’s users should be hidden in the implementation, where they are less obvious. When choosing a variable name, the goal is to pick a few words that convey the most possible information about the variable and use those in the name; these are the aspects of the variable that matter most. If performance really matters for a module, then the design of the module should be structured around achieving the performance goals; in the example of Section 20.4, this meant finding a design where the performance-critical path had as few method calls and special-case checks as possible, while still being clean, simple, and obvious.
21.1 How to decide what matters?
Sometimes things that are important are imposed as external constraints on a system, such as performance in Section 20.4. More often it is up to the designer to determine what matters. Even when there are external constraints, the designer must figure out what matters most in achieving those constraints.
To decide what matters, look for leverage, where the solution to one problem also allows many other problems to be solved, or where knowing one piece of information makes it easy to understand many other things. For example, in the discussion of how to store text in Section 6.2, a general-purpose interface for inserting and deleting ranges of characters could be used to solve many problems, whereas specialized methods such as backspace only solved a single problem. The general-purpose interface provided more leverage. At the level of the text class interface, it didn’t matter whether the interface was being invoked in response to the backspace key; all that really mattered was that text needed to be deleted. An invariant is another example of a leverage point: once you know an invariant for a variable or structure, you can predict how that variable or structure will behave in many different situations.
It’s easier to determine what is most important if you have multiple options to choose among. For example, when choosing a variable name, make a mental list of words that relate to that variable, then pick a few of the words that convey the most information. Use those words to form the variable name. This is an example of the “design it twice” principle.
Sometimes it may not be obvious which things matter the most; this can be particularly hard for younger developers who don’t have much experience. In these situations I recommend making a hypothesis: “I think this is what matters most.” Then commit to that hypothesis, build the system under that assumption, and see how it works out. If your hypothesis was right, think about why it ended up being right, and what clues there might have been that you can use in the future. If your hypothesis was wrong, that’s still OK: think about why it ended up being wrong, and whether there were clues that you could have used to avoid this choice. Either way, you will learn from the experience and you will gradually make better and better choices.
21.2 Minimize what matters
Try to make as little matter as possible: this will result in simpler systems. For example, try to minimize the number of parameters that must be specified to construct an object, or provide default values that reflect most common usage. For things that do matter, try to minimize the number of places where they matter. Information that is hidden within a module doesn’t matter to code outside that module. If an exception can be handled entirely at a low level in a system, then it doesn’t matter to the rest of the system. If a configuration parameter can be computed automatically based on system behavior (rather than exposing it for an administrator to choose manually) then it no longer matters to administrators.
21.3 How to emphasize things that matter
Once you have identified the things that matter, you should emphasize them in the design. One way to emphasize is with prominence: important things should appear in places where they are more likely to be seen, such as interface documentation, names, or parameters to heavily used methods. Another way to emphasize is with repetition: key ideas appear over and over again. A third way to emphasize is with centrality. The things that matter the most should be at the heart of the system, where they determine the structure of things around them. One example is the interface for device drivers in operating systems; this is a central idea because hundreds or thousands of drivers will depend on it.
Of course, the converse is also true: if an idea is more likely to be seen, or if it appears over and over again, or if it impacts a system’s structure in significant ways, then that idea matters.
Similarly, things that don’t matter should be de-emphasized. They should be hidden as much as possible, they should not be encountered frequently, and they should not impact the structure of the system.
21.4 Mistakes
In deciding what matters, there are two kinds of mistakes you can make. The first mistake is to treat too many things as important. When this happens, unimportant things clutter up the design, adding complexity and increasing cognitive load. One example is methods with arguments that are irrelevant to most callers. Another example is the Java I/O interface discussed on page 26: it forced developers to be aware of the distinction between buffered and unbuffered I/O, even though this distinction is almost never important (developers almost always want buffering and don’t want to waste time asking for it explicitly). Shallow classes are often the result of treating too many things as important.
The second kind of mistake is to fail to recognize that something is important. This mistake leads to situations where important information is hidden, or important functionality is not available so developers must continually recreate it. This kind of mistake impedes developer productivity and leads to unknown unknowns.
21.5 Thinking more broadly
The idea of focusing on what’s most important applies in other domains beside software design. It’s also important in technical writing: the best way to make a document easy to read is to identify a few key concepts at the beginning and structure the remainder of the document around them. When discussing the details of a system, it helps to tie them back to the overall concepts.
Focusing on what is important is also a great life philosophy: identify a few things that matter most to you, and try to spend as much of your energy as possible on those things. Don’t fritter away all of your time on things that you don’t consider important or rewarding.
The phrase “good taste” describes the ability to distinguish what is important from what isn’t important. Having good taste is an important part of being a good software designer.