Reading "A Philosophy of Software Design"

November 29, 2021

In the preface that book starts with a well-known fact: that problem decomposition is the most fundamental problem in computer science. As a software developer I can only agree on it - it’s something that shows up every way. Without decomposition a problem or problem domain remains complex. Decomposing a problem into smaller parts reduces the complexity. The individual parts are still complicated but somehow manageable. The books goal is to bring some advice and suggestion on how software design should help you to deal with the complexity. Why I like is that puts on emphasize on the overall goal: the reduction of complexity. It states that every suggestion should be taken with a grain of salt and you should not blindly follow a principle that does not help with in regards to complexity. Next up I want to to highlight two parts that I found useful.

The distinction between strategic and tactical programming

Tactical programming is all about bringing features to life and fixing bugs. Tactical programming is also mindset. It may come up when a deadlines comes close. I can certainly relate to that. It feels natural at first: What could be wrong with pumping out features that will make product owners, users and other stakeholders happy?

Ousterhout notes that by purely following the tactical mindset the complexity of the software will increase overtime. And since it happens in a incremental fashion it may stay unnoticed for some time. But inevitably it will show up and cause headaches for developers. The need for refactoring will come up. You will be forced to either slow down on the current task and do a larger refactoring or to double down with the tactical approach. If you choose the last option it will most likely end up with a mess.

A person that is purely focused on tactical programming is by Ousterhout defined as a tactical tornado. He can pump out features way faster than everybody else in a team or organization. Depending on the organization you work in a tactical tornado might be be highly valued.

Strategical programming on the contrary is not solely focused on producing working code. The goal here is to produce code with “a great design, which that also happens to work”. The long term structure of code is given major attention. This implies some investment. Investment for example to experiment with different design ideas and not sticking with first idea or pattern that comes to mind. When design issues show up you take time to improve the underlying model instead of adding on patches that add complexity. Upfront investments can pay off later. Not all necessary pay off - especially when don’t know all the details up front. Ousterhout recommends that about 10-20% of the development time should be used for investments. That is inline what I have experienced with time that is worthing spending for refactoring. It has to happen on a continuos base. My takeaway from the distinction of strategic and tactical programming is mostly about being aware in what mindset you are and the also dangers of solely pursing tactical style.

Deep modules vs shallow modules

In chapter 4 of the book Ousterhout explains his preference for deep modules compared to shallow ones. A module can be seen as just one function, class or bigger structure. Deep modules pack lot’s of functionality in it while having a relatively simple interface. Shallows modules on the opposite have a relatively large interface compared to what they actual do. For him shallow modules are a red flag. His main motivation is that a shallow module does not help a lot when it comes to tackling complexity. The cost of using a shallow module is simply not worth the effort of using it. He comes up with some examples in the book. Reading that I initially nodded with my head. Reading a class with a huge set of public methods makes it harder to understands its purpose. But like in most cases it depends on how you define a simple interface. A small interface certainly should not mistaken by a simple one. Otherwise you could always shrink down a class to some magic, all-encompassing execute(…) - method. All use cases would then need to be exposed through numerous and complex parameters.

How to split things (or not)

In chapter 9 “Better together or bether apart” John explains his strategy on how to split code apart. What defines wether a class or method should be split apart or not? Again it’s all about managing complexity.

Components/Class level

When it comes to classes those questions might help:

  • Are two classes often used in conjunction ?
  • Do they share information ?
  • Do they even contain duplicate code ?

Those are signs that classes or components should probably be merged. If they have their own individual purpose and responsibility they can stay apart. Every class or additional component add some complexity just by it’s existence. Creating it should justify the small increment in complexity.

Method level

Long methods are hard to reason about - that is something nearly every programmer would agree on. Now - does splitting them apart make thing automatically easier? Not necessarily as John states. Splitting methods apart to follow some kind metric or dogma will not result in better code. Methods need be split in away that makes them both easier to reason about. If both methods are conjoined it will be harder to for the reader. Have you ever seen two or more methods were you always had to jump between them to gain understanding? That’s quite necessarily a red flag for him as well.

Things I am (somewhat) disagreeing with

In one of the last chapters Ousterhout considers common patterns in trends in the software industry. He states that he is not a big fan of using Test-driven development (TDD). According to him TDD focuses on getting things done and not on coming up with the best design. Although I am not a 100% TDD practitioner by myself, I have to disagree here. Writing tests before starting out with the implementation forces to think about interfaces. On the contrary starting with implementation and forming the interfaces afterwards might lead to a design where the interface doesn’t offer lot of abstraction.

Conclusion/TLDR

It’s a good book. What I particularly like is the size - it’s only about 180 pages long and quite dense. There are short code examples within most of the chapters. Ousterhout also presents different opinions (e.g. in chapter 14 about naming conventions). At the end each chapter he comes up with a conclusion. John Ousterhout teaches principles in a modest way. At no time I had the feeling that he is trying to convince the reader.

If you want get more information head out to Johns page about the book. As of today a free PDF is available that contains the additions that were made in the 2nd edition of the book.