A Philosophy of Software Design

John Ousterhout

Summary: reducing complexity of software, from the author of Tcl

Score: 85 / 100


Chapter 1: Introduction

Simpler designs allow us to build larger and more powerful systems before complexity becomes overwhelming.

To fight complexity we can:

  1. Make code simpler.
  2. Modularize it.

Modularization is useful for us humans as we have limited capacity of working memory.

Waterfall model:

----
    ----
        ----
            ----

Project is designed at the beginnins and does not allow changes. Like in normal engineering.

Agile development:

     xxxx
     x  xx    xxxx   xxxxx
x   xx   x   xx  x  xx   xx
xxxxxxxxxx   x   x  x     x
    xx       x  xx xx     x
      xxx    xxxx  x     xx
         xxxxx     x    xx
              xxxxxxxxxx
                   x
                    xx
                     xxxxxxxx

Development in agile is incremental.

Developers should always think about design issues.

Chapter 2: Complexity

Complexity = dependency + obscurity

What about simple system but with many dependencies? It might be hard to do a change/fix if a dependency breaks… Also, a framework allows for small changes but it’s hard to know what the changes should be.

Chapter 3: Strategic vs. tactical programming

Working code isn’t enough. The best way to lower costs is to hire great engineers.

Chapter 4: Modules should be deep

Interface (what module does) and implementation (how it does it).

    ------interface----
    |                 |
#######               |
|     |               |
|     |       ####################
|     |       |                  |
|     |       |          |       |
|  |  |       -----------|--------
|  |  |                  |
---|---                  |
   |                     |
   -----implementation----

Thought: modern IDEs encourage shallow modules: users don’t need to remember much, there is the IDE’s autocomplete…

Chapter 5: Information hiding (vs. leakage)

One of the best skills: a high level of sensitivity to information leakage.

Pass-through methods.

↓ ↓ ↓ ↓ |
A B C D |
  ↓ ↓ ↓ ↓
  B C D E

Solutions:

a) invoke directly

↓ | | | |
A | | | |
  ↓ ↓ ↓ ↓
  B C D E

b) redistribute functionality

↓ ↓   ↓ ↓ ↓
A B   C D E

c) combine

↓ ↓ ↓ ↓ ↓
A B C D E

Chapter 8: Complexity

Most modules have more users than developers so it is better for the developers to suffer.

Suffer instead of your user.

Chapter 9: Together or apart?

In general, the trend is to break up methods too much!

    CALLERS    CALLERS   CALLERS       CALLERS
       ↓          ↓        ↓   ↓      ↓   |   ↓
  ###########  #######  ##### ###  #####  |  #####
  |         |  |     |  |   | | |  |   |  |  |   |
  |         |  |   | |  |   | | |  | | |  ↓  -----
  -----------  ----|--  ----- ---  --|-- #########
                   ↓                 ↓   |       |
                 #####             ##### ---------
                 |   |             |   |
                 -----             -----
                         rarely        avoid!
                         works

Each method should do one thing and do it completely.

Split or join based on complexity!

Chapter 10: Define errors out of existence

Exceptions are hard to test.

Code that hasn’t been executed doesn’t work.

Exceptions make interface more complex.

Throwing is easy, handling is hard. Reduce number of places where exceptions have to be handled.

Chapter 11: Design it twice

This will help you come up with a better design.

Chapter 12: Comments (2023-06-10)

Good for:

Myth: good code is self-documentary.

To capture information in mind not representable in the code.

Chapter 13: Comments to describe the non-obvious

Distinguish (and separate) interface and implementation comments!

Chapter 14: Choosing names

Underrated aspect of software engineering. It’s a form of documentation.

(Does functional code with limited use of variables is less error-prone?)

Good names are consistent and precise.

If it’s hard to pick a name, the code might need refactoring?

Chapter 15: Write the comments first

It helps with design process. Delayed comments won’t happen at all.

Is it hard to describe/comment a code: It might be an indicator of a design problem?

Simple comments indicate good abstractions.

Chapter 16: Modifying existing code

Every change (adding a feature, refactoring, …) should improve the codebase. Is this attainable?

When you have finished with each change, the system will have the structure it would have had if you had designed it from the start with that change in mind.

If you are not making the design better you are probably making it worse.

Keep comments near the code.

The farther from code the more abstract should a comment be. It helps with obsoleting comments as well.

Refer to external documentation, do not repeat / duplicate info.

Chapter 17: Consistency 2023-06-23

Document and enforce rules. Follow local (file) conventions.

Chapter 18: Code should be obvious

Good names, consistency helps. Formatting comments (typography) helps.

General rule:

software should be designed to ease to be read not to be written.

Chapter 20: Designing for performance


Conclusion: this was well worth reading!

The author has a few useful concepts:

published: 2023-05-23
last modified: 2023-07-03

https://vit.baisa.cz/books/a-philosophy-of-software-design/