Skip to content

Instantly share code, notes, and snippets.

@nchelluri
Last active January 5, 2021 15:39
Show Gist options
  • Save nchelluri/c7c0635bddaa7d67687c411ea7621b21 to your computer and use it in GitHub Desktop.
Save nchelluri/c7c0635bddaa7d67687c411ea7621b21 to your computer and use it in GitHub Desktop.

Notes on A Philosophy of Software Design, by John Ousterhout

Chapter 1 - Introduction (It's All About Complexity)

All programming requires is a creative mind and the ability to organize your thoughts. If you can visualize a system, you can probably implement it in a computer program.

This means that the greatest limitation in writing software is our ability to understand the systems we are creating. As a program evolves and acquires more features, it becomes complicated, with subtle dependencies between its components. Over time, complexity accumulates, and it becomes harder and harder for programmers to keep all of the relevant factors in their minds as they modify the system. [...] Complexity increases inevitably over the life of any program.

Good development tools can help us deal with complexity, and many great tools have been created over the last several decades. But there is a limit to what we can do with tools alone. If we want to make it easier to write software, so that we can build more powerful systems cheaply, we must find ways to make software simpler.

Two general approaches to fighting complexity:

  • eliminate complexity by making software simpler and more obvious
  • encaspulating complexity so that programmers can work on other areas of the code without understanding the complexity ("modular design")

There's a brief discussion of why incremental development (as opposed to waterfall) is more suited to software than other forms of creation (it's hard to visualize the best design at the outset).

Incremental development means that software design is never done. Design happens continuously over the life a system: developers should always be thinking about design issues.

This book is about how to use complexity to guide the design of software throughout its lifetime.

Two overall goals of the book: describing the nature of complexity ("what it means, why does it matter, and how you can recognize when a program has unnecessary complexity"), and its "second, more challenging goal" is "to present techniques you can use during the software development process to minimize complexity".

Unfortunately, there isn't a simple recipe that will guarantee great software designs. Instead, I will present a collection of higher level concepts [...] These concepts may not immediately identify the best design, but you can use them to compare design alternatives and guide your exploration of the design space.

1.1 How to use this book

  • Best used in conjunction with code reviews ("It's easier to see design problems in someone else's code than your own.")
  • Learn to recognize "red flags": "signs that a piece of code is more complicated than it needs to be".
    • "When you see a red flag, stop and look for an alternate design that eliminates the problem."

    • This is a skill that is built up and grows over time.
  • Every idea can be taken too far. He'll present sections on how to avoid doing that.

Chapter 2 - The Nature of Complexity

This book is about how to design software systems to minimize their complexity. [...] Exactly what is "complexity"? How can you tell if a system is unnecessarily complex? What causes system to become complex? This chapter will address those questions at a high level; subsequent chapters will show you how to recognize complexity at a lower level, in terms of specific structural features.

The ability to recognize complexity is a crucial design skill.

  • it allows you identify problems before you invest a lot of time in them
  • it allows you to make good choices among alternatives
  • Over time, you will notice that certain techniques tend to result in simpler designs, while others correlate with complexity.

2.1 Complexity defined

Complexity is anything related to the structure of a software system that makes it hard to understand and modify the system.

It takes many forms:

  • it might be hard to understand how a piece of code works
  • it might take a lot of effort to implement a small improvement
  • it might not be clear which parts of the system must be modified to make an improvement
  • it might be difficult to fix one bug without introducing another

C = Sigma over p of cp * tp where p are the parts, C is the complexity of a system, cp is the complexity of a part, and tp is the fraction of time develoeprs spend working on that part.

Isolating complexity in a place where it will never be seen is almost as good as eliminating the complexity entirely.

Complexity is more apparent to readers [of code] than writers [of code].

If you write a piece of code and it seems simple to you, but others think it is complex, then it is complex. When you find yourself in situations like this, it's worth probing the other developers to find out why the code seems complex to them; there are probably some interesting lessons to learn from the disconnect between your opinion and theirs. Your job as a developer is not just to create code that you can work with easily, but to create code that others can work with easily.

2.2 Symptoms of complexity

  • Change amplification:

    • A seemingly simple change requires code modifications in many places.

  • Cognitive load:

    • How much a developer needs to know in order to complete a task.

    • A higher cognitive load means that developers have to spend more time learning the required information, and there is a greater risk of bugs because they have missed something important.

    • Sometimes an approach that requires more lines of code is actually simpler, because it reduces cognitive load.

  • Unknown unknowns:

    • When it is not obvious which pieces of code must be modified to complete a task, or what information a developer must have to carry out the task successfully.
    • Of these three manifestations of complexity, unknown unknowns are the worst.

    • You won't find out about a bug until after it's written. It is unclear whether a proposed solution will work.
  • One of the most important goals of good design is for a system to be obvious. This is the opposite of high cognitive load and unknown unknowns. [...] An obvious system is one in where a developer can make a quick guess about what to do, without thinking very hard and yet be confident that the guess is correct.

More on obvious design in ch. 18.

He uses the example of a website with many pages that have the same background color. In fig 2.1(a) every page has the color hardcoded within it. In fig 2.1(b) a shared variable holds the color and every pages uses that variable. In fig 2.1(c) some pages use a different color instead, a variation of the shared variable, for emphasis (i.e. darkred instead of red).

2.3 Causes of complexity

  • Dependencies

    • A dependency exists when a given piece of code cannot be understood and modified in isolation[.]

    • In fig 2.1(a), the background color creates a dependency on all of the pages. Changing the color on the website requires changing all of the pages.
    • [O]ne of the goals of software design is to reduce the number of dependencies and to make the dependencies that remain as obvious as spossible.

  • Obscurity

    • Obscurity occurs when important imformation is not obvious.

    • In fig 2.1(c), he talks about unknown unknowns, where the emphasis color must be changed but that is not obvious because it's not defined in the same place as the shared background color variable.
    • Inconsistency is [...] a major contributor to obscurity[.]

    • Ch. 13 discusses inadequate documentation, but here he says "If a system has a clean and obvious design, then it will need less documentation. The need for extensive documentation is often a red flag that the design isn't quite right."
  • Dependencies lead to change amplification and a high cognitive load. Obscurity creates unknown unknowns, and also contribute to cognitive load.

2.4 Complexity is incremental

  • Complexity isn't caused by a single catastrophic error; it accumulates in lots of small chunks.

  • The incremental nature of complexity makes it hard to control. It's easy to convince yourself that a little bit of complexity introduced by your current change is no big deal. However, if every developer takes this approach for every change, complexity accumulates rapidly.

  • And once it's accumulated, it's hard to eliminate.
  • He'll discuss a "zero tolerance" philosophy in ch. 3.

2.5 Conclusion

Complexity comes from an accumulation dependencies and obscurities.

The bottom line is that complexity makes it difficult and risky to modify an existing code base.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment