A Philosophy of Software Design

An elegant tome on elegant code

Posted by Grace on Jan 6, 2022

Header photo by Ursus Wehrli

Complexity causes more cognitive overhead for programmers - aka the brain-hurting feelings when you’re trying to understand a circuitous piece of code. In addition, the inability to remove dependencies altogether, and obscurity, such as cryptic and indecipherable variables, leads to additional complexity. Ousterhout cautions that the primary goal of coding is not to create code that works. It’s to generate well-designed code. While it may take longer upfront to thoughtfully design code rather than just hook a bunch of things together in a way that works, the long-term costs are dire. Tactical programming is programming with a “get it done fast“ approach, while strategic programming prioritizes design thinking. One part of the philosophy: modules should be deep rather than wide. The interface, available to other parts of the program, should be helpful and small. When designing an interface, you should tuck away functionality to avoid exposing it to the rest of the program. A well-designed interface helps reduce cognitive overhead for programmers. An immature approach to simple objects would be making too many small classes that need complete knowledge of each other because they share too much responsibility. As a result, small modules tend to be shallow. As a result, the benefit of underlying functionality does not compensate for the cost of learning their interface. Furthermore, classes designed in this manner go against the principle of information hiding, which involves handling complexity out of view of the interface and only giving the user the end product they need. Ousterhout says that general-purposes modules are deeper, but of course, there’s a balance to be struck. There’s such a thing as “too general,” and there is a sweet spot to be found. When making design decisions, the author recommends “pick the one with the best information hiding, the fewest dependencies, and the deepest interfaces.” How can developers end up with the best designs, or at least reasonably confident that they’ve made a good choice? Design it twice - try it twice and then decide which one works better. This approach is an interesting idea that could save a lot of time if one of the designs ends up identifying benefits you wouldn’t have otherwise discovered. The author took a pro-comments stance, saying that comments are a good thing to maintain as documentation, and they keep the reader from having to laboriously read through entire functions to find out what it does. Comments need to be up to date, but they should describe modules and save future programmers figuring out what is going on. He says to write the comments first and use them to guide the code. Delayed comments are bad comments, but good comments act as documentation and save precious developer thinking. For this technique to work, these comments need to be kept up to date as fastidiously as you would the codebase itself. This book provided value for its short size. John Ousterhout provides a compelling explanation for his philosophy. Deep modules, good comments, layered applications, and obvious code add to a better codebase to increase developer happiness.