Rethinking Software Design: A Review of A Philosophy of Software Design
26 Jan 2026
At December 2025, I've finished reading John Ousterhout’s book: a Philosophy of Software Design. This book is also my first programming related book and it made me rethink the way how to design software's architecture and avoid complexity. In this blog, I will be sharing my takes on the book without any major spoilers.
Dealing with Complexity in a Running Software
The early chapters explain on the problem that occurs in long-lived systems: complexity. The book frames complexity as something you feel when writing code, especially when making changes that should have been simple but aren't. The book explores symptoms of complexity, what causes them and how to mitigate them in an existing codebase.
One perspective that stood out to me is how complexity are evaluated not by the author's intent, but by how they are experienced by other developers over time. It subtly shifts the question from "does this make sense to me?" to "how hard will this be for the next person to work with?"
Imagine you're asked to make a small UI changes, to show a badge for suspended users. You open an existing SwiftUI screen and find this ViewModel.
final class UserViewModel: ObservableObject {
@Published var userTypeCode: Int
init(userTypeCode: Int) {
self.userTypeCode = userTypeCode
}
var statusText: String {
switch userTypeCode {
case 1:
return "Regular"
case 2:
return "Premium"
case 3:
return "Trial"
case 4:
return "Suspended"
default:
return "Unknown"
}
}
var shouldShowBadge: Bool {
userTypeCode == 2 || userTypeCode == 3
}
}From the original author’s point of view, this might have felt straightforward:
-
One numeric code
-
Simple computed properties
-
Easy to add new cases
But as the next developer, the experience is different:
-
What does
userTypeCode = 3actually mean? -
Why does Trial show a badge but Suspended does not?
-
Is it safe to reuse this logic elsewhere?
-
Will adding a new type affect multiple UI conditions?
Now a change that sounded simple—“show a badge for suspended users”—requires tracing assumptions scattered across computed properties.
Designing Modules
Several parts of the book discuss how module design affects reusability and maintainability. Rather than focusing on how a module is implemented internally, the emphasis is on how the module is called by the code that depends on it.
This section made me more aware of how design decisions at module boundaries can either simplify integration or increase the effort required to use and maintain a system over time.
Here is a case sample using Swift
Imagine you’re integrating a payment feature. Instead of caring how payment is processed, the rest of the app only interacts with a simple interface. So the concrete implementation hide all the internal details.
// What the rest of the app sees
protocol PaymentProcessor {
func charge(amount: Decimal) throws
}
// Actual Implementation
final class StripePaymentProcessor: PaymentProcessor {
func charge(amount: Decimal) throws {
// Internal logic:
// - network requests
// - retries
// - error mapping
// - logging
// None of this leaks outside the module
}
}
// The usage stays simple
func checkout(processor: PaymentProcessor) {
try? processor.charge(amount: 10.99)
}Writing Comments that Matter
Several chapters mentioned toward comments in code. Instead of treating comments as optional or a burden for developers, the book examines why developers often avoid writing them in the first place and why those reasons tend to fall apart in long-lived systems.
The book rethinks the role of comments, not as substitute for good design but as a tool that represents context that code alone cannot express. Although the code may seems self-explanatory, other developers may not know the reason why the code are written that way.
After reading this section, I started experimenting with writing comments before writing the code itself. Instead of describing what the code does, these early comments capture the intention--what problem is being solved and why this approach was chosen. I've found that this small shift acts as a design checkpoint: if the intent is hard to explain in a few sentences, the design often needs more thought. Over time, this has made both the code and the accompanying comments more deliberate and easier to reason about.
Overall
Along with the points mentioned in the blog, the book recommends design principles that can be implemented. Additionally, we will see common examples on red flags that should be avoid when designing software and how to refactor on existing codebase. I really recommend this book to developers that wants to learn about abstractions or modular design, especially those who whave experienced the challenges of maintaining growing systems.