Last week I started a new role and ahead of this new adventure I read A Philosophy of Software Design with the specific purpose of gifting the book and a summary of it to each of my engineers. What follows is my paraphrased summary of the first 9 chapters for those who might find the topic interesting.
Chapter 1
The greatest limitation in writing software is our ability to understand the systems we are creating
The larger the program, and the more people that work on it, the more difficult it is to manage complexity
2 approaches to fighting complexity
- eliminate complexity by making code simpler and more obvious
- encapsulate it so that programmers can work on a system without being exposed to all of it’s complexity at once
Chapter 2
Complexity is anything related to the structure of a software system that makes it hard to understand and modify the system
The overall complexity of a system is defined by the complexity of each part multiplied by the amount of time developers spend working on that part
Symptoms of complexity
- change amplification -a seemingly simple change requires code modifications in many different parts of the system
- cognitive load -refers to how much a developer needs to know to complete a task
- unknown unknowns -it’s not obvious what pieces of code should be modified to complete a task
Causes of complexity
- dependencies -exists when a given piece of code cannot be understood and modified in isolation. We cannot eliminate dependencies so the goal is to have fewer and make it obvious how and where they are used
- obscurity - when important information is not obvious
- honorable mention - Inconsistency (as a contributor to obscurity)
Complexity comes from an accumulation of dependencies and obscurities
Chapter 3
Complexity is incremental
Working code isn’t enough because it can introduce complexity if done purely tactical
The more strategic approach strikes a balance between design and working code
Chapter 4
One of the most important techniques for managing complexity is to design systems so that developers only need to face a small fraction of the overall complexity at any given time
Software systems are decomposed into a collection of modules that are relatively independent
The arguments to a function create a dependency between the function and the call site
The goal of modular design is to minimize the dependencies between modules
The best modules provide an interface that is much simpler than its implementation
If a developer needs to know a particular piece of information in order to use a module, that information is part of that modules interface
The best modules are deep: they have a lot of functionality hidden behind a simple interface
A deep module is a good abstraction because only a small fraction of its internal complexity is visible to the user
A shallow module is one whose interface is relatively complex in comparison to the functionality that it provides
By separating the interface of a module from its implementation, we can hide the complexity of the implementation from the rest of the system
Chapter 5
The most important technique for achieving deep modules is information hiding
Each module should encapsulate a few pieces of knowledge, which represent design decisions. And this knowledge is embedded in the modules implementation but does not appear in its interface
Information hiding reduces complexity in 2 ways
- it simplifies the interface to a module reducing cognitive load on the developer using the module
- it makes it easier to evolve the system. No dependencies so any change will effect only the one module
The opposite of information hiding is information leakage. This occurs when a design decision is reflected in multiple modules. This creates a dependency between the modules
If a piece of information is reflected in the interface for a module, then by definition it has been leaked; thus simpler interfaces tend to correlate with better information hiding
Back door leakage (that which is not visible in the interface) is more harmful because it’s not obvious
Information hiding can often be improved by making a module slightly larger
ex: 2 modules need to be run in a specific order, combining the 2 can hide this complexity
Chapter 6
The modules functionality should reflect your current needs, but its interface should not. Instead the interface should be general enough to support multiple uses
But don't get carried away and build something so general purpose that it is difficult to use
The most important benefit of the general purpose approach is that it results in simpler and deeper interfaces when compared to the special purpose approach
One of the most important elements of software design is determining who needs to know what, and when
Questions to ask about the design
- what is the simplest interface that will cover all my current needs
- in how many situations will this function be used
- is this api easy to use for my current needs
General purpose interfaces provide a cleaner separation between modules, whereas special purpose interfaces tend to leak information between modules
Chapter 7
When adjacent layers have similar abstractions, the problem often manifests itself in the form of pass-through functions
A pass-through function typically indicates that there is not a clean division of responsibility between the modules
A pass-through function makes modules shallow; they increase the interface complexity of a module, which adds complexity without increasing the total functionality of the system
Chapter 8
Pulling complexity down makes the most sense when...
- the complexity pulled down is closely related to the modules existing functionality
- it results in simplifications elsewhere in the application
- it simplifies the modules interface
Chapter 9
The act of subdividing creates additional complexity that was not present before subdivision
- complexity can arise from the number of modules. The more you have, the more difficult it can be to keep track of them all and the more challenging it can be to find a desired component within the large collection (ie: more interfaces, every interface adds complexity)
- you may need code to manage the collection of modules
- subdivision can result in code duplication.
The act of combing code could be beneficial when...
- they share information
- they overlap conceptually
- it is difficult to understand one of the modules without also looking at the other
- it results in a simpler interface for those who rely on the behavior(s)