Just finished “A Philosophy of Software Design” by John Ousterhout. Some thoughts from the book mixed with mine ones.
- Software design is to reduce software complexity. If the solution/design doesn't reduce the complexity, then don't use it.
- Complex system is a system which is hard to understand and modify.
- Systems, which are easier to understand aren't always the ones with less lines of code.
- Complexity isn't caused by a single big event, it accumulates over time. Little changes increasing complexity only a little, over long time might make code unmaintainable. So we should be careful with changes, which increase complexity even a little.
- Pressure to get features working ASAP might be big in business. Making a mess may lead to code being unmaintainable.
- Working code isn't enough. It also shouldn't introduce unnecessary complexity.
- Make improvements for problems as soon as you spot them. Refactor, clean, redesign, fix, etc. Leaving to deal with all the mess at once some time in the future doesn't work. It's the same as tidy your house twice per year; better a bit each time you see some dirt around.
- Decomposing software into modules controls how much complexity developer faces at once.
- Modules consist of interface (externally visible) and implementation (hidden from the module user). The smaller is the interface comparing to the functionality the module provides, the more complexity is hidden from the user (small interface and big functionality gives so called deep modules).
- Make a common used case either a default one or as simple as possible. This reduces cognitive load for the users. They don't have to be aware of the parameters which in most cases are not needed and set to default behind the scenes.
- When working on a new module, it's a reasonably good way to start with the simplest general-purpose interface, which satisfies current requirements. Later interface can be extended as needed.
- Different layer, different abstraction.
- Usually, there are way more users of a module than its developers. So it's usually more important for a module to have a simple interface than a simple implementation.
- It might be useful to put pieces of code together (functions, classes, modules etc.) when they have shared information or when it'd simplify the interface.
- Separate general-purpose code from special-purpose.
- Exceptions and their handling add complexity. Does an exception have to be raised? Can it be handled internally/at low level? Can number of exceptions be reduced? Can many exceptions be handled in a single place?
- Design it twice. Even if the first option looks good enough, alternatives can provide great insights.
- Comments should describe "what" the code is doing and "why". We don't have to write "how" the code achieves it as it's already self explanatory by the code itself. Writing "how" will most probably result in comments which duplicate code.
- Comments can show the parts of the code which are too complex. If the comment gets too long, too complex or it's even hard to write it at all, it might be a refactoring opportunity.
- Commenting before the code is written may help with the design.
- Upon every code modification, check if the system design still makes sense or can be improved.
- Be consistent. It means, follow the agreed conventions, styles, naming, structure etc. Code shouldn't surprise.
- Whenever possible, enforce the conventions you agreed on (using linters, scripts etc.).
- We spend much more time reading the code than writing it. So the code should be designed for ease of reading, not ease of writing.
- When performance matters, go with the design that's both simple and fast. If you're certain that a particular place will be slow with a good design, then it may make sense to optimize it from the beginning. However, avoid premature optimization. If the system needs improvement after it's built, don't try to improve the performance based on intuition. Instead, make performance measurements, then make changes to the system and make measurements again. In such way you'll have sufficient information to make the right decision between clean design & performance.
In the book the main theme is to make modules, classes, methods, functions etc. deep (considered deep when a lot of functionality is hiding under small interface). According to author this should reduce complexity. I do agree it sounds like a great idea, however, the more I was reading, the more "but"s came to my mind. From my not that long SE experience I remembered various examples of dealing with complexity, where this idea or tips related with it wouldn't work. So my take is that "deep" stuff idea can be more of a rule of a thumb, but by no means the end goal. The end goal is always reduced complexity. And if "deep" stuff isn't along the way, then I'd simply disregard it.
For the ones interested in alternative approaches, I recommend "Clean code" By Robert B. Martin (a.k.a. Uncle Bob).