I was asked recently for my recommendations for books on software design; specifically for “building good habits ideally, and not specific to any particular language”.

My model of software development looks something like the following:

design continuum
  • The Design part of this model resides in our minds — it is how we intend to solve the problem at hand.

  • The Documentation and Code parts are derived from this intention:

    • Documentation refers to communication intended for human readers.

    • Code is communication meant for both machines and human readers.

  • Our Code gets deployed on one or more machines in the Implementation part of the model.  This part also includes the human processes that keep the system operational.

The background color gradient in the figure is meant to signify that Design, Documentation/Code, and Implementation are aspects of a whole.  All three aspects —Design, Documentation/Code, Implementation — can (and should!) occur concurrently.

These aspects also influence each other:

  • Real-world (i.e., implementation-level) constraints often determine our Design choices.  For example, a design that works well enough within a data center may perform poorly when deployed on to a low-bandwidth / high-latency network.

  • There are also subtler processes going on — for example, the Sapir-Whorf Hypothesis seems to apply to the software design process.  This hypothesis states that the structure of a (human) language influences how that language’s user perceives the world.  Translated to software development, the hypothesis states that the programming paradigms that we ‘think in’ will influence the range of designs that we are able to conceptualize.

Documentation artifacts are not “designs”: we do not usually express every aspect of our designs in design documentation — that would be too much detail.  Human language is also not precise enough for use as a design notation.  Instead, such documents generally contain just those elements of our Design that are relevant to another human reader.

But Code does not capture our intentions (Design) fully either: we think of Code as specifying the state transitions that we want our computer to follow.  But in many real-world programming languages, the set of state transitions that are actually being specified by our Code could well be a superset of what we intended.  Consider the following small snippet of C code:

Integer addition in C99
int a, b, c;
a = ...; // Some value.
b = ...; // Some other value.

c = a + b;  // What value does 'c' hold?

At the end of this code fragment the value in variable c could be the sum of the values in variables a and b, or could be something entirely indeterminate if an integer overflow or underflow had occurred during addition.

So, in my view, becoming a good software designer involves:

  • Transcending the constraints in our thinking implied by the Sapir-Whorf hypothesis.

  • Understanding well the characteristics of the tools used for specifying designs.

  • Communicating clearly, whether in the form of Documentation or Code.

The reading recommendations below are being made with these points in mind.

Where possible, I have tried to recommend material that is freely available to read.

But that said, the best way to learn good software design is to observe how someone who is an exceptionally good software designer goes about designing software.  In this regard I was very lucky to have been able to observe Karthik Gargi at his work.

Here we come across a tremendous fact; namely, that a language, any language, has at its bottom certain metaphysics, which ascribe, consciously or unconsciously, some sort of structure to this world. […​] We do not realize what tremendous power the structure of an habitual language has.  It is not an exaggeration to say that it enslaves us through the mechanism of s.r [semantic-evaluational-reactions] and that the structure which a language exhibits, and impresses upon us unconsciously, is automatically projected upon the world around us.
— Alfred Korzybski
Science and Sanity, 1933.

Book Recommendations

Diagram

For Someone Just Getting Started

How to Design Programs, by Matthias Felleisen, Robert Bruce Findler, Matthew Flatt & Shriram Krishnamurthi.

A step-by-step introduction for those who are completely new to software development.

This book freely available to read on the Web.

For A Deeper Overview

Structure and Interpretation of Computer Programs, by Harold Abelson, Gerald Jay Sussman with Julie Sussman.

A masterpiece of an introduction to the field, originally used to teach introductory computer science at MIT.

The book is freely available to read on the Web.

Writing Code That Can Be Reasoned About

The Science of Programming, by David Gries.

The author argues for structuring code using the notion of ‘preconditions’ and ‘postconditions’ — a useful design habit, in my view.

Designing for Future Change

Papers by David Parnas.

As software designers, we are often exhorted to make our designs “modular”.  In practice though, there are usually multiple ways to “modularize” a design, with each way having its pros and cons.  It is often hard to decide between these alternative designs.

David Parnas’s work offers insight into this issue — I have linked to two of his papers, for you to get started.

Writing Readable Code

A Philosophy of Software Design, by John Ousterhout.

Recommends best practices for structuring code.

Modelling The Problem Domain

[…​] Lisp is a programmable programming language.  Not only can you program in Lisp (that makes it a programming language) but you can program the language itself.
— John Foderaro
CACM, September 1991.
Practical Common Lisp, by Peter Siebel.

Languages in the LISP family offer “macros”, which are a structured way to add new constructs to the language.  This allows us to directly model the problem domain that we are working in.

Practical Common Lisp introduces you to the Common Lisp language, and the use of macros, by using Lisp to tackle several real-world problems.

This book is freely available to read on the Web.

On Lisp, by Paul Graham

An introduction to the use of macros in Lisp.  This book is aimed at someone already familiar with Lisp.

This book is freely available to download on the Web.

Starting FORTH, by Leo Brodie.

The FORTH language and runtime also allows us, as programmers, to extend FORTH’s compiler to add new control constructs.

Here is an example of FORTH code that directly models the state transitions of a finite-state machine (adapted from: Finite State Machines in FORTH, J. V. Noble, 1995):

Diagram

The following (user-defined) FORTH syntax implements this state machine:

4 WIDE FSM: <Fixed.Pt#>
   ( 0 )     DROP >0    EMIT >1   EMIT >1     EMIT >2
   ( 1 )     DROP >1    EMIT >1   DROP >1     EMIT >2
   ( 2 )     DROP >2    EMIT >2   DROP >2     DROP >2 ;

The mapping between the finite state machine and the source code should be clear.

Starting Forth is freely available to read on the Web.

Preparing Correct Designs

The SPIN Model Checker, by Gerard J. Holzmann.

Model checking can verify that our designs have the properties we intend them to have.

The SPIN model checker described in the book is open-source.

Designing For Human Processes

Site Reliability Engineering, Google Inc.

This book covers some of the issues that we need to tackle as designers if we want our code to run smoothly in production.

This book is freely available to read on the Web.

Understanding The Tools

The ‘Lambda The Ultimate’ Series, by Guy L. Steele and Gerald Jay Sussman.

A series of excellent papers exploring programming language design:

Essentials of Programming Languages, by Daniel P. Friedman and Mitchell Wand.

The authors guide you through the implementation of a series of programming languages.

Category Theory For Programmers, by Bartosz Milewski.

A treasure trove of useful techniques for programmers.

This book is freely available to read on the Web.

Dealing With Complexity

There is only reliable way that I know of to keep system complexity under control is to hire fewer developers.  Generally speaking, developers are motivated to write code, while managers in organisations are motivated to hire large teams of developers — the end result is inevitably an implementation with too many “moving parts” and an enormous amount of code bloat.

The Mythical Man-Month: Essays on Software Engineering, by Frederic P. Brooks.

Brooks was one the earliest writers to point out that adding programmers to a project will generally make the project go slower.  The book’s insights remain as relevant today as when it was first published in 1975.

Communicating Your Design

Technical Communication, A Reader-Centered Approach, by Paul V. Anderson.

I read this book when it was relatively new.  I found it to be quite useful.

But that said, recent editions of the book seem costly — possibly a consequence of it being used as a textbook by US universities.

I will update this section once I find an equivalent, but more accessible, book.