I recently read John Ousterhout’s excellent book, A Philosophy of Software Design, which is about high-level design principles for good code. (There is also a YouTube video.)
The core concept that John introduces is the idea of “deep” versus “shallow” modules.1 Quoting the video2:
Think about a class as a rectangle. The area of the rectangle is the functionality that class provides—that’s the benefit that the class provides to the rest of the system. Then think about the top edge as the interface to the class. By interface, I mean everything someone has to have in their mind in order to use that class. It’s not just the signatures for the functions, but things like side-effects and dependencies and things like that. That’s really the cost—that’s the complexity cost that this class imposes on the rest of the system.
A deep class is good because it provides a high ratio of functionality to interface, and a shallow class is bad because it imposes a high ratio of interface to functionality. Here is Figure 4.1 from the book illustrating that concept:
His example of a deep module is the concept of a file: the operating system hides lots of low-level details from the programmer, who just thinks in terms of opening, reading, writing, and closing. His example of a shallow method is:
private void addNullValueForAttribute(String attribute) { data.put(attribute, null); }
which “is so bad that it takes more keystrokes to invoke the method than if you just did the body of the function yourself.”
While the rectangles are a good illustration for defining the deep/shallow distinction, they don’t show you how the modules fit together into a larger system. The metaphor that I like to use is cutting an octopus into eight equally sized pieces. There is one good way to do it, and many bad ways. The good way is the one which minimizes the total length of all cuts, i.e. the size of the interfaces where the pieces meet. Other ways produce longer cuts (interface) per unit of octopus (functionality).

In the octopus example, there are eight clearly defined modules—the legs. It’s intuitively obvious that you should make the legs be the modules, even if you don’t know exactly why. In hindsight, we can see that it minimizes the cut length.
In software design, it is not always as obvious what constitute the “natural” modules. This is why it helps to formalize the problem in terms of deep modules—you’ll know you’ve found the right decomposition when the interfaces between modules are minimal.
He means module in the ordinary sense of a sub-component, including “classes, subsystems, or services” [p. 19]. He says that this deep/shallow distinction is a reformulation of David Parnas’s concept of “information hiding” from the paper “On the criteria to be used in decomposing systems into modules.”
Lightly edited for clarity