More on this book
Community
Kindle Notes & Highlights
the greatest limitation in writing software is our ability to understand the systems we are creating.
Complexity is anything related to the structure of a software system that makes it hard to understand and modify the system.
Isolating complexity in a place where it will never be seen is almost as good as eliminating the complexity entirely.
Sometimes an approach that requires more lines of code is actually simpler, because it reduces cognitive load.
An unknown unknown means that there is something you need to know, but there is no way for you to find out what it is, or even whether there is an issue.
Dependencies lead to change amplification and a high cognitive load. Obscurity creates unknown unknowns, and also contributes to cognitive load.
It’s easy to convince yourself that a little bit of complexity introduced by your current change is no big deal. However, if every developer takes this approach for every change, complexity accumulates rapidly. Once complexity has accumulated, it is hard to eliminate, since fixing a single dependency or obscurity will not, by itself, make a big difference. In order to slow the growth of complexity, you must adopt a “zero tolerance” philosophy,
Most programmers approach software development with a mindset I call tactical programming. In the tactical approach, your main focus is to get something working, such as a new feature or a bug fix.
complexity is incremental.
you program tactically, each programming task will contribute a few of these complexities.
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.
the best approach is to make lots of small investments on a continual basis. I suggest spending about 10–20% of your total development time on investments.
That extra time will result in a better software design, and you will start experiencing the benefits within a few months. It won’t be long before you’re developing at least 10–20% faster than you would if you had programmed tactically. At this point your investments become free: the benefits from your past investments will save enough time to cover the cost of future investments.
good design eventually pays for itself, and sooner than you might think.
think of investment as something to do today, not tomorrow.
The best modules are those whose interfaces are much simpler than their implementations.
interfaces should be designed to make the common case as simple as possible
Information leakage occurs when the same knowledge is used in multiple places, such as two different classes that both understand the format of a particular type of file.
When designing modules, focus on the knowledge that’s needed to perform each task, not the order in which tasks occur.
information hiding can often be improved by making a class slightly larger.
If you take the special-purpose approach and discover additional uses later, you can always refactor it to make it general-purpose. The special-purpose approach seems consistent with an incremental approach to software development.
A pass-through method is one that does nothing except pass its arguments to another method, usually with the same API as the pass-through method. This typically indicates that there is not a clean division of responsibility between the classes.
it is more important for a module to have a simple interface than a simple implementation.
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.
When you encounter a class that includes both general-purpose and special-purpose features for the same abstraction, see if the class can be separated into two classes, one containing the general-purpose features, and the other layered on top of it to provide the special-purpose features.
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.
Designing software is hard, so it’s unlikely that your first thoughts about how to structure a module or system will produce the best design.
If users must read the code of a method in order to use it, then there is no abstraction:
comments should describe things that aren’t obvious from the code.
Developers should be able to understand the abstraction provided by a module without reading any code other than its externally visible declarations. The only way to do this is by supplementing the declarations with comments.
If the information in a comment is already obvious from the code next to the comment, then the comment isn’t helpful. One example of this is when the comment uses the same words that make up the name of the thing it is describing.
use different words in the comment from those in the name of the entity being described.
Comments augment the code by providing information at a different level of detail.
you want code that presents good abstractions, you must document those abstractions with comments.
If interface comments must also describe the implementation, then the class or method is shallow.
The main goal of implementation comments is to help readers understand what the code is doing (not how it does it).
When following the rule that comments should describe things that aren’t obvious from the code, “obvious” is from the perspective of someone reading your code for the first time (not you). When writing comments, try to put yourself in the mindset of the reader and ask yourself what are the key things he or she will need to know. If your code is undergoing review and a reviewer tells you that something is not obvious, don’t argue with them; if a reader thinks it’s not obvious, then it’s not obvious. Instead of arguing, try to understand what they found confusing and see if you can clarify that,
...more
The comment that describes a method or variable should be simple and yet complete. If you find it difficult to write such a comment, that’s an indicator that there may be a problem with the design of the thing you are describing.
Ideally, when you have finished with each change, the system will have the structure it would have had if you had designed it from the start with that change in mind.
an investment mindset sometimes conflicts with the realities of commercial software development.
The best way to ensure that comments get updated is to position them close to the code they describe,
If information is already documented someplace outside your program, don’t repeat the documentation inside the program; just reference the external documentation.
Having a “better idea” is not a sufficient excuse to introduce inconsistencies.
software should be designed for ease of reading, not ease of writing.
the increments of development should be abstractions, not features.
The problem with test-driven development is that it focuses attention on getting specific features working, rather than finding the best design. This is tactical programming pure and simple, with all of its disadvantages. Test-driven development is too incremental: at any point in time, it’s tempting to just hack in the next feature to make the next test pass. There’s no obvious time to do design, so it’s easy to end up with a mess.
Whenever you encounter a proposal for a new software development paradigm, challenge it from the standpoint of complexity:
Complexity is incremental: you have to sweat the small stuff (see p. 11). Working code isn’t enough (see p. 14). Make continual small investments to improve system design (see p. 15). Modules should be deep (see p. 22) Interfaces should be designed to make the most common usage as simple as possible (see p. 27). It’s more important for a module to have a simple interface than a simple implementation (see pp. 55, 71). General-purpose modules are deeper (see p. 39). Separate general-purpose and special-purpose code (see p. 62). Different layers should have different abstractions (see p. 45).
...more