Fundamentals 20 min read

Why Reducing Complexity Is the Key to Better Software Design

This article distills the core principles from John Ousterhout's *A Philosophy of Software Design*—defining software complexity, presenting a quantitative model, and offering practical strategies such as layering, modularization, information hiding, and effective commenting—to help engineers build maintainable, low‑complexity systems.

21CTO
21CTO
21CTO
Why Reducing Complexity Is the Key to Better Software Design

After reading John Ousterhout's A Philosophy of Software Design , the author summarizes the essential idea that software design’s core goal is to reduce complexity, supplementing the book’s principles with personal experience and examples.

How to Define Complexity

Complexity lacks a single definition; it can be measured by quantity (e.g., number of components), hierarchical recursion, or information entropy. Ousterhout defines software complexity from the perspective of cognitive load and development effort, proposing the formula:

The overall system complexity C is the sum of each submodule’s complexity Cp multiplied by its development‑time weight tp. Even a highly complex module has limited impact if it is rarely used or modified.

General Principles for Reducing Complexity

1. Iterative Design : Perfect design is unattainable at the start; systems evolve through incremental feature development, requiring continuous attention to design and detail.

2. Specialization and Code Reuse : Separate concerns (e.g., hardware vs. software engineers) and define clear layer interfaces to lower per‑layer complexity while enabling reuse.

3. Modularization : Decompose systems vertically into modules (e.g., micro‑services). Modules should be isolated, though this introduces new complexity in inter‑module interactions.

4. Effective Commenting : Comments capture design intent and reduce cognitive load, especially for “unknown unknowns.” Good comments improve maintainability.

4.1 Reject Tactical Programming

Tactical programming focuses on quick fixes, which accumulates complexity and makes later refactoring costly. Strategic programming invests time in design, yielding long‑term maintainability.

4.2 Design Twice

Provide multiple design alternatives to identify the optimal solution, answering both feasibility and optimality under constraints.

5. Layering

Layered architectures limit each layer’s visibility to adjacent layers, simplifying reasoning and maintenance. The TCP/IP model exemplifies effective layering.

5.1 Complexity Sinking

Push complexity as low as possible in the stack (e.g., encapsulate retry logic inside a library) to shield higher‑level users.

5.2 Exception Handling

Over‑defensive error handling inflates complexity. Prefer terminating errors when appropriate, reducing the need for upstream handling.

6. Modularization Details

6.1 Deep vs. Shallow Modules

Deep modules offer powerful functionality behind simple interfaces, while shallow modules expose complex interfaces for trivial functions.

Example of a deep module: Unix open function.

int open(const char *path, int flags, mode_t permissions);

Example of a shallow module: Java I/O chain requiring separate FileInputStream, BufferedInputStream, and ObjectInputStream.

FileInputStream fileStream = new FileInputStream(fileName);
BufferedInputStream bufferedStream = new BufferedInputStream(fileStream);
ObjectInputStream objectStream = new ObjectInputStream(bufferedStream);

6.2 General vs. Specific Modules

Design modules to be reusable when possible, but avoid premature generality that adds unnecessary features.

6.3 Information Hiding

Hide internal logic within modules to reduce cognitive load and inter‑module dependencies. Databases hide B‑tree details behind SQL, enabling flexible implementation changes.

6.4 Split and Merge

Decide whether related functionalities belong together or separately; merge shared configuration, simplify interfaces, or separate generic and specialized code as needed.

7. Commenting Practices

7.1 Common Misconceptions

Believing “good code is self‑documenting” or that comments are a waste of time ignores the value of capturing design rationale, especially for complex modules.

7.2 Writing Effective Comments

Focus on *what* and *why* rather than *how*. Use high‑level comments for overall design and low‑level comments for critical details (e.g., units, edge conditions).

7.3 Integrating Comments into Design

Write comments before coding to clarify abstractions; simple, concise comments indicate well‑designed interfaces.

Conclusion

Experienced engineers will recognize these principles, which echo ideas from classic works like *Code Complete* and *Domain‑Driven Design*. While not absolute truths, they provide actionable guidance for reducing software complexity and improving maintainability.

References

John Ousterhout. A Philosophy of Software Design . Yaknyam Press, 2018.

梅拉尼·米歇尔. 复杂 . 湖南科学技术出版社, 2016.

Martin Fowler. Refactoring: Improving the Design of Existing Code (2nd Edition) . Addison‑Wesley Signature Series, 2018.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

software designcode commentsmodular architecturecomplexity reduction
21CTO
Written by

21CTO

21CTO (21CTO.com) offers developers community, training, and services, making it your go‑to learning and service platform.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.