More on this book
Community
Kindle Notes & Highlights
Complexity is anything related to the structure of a software system that makes it hard to understand and modify the system.
Complexity is determined by the activities that are most common. If a system has a few parts that are very complicated, but those parts almost never need to be touched, then they don’t have much impact on the overall complexity of the system.
The first symptom of complexity is that a seemingly simple change requires code modifications in many different places.
The second symptom of complexity is cognitive load, which refers to how much a developer needs to know in order to complete a task.
Sometimes an approach that requires more lines of code is actually simpler, because it reduces cognitive load.
The third symptom of complexity is that it is not obvious which pieces of code must be modified to complete a task, or what information a developer must have to carry out the task successfully.
Complexity isn’t caused by a single catastrophic error; it accumulates in lots of small chunks.
The bottom line is that complexity makes it difficult and risky to modify an existing code base.
In the tactical approach, your main focus is to get something working,
working code isn’t enough.
The term technical debt is often used to describe the problems caused by tactical programming.
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. However, the best engineers care deeply about good design. If your code base is a wreck, word will get out, and this will make it harder for you to recruit. As a result, you are likely to end up with mediocre engineers. This will increase your future costs and probably cause the system structure to degrade even more.
These examples show that a company can succeed with either approach. However, it’s a lot more fun to work in a company that cares about software design and has a clean code base.
Good design doesn’t come for free. It has to be something you invest in continually, so that small problems don’t accumulate into big ones. Fortunately, good design eventually pays for itself, and sooner than you might think.
It’s crucial to be consistent in applying the strategic approach and to think of investment as something to do today, not tomorrow.
The longer you wait to address design problems, the bigger they become;
The most effective approach is one where every engineer makes continuous small investments in good design.
When designing modules, focus on the knowledge that’s needed to perform each task, not the order in which tasks occur.
When developing a module, look for opportunities to take a little bit of extra suffering upon yourself in order to reduce the suffering of your users.
This red flag occurs when a general-purpose mechanism also contains code specialized for a particular use of that mechanism. This makes the mechanism more complicated and creates information leakage between the mechanism and the particular use case: future modifications to the use case are likely to require changes to the underlying mechanism as well.
It should be possible to understand each method independently. If you can’t understand the implementation of one method without also understanding the implementation of another, that’s a red flag. This red flag can occur in other contexts as well: if two pieces of code are physically separated, but each can only be understood by looking at the other, that is a red flag.
Depth is more important than length: first make functions deep, then try to make them short enough to be easily read. Don’t sacrifice depth for length.
The decision to split or join modules should be based on complexity. Pick the structure that results in the best information hiding, the fewest dependencies, and the deepest interfaces.
File deletion provides another example of how errors can be defined away. The Windows operating system does not permit a file to be deleted if it is open in a process. This is a continual source of frustration for developers and users. In order to delete a file that is in use, the user must search through the system to find the process that has the file open, and then kill that process. Sometimes users give up and reboot their system, just so they can delete a file. The Unix operating system defines file deletion more elegantly. In Unix, if a file is open when it is deleted, Unix does not
...more
Try to pick approaches that are radically different from each other;
The design-it-twice approach not only improves your designs, but it also improves your design skills. The process of devising and comparing multiple approaches will teach you about the factors that make designs better or worse. Over time, this will make it easier for you to rule out bad designs and hone in on really great ones.
comments are fundamental to abstractions.
an abstraction is a simplified view of an entity, which preserves essential information but omits details that can safely be ignored.
If users must read the code of a method in order to use it, then there is no abstraction:
If you want to use abstractions to hide complexity, comments are essential.
Good documentation helps with the last two of these issues. Documentation can reduce cognitive load by providing developers with the information they need to make changes and by making it easy for developers to ignore information that is irrelevant.
Well-written comments are not failures. They increase the value of code and serve a fundamental role in defining abstractions and managing system complexity.