A Philosophy of Software Design By John Ousterhout
I came to know about this book in a Hacker News post and decided to order it as soon as it was published.
The book proved very useful since the first chapter. Since I read it I’ve applied many of its concepts and many times while coding I find myself thinking about Tactical vs. Strategic programming, two concepts that really stuck with me and have helped me write better code.
Tactical programming refers to writing and shipping code fast without stopping too much to think about design. This might be good for fast prototyping but it is not the correct approach when building products. Instead we should use strategic programming: plan ahead and design the system before writing any code.
If you want to know more there’s also a Google Talk by the author!
Notes and Highlights
The most fundamental problem in computer science is problem decomposition: how to take a complex problem and divide it up into pieces that can be solved independently.
If you can visualize a system, you can probably implement it in a computer program.
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.
Developer should always be thinking about design issues.
Complexity is anything related to the structure of a sotware system that makes it hard to understand and modify the system.
You can also think of complexity in terms of cost and benefit. In a complex system, it takes a lot of work to implement even small improvements. In a simple system, larger improvements can be implemented with less effort.
Complexity is more apparent to readers than writers.
Your job as a developer is not just to create code that you can work with easily, but to create code that others can also work with easily.
One of the goals of good design is to reduce the amount of code that is affected by each design decision, so design changes don’t require very many code modifications.
One of the most important goals of good design is for a system to be obvious.
Almost every software development organization has at least one developer who takes tactical programming to the extreme: a tactical tornado. The tactical tornado is a prolific programmer who pumps out code far faster than other but works in a totally tactical fashion.
They are rarely considered heroes by engineers who must work with their code in the future.
In the tactical approach, your main focus is to get something working, such as a new feature or a bug fix. The problem with tactical programming is that it is short-sighted. If you’re programming tactically, you’re trying to finish a task as quickly as possible. As a result, planning for the future isn’t a priority.
The first step towards becoming a good software designer is to realize that working code isn’t enough.
Your primary goal must be to produce a great design, which also happens to work. This is strategic programming.
Another thing to consider is that one of the most important factors for success of a company is the quality of its engineers. The best way to lower development costs is to hire great engineers: they don’t cost much more than mediocre engineers but have tremendously higher productivity.
Module depth is a way of thinking about cost versus benefit. The benefit provided by a module is its functionality.
The best modules are deep: they have a lot of functionality hidden behind a simple interface.
The smaller and simpler the interface, the less complexity that it introduces.
Providing choice is good, bu interfaces should be designed to make the common case as simple as possible.
Information hiding reduces the complexity in two ways. First, it simplifies the interface to a module. Second, information hiding makes it easier to evolve the system.
It is more important for a module to have a simple interface than a simple implementation.
If a class exports configuration parameters, every system administrator in every installation will have to learn how to set them.
Configuration parameters also provide an easy excuse to avoid dealing with important issues and pass them on to someone else. In many cases, it’s difficult or impossible for users or administrators to determine the right variables for the parameters.
Length by itself is rarely a good reason for splitting up a method. In general, developers tend to break up methods too much. Splitting up a method introduces additional interfaces, which add to complexity.
Each method should do one thing and do it completely. The method should have a clean and simple interface, so that users don’t need to have much information in their heads in order to use it correctly. The method should be deep: its interface should be much simpler than its implementation.
Throwing execptions is easy; handling them is hard.
A good software design loses much of its value if it is poorly documented.
If users must read the code of a method in order to use it, then there is no abstraction.