Kevin Dallian's Portfolio Website

Rethinking Software Design: A Review of A Philosophy of Software Design

Software Engineer|Book Review|Clean Code

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 = 3 actually 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.