How to Reduce Software Complexity: Principles from Ousterhout’s Design Philosophy
Drawing on John Ousterhout’s “A Philosophy of Software Design,” this article explores how to lower software complexity through clear definitions, modular design, layering, information hiding, thoughtful commenting, and strategic development practices, offering actionable principles for building maintainable systems.
Preface
John Ousterhout, the Stanford professor who created Tcl, wrote A Philosophy of Software Design . The book’s core message is that software design aims to reduce complexity.
How Complexity Is Defined
Complexity lacks a single definition. It can be measured by quantity (e.g., number of transistors), hierarchy, or information entropy. Ousterhout defines software complexity in terms of cognitive load and development effort, proposing a formula that multiplies a submodule’s intrinsic complexity C p by its development‑time weight t p and sums across modules.
The submodule complexity C p reflects three phenomena:
Modification diffusion : changes cause chain reactions.
Cognitive burden : time developers need to understand a module.
Unknown unknowns : developers may not know where to start.
Complexity usually stems from code dependencies and obscurity. Dependencies tie code together, while obscurity makes important information hard to locate.
General Principles for Reducing Complexity
1. Design is an ongoing effort; incremental improvements lead to a robust system.
2. Specialization and code reuse boost productivity. By dividing a system into horizontal layers with clear roles, each layer can expose a simple interface while allowing varied implementations.
3. Vertical decomposition (micro‑services) isolates modules, though it introduces new inter‑module complexity that must be managed.
4. Good comments capture design intent, reducing cognitive load and addressing unknown unknowns.
Daily Practices to Tame Complexity
4.1 Reject Tactical Programming
Tactical programming focuses on quick fixes, which accumulates hidden complexity. Strategic programming invests time in design, improving long‑term maintainability.
4.2 Design Twice
Provide multiple design alternatives for a class or module to identify the optimal solution. Answer two questions: why a solution works, and why it is optimal given constraints.
5. Layered Architecture
Layered systems limit each layer’s visibility to adjacent layers, simplifying reasoning. The TCP/IP model exemplifies this approach.
Complexity should be pushed down to lower layers whenever possible, e.g., handling retries inside a Thrift client rather than exposing them to downstream developers.
6. Modular Design
Modules should be isolated so developers only need to understand a small part of the system.
6.1 Deep vs. Shallow Modules
Deep modules offer powerful functionality behind a simple interface. Example: Unix open function.
int open(const char* path, int flags, mode_t permissions);Shallow modules expose complex interfaces for simple functionality, increasing cognitive load. Example: Java I/O chain.
FileInputStream fileStream = new FileInputStream(fileName);
BufferedInputStream bufferedStream = new BufferedInputStream(fileStream);
ObjectInputStream objectStream = new ObjectInputStream(bufferedStream);6.2 General‑Purpose vs. Specialized Modules
General‑purpose modules serve many scenarios, while specialized modules address immediate needs. A balanced approach combines quick implementation with a generic interface for future extensibility.
6.3 Information Hiding
Hide internal design and logic within a module, exposing only a simple interface. This reduces cognitive burden and inter‑module dependencies, as illustrated by database index APIs that hide B+‑tree details.
6.4 Splitting and Merging
Decide whether to combine or separate functionalities based on shared information, interface simplicity, and duplication.
7. The Role of Comments
Comments record design rationale and reduce cognitive load. Good comments answer “what” and “why,” not “how.” They can be low‑level (details like units) or high‑level (overall design). Writing comments early, as part of design, yields better documentation and design quality.
8. Conclusion
John Ousterhout’s 250,000 lines of code and contributions to three operating systems embody the principles discussed. These guidelines, echoed in works like “Code Complete” and “Refactoring,” provide practical value for engineers seeking to reduce complexity and improve maintainability.
References
[1] John Ousterhout. A Philosophy of Software Design . Yaknyam Press, 2018.
[2] Melanie Mitchell. Complexity . Hunan Science & Technology Press, 2016.
[3] Martin Fowler. Refactoring: Improving the Design of Existing Code (2nd Edition) . Addison‑Wesley, 2018.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
21CTO
21CTO (21CTO.com) offers developers community, training, and services, making it your go‑to learning and service platform.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
