A Philosophy of Software Design is my favorite book I’ve read to date about designing large long-lived maintainable software programs. Here’s what I learned:
Complexity is anything related to the structure of a software system that makes it hard to understand & modify the system.
Symptoms of complexity:
Causes of complexity:
To keep a software system maintainable, you must strive to keep the complexity of the system low as you & others make changes to it.
Concepts related to complexity, which the remaining sections of this article will zoom in on:
A dependency exists when a given piece of code cannot be understood in isolation; the code relates in some way to other code. Numerous and strong dependencies between modules of a system make it difficult to change one module without changing others (Change Amplification) and make it difficult to understand modules in isolation (High Cognitive Load).
Key contributors to dependency-complexity are:
The opposite of dependency-complexity is cohesive code, created primarily by focusing on designing deep modules.
Deep modules allow a lot of functionality to be accessed through a small interface:
Keeping module interfaces small lowers the number and strength of dependencies between modules, resulting in lower overall system complexity:
Chapters 4-9 of the book are related to techniques for forming deeper modules. My article How to Design Large Programs with Abstraction and Encapsulation also discusses deep modules in the context of encapsulation & abstraction.
Obscurity occurs when important information is not obvious.
Obscurity creates Unknown Unknowns and contributes to High Cognitive Load:
Key contributors to obscurity:
Smaller contributors to obscurity:
The opposite of obscurity is obvious code. Obvious code
There are many techniques for creating consistency in code:
Consistent use of names, consistent code style, and consistent implementation patterns are hallmarks of consistency in general.
Tools such as autoformatters, linters, and automated consistency tests are great for enforcing consistency.
Documentation of conventions can be captured in coding style guides and through references to documented design patterns.
Complexity accumulates naturally in a software system if left unchecked.
And once complexity has accumulated it is hard to eliminate.
Therefore the book recommends taking a “zero-tolerance” stance toward the incremental introduction of complexity.
Specifically, it advocates taking a strategic approach to programming tasks, where you intentionally invest time to produce clean designs and fix problems, in addition to getting new features working.
Many programmers, by contrast, take a purely tactical approach that focuses only on getting new features working, disregarding the long-term costs of adding incremental complexity and other forms of cutting corners.
Working code (alone) is not enough.
To keep a software system maintainable, you must strive to keep the complexity of the system low as you & others make changes to it.