More on this book
Community
Kindle Notes & Highlights
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.
Complexity is anything related to the structure of a software system that makes it hard to understand and modify the system.
If a software system is hard to understand and modify, then it is complicated; if it is easy to understand and modify, then it is simple.
The overall complexity of a system (C) is determined by the complexity of each part p (cp) weighted by the fraction of time developers spend working on that part (tp). Isolating complexity in a place where it will never be seen is almost as good as eliminating the complexity entirely.
Complexity is more apparent to readers than writers. If you write a piece of code and it seems simple to you, but other people think it is complex, then it is complex.
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.
Change amplification: The first symptom of complexity is that a seemingly simple change requires code modifications in many different places.
Cognitive load: 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.
Unknown unknowns: 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.
One of the most important goals of good design is for a system to be obvious.
Complexity is caused by two things: dependencies and obscurity.
The second cause of complexity is obscurity. Obscurity occurs when important information is not obvious.
Complexity isn’t caused by a single catastrophic error; it accumulates in lots of small chunks.
The tactical tornado is a prolific programmer who pumps out code far faster than others but works in a totally tactical fashion.
However, tactical tornadoes leave behind a wake of destruction. They are rarely considered heroes by the engineers who must work with their code in the future.
Over time, these mistakes will become obvious. When you discover a design problem, don’t just ignore it or patch around it; take a little extra time to fix it.
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:
One of the most important techniques for managing software complexity is to design systems so that developers only need to face a small fraction of the overall complexity at any given time.
The best modules are those whose interfaces are much simpler than their implementations.
An abstraction that omits important details is a false abstraction
By separating the interface of a module from its implementation, we can hide the complexity of the implementation from the rest of the system.
The basic idea is that each module should encapsulate a few pieces of knowledge, which represent design decisions.
This creates a dependency between the modules: any change to that design decision will require changes to all of the involved modules.
Information leakage is one of the most important red flags in software design.
When designing modules, focus on the knowledge that’s needed to perform each task, not the order in which tasks occur
The best features are the ones you get without even knowing they exist.
In reviewing student projects I noticed that general-purpose classes were almost always better than special-purpose alternatives.
One of the most important elements of software design is determining who needs to know what, and when.
The best way to do this is by designing the normal case in a way that automatically handles the edge conditions without any extra code.
Specialization can’t be eliminated completely, but with good design you should be able to reduce it significantly and separate specialized code from general-purpose code. This will result in deeper classes, better information hiding, and simpler and more obvious code.
The motivation for decorators is to separate special-purpose extensions of a class from a more generic core.
Contexts may also create thread-safety issues; the best way to avoid problems is for variables in a context to be immutable. Unfortunately, I haven’t found a better solution than contexts.
it is more important for a module to have a simple interface than a simple implementation.
This separation added complexity with no benefit. The logging methods were shallow: most consisted of a single line of code, but they required a considerable amount of documentation.
Methods containing hundreds of lines of code are fine if they have a simple signature and are easy to read. These methods are deep (lots of functionality, simple interface), which is good.
When designing methods, the most important goal is to provide clean abstractions. Each method should do one thing and do it completely.
red flag (“Conjoined Methods”)
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.
A recent study found that more than 90% of catastrophic failures in distributed data-intensive systems were caused by incorrect error handling1.
Exception masking is an example of pulling complexity downward.
no-one is good enough to get it right with their first try.
I hope these chapters will convince you of three things: good comments can make a big difference in the overall quality of software; it isn’t hard to write good comments; and (this may be hard to believe) writing comments can actually be fun.
When developers don’t write comments, they usually justify their behavior with one or more of the following excuses: “Good code is self-documenting.” “I don’t have time to write comments.” “Comments get out of date and become misleading.” “The comments I have seen are all worthless; why bother?”
Code reviews provide a great mechanism for detecting and fixing stale comments.
The overall idea behind comments is to capture information that was in the mind of the designer but couldn’t be represented in the code.