# A Philosophy of Software Design

## Metadata
- Author: [[John Ousterhout]]
- Full Title: A Philosophy of Software Design
- Category: #books
- Source: kindle
- Last Highlighted: 2021-04-02 04:56:00+00:00
## Highlights
- David Parnas’ classic paper “On the Criteria to be used in Decomposing Systems into Modules” appeared in 1971, but the state of the art in software design has not progressed much beyond that paper in the ensuing 45 years. ([Location 196](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=196))
- 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. ([Location 198](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=198))
- In addition, there is a huge variation in quality and productivity among programmers, but we have made little attempt to understand what makes the best programmers so much better or to teach those skills in our classes. ([Location 202](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=202))
- However, there is quite a bit of scientific evidence that outstanding performance in many fields is related more to high-quality practice than innate ability (see, for example, Talent is Overrated by Geoff Colvin). ([Location 205](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=205))
- In an English class, students use an iterative process where they write a draft, get feedback, and then rewrite to make improvements. ([Location 211](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=211))
- Kozyrakis suggested the terms “deep” and “shallow” for classes and interfaces, replacing previous terms “thick” and “thin”, which were somewhat ambiguous. ([Location 248](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=248))
- Writing computer software is one of the purest creative activities in the history of the human race. ([Location 254](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=254))
- This means that the greatest limitation in writing software is our ability to understand the systems we are creating. ([Location 257](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=257))
- 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. ([Location 259](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=259))
- The first approach is to eliminate complexity by making code simpler and more obvious. ([Location 267](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=267))
- The second approach to complexity is to encapsulate it, so that programmers can work on a system without being exposed to all of its complexity at once. ([Location 268](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=268))
- In modular design, a software system is divided up into modules, such as classes in an object-oriented language. The modules are designed to be relatively independent of each other, so that a programmer can work on one module without having to understand the details of other modules. ([Location 269](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=269))
- Because software is so malleable, software design is a continuous process that spans the entire lifecycle of a software system; this makes software design different from the design of physical systems such as buildings, ships, or bridges. ([Location 272](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=272))
- The extreme of this approach is called the waterfall model, in which a project is divided into discrete phases such as requirements definition, design, coding, testing, and maintenance. ([Location 275](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=275))
- it isn’t possible to visualize the design for a large software system well enough to understand all of its implications before building anything. ([Location 279](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=279))
- The incremental approach works for software because software is malleable enough to allow significant design changes partway through implementation. ([Location 289](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=289))
- One of the best ways to improve your design skills is to learn to recognize red flags: signs that a piece of code is probably more complicated than it needs to be. ([Location 311](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=311))
- Every rule has its exceptions, and every principle has its limits. If you take any design idea to its extreme, you will probably end up in a bad place. ([Location 318](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=318))
- Beautiful designs reflect a balance between competing ideas and approaches. ([Location 320](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=320))
- Complexity is anything related to the structure of a software system that makes it hard to understand and modify the system. ([Location 339](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=339))
- might be hard to understand how a piece of code works; it might take a lot of effort to implement a small improvement, or it might not be clear which parts of the system must be modified to make the improvement; it might be difficult to fix one bug without introducing another. ([Location 340](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=340))
- People often use the word “complex” to describe large systems with sophisticated features, but if such a system is easy to work on, then, for the purposes of this book, it is not complex. ([Location 346](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=346))
- Isolating complexity in a place where it will never be seen is almost as good as eliminating the complexity entirely. ([Location 354](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=354))
- 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. ([Location 355](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=355))
- Change amplification: The first symptom of complexity is that a seemingly simple change requires code modifications in many different places. ([Location 361](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=361))
- 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. ([Location 367](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=367))
- 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. ([Location 369](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=369))
- Cognitive load arises in many ways, such as APIs with many methods, global variables, inconsistencies, and dependencies between modules. ([Location 374](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=374))
- Sometimes an approach that requires more lines of code is actually simpler, because it reduces cognitive load. ([Location 379](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=379))
- 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. ([Location 384](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=384))
- 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. ([Location 391](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=391))
- Change amplification is annoying, but as long as it is clear which code needs to be modified, the system will work once the change has been completed. ([Location 393](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=393))
- Similarly, a high cognitive load will increase the cost of a change, but if it is clear which information to read, the change is still likely to be correct. ([Location 394](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=394))
- One of the most important goals of good design is for a system to be obvious. ([Location 398](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=398))
- In an obvious system, a developer can quickly understand how the existing code works and what is required to make a change. ([Location 399](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=399))
- An obvious system is one where a developer can make a quick guess about what to do, without thinking very hard, and yet be confident that the guess is correct. ([Location 399](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=399))
- Complexity is caused by two things: dependencies and obscurity. ([Location 404](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=404))
- a dependency exists when a given piece of code cannot be understood and modified in isolation; the code relates in some way to other code, and the other code must be considered and/or modified if the given code is changed. ([Location 406](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=406))
- Another example of dependencies occurs in network protocols. Typically there is separate code for the sender and receiver for the protocol, but they must each conform to the protocol; changing the code for the sender almost always requires corresponding changes at the receiver, and vice versa. ([Location 409](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=409))
- The signature of a method creates a dependency between the implementation of that method and the code that invokes it: if a new parameter is added to a method, all of the invocations of that method must be modified to specify that parameter. ([Location 411](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=411))
- However, one of the goals of software design is to reduce the number of dependencies and to make the dependencies that remain as simple and obvious as possible. ([Location 415](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=415))
- Furthermore, compilers help to manage API dependencies: if the name of the shared variable changes, compilation errors will occur in any code that still uses the old name. ([Location 421](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=421))
- The second cause of complexity is obscurity. Obscurity occurs when important information is not obvious. ([Location 423](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=423))
- Obscurity is often associated with dependencies, where it is not obvious that a dependency exists. ([Location 426](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=426))
- Inconsistency is also a major contributor to obscurity: if the same variable name is used for two different purposes, it won’t be obvious to developer which of these purposes a particular variable serves. ([Location 428](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=428))
- In many cases, obscurity comes about because of inadequate documentation; ([Location 430](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=430))
- The need for extensive documentation is often a red flag that the design isn’t quite right. ([Location 432](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=432))
- The best way to reduce obscurity is by simplifying the system design. ([Location 432](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=432))
- Dependencies lead to change amplification and a high cognitive load. ([Location 434](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=434))
- Obscurity creates unknown unknowns, and also contributes to cognitive load. ([Location 435](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=435))
- If we can find design techniques that minimize dependencies and obscurity, then we can reduce the complexity of software. ([Location 435](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=435))
- Complexity isn’t caused by a single catastrophic error; it accumulates in lots of small chunks. ([Location 437](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=437))
- The incremental nature of complexity makes it hard to control. ([Location 440](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=440))
- In order to slow the growth of complexity, you must adopt a “zero tolerance” philosophy, ([Location 443](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=443))
- Complexity comes from an accumulation of dependencies and obscurities. As complexity increases, it leads to change amplification, a high cognitive load, and unknown unknowns. ([Location 446](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=446))
- One of the most important elements of good software design is the mindset you adopt when you approach a programming task. ([Location 452](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=452))
- However, if you want a good design, you must take a more strategic approach where you invest time to produce clean designs and fix problems. ([Location 454](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=454))
- In the tactical approach, your main focus is to get something working, such as a new feature or a bug fix. ([Location 457](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=457))
- However, tactical programming makes it nearly impossible to produce a good system design. ([Location 459](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=459))
- The problem with tactical programming is that it is short-sighted. ([Location 460](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=460))
- However, the complexities accumulate rapidly, especially if everyone is programming tactically. ([Location 466](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=466))
- Almost every software development organization has at least one developer who takes tactical programming to the extreme: a tactical tornado. ([Location 475](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=475))
- The tactical tornado is a prolific programmer who pumps out code far faster than others but works in a totally tactical fashion. ([Location 476](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=476))
- Typically, other engineers must clean up the messes left behind by the tactical tornado, which makes it appear that those engineers (who are the real heroes) are making slower progress than the tactical tornado. ([Location 479](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=479))
- The first step towards becoming a good software designer is to realize that working code isn’t enough. ([Location 482](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=482))
- The most important thing is the long-term structure of the system. ([Location 483](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=483))
- Most of the code in any system is written by extending the existing code base, so your most important job as a developer is to facilitate those future extensions. ([Location 484](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=484))
- Your primary goal must be to produce a great design, which also happens to work. This is strategic programming. ([Location 485](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=485))
- Strategic programming requires an investment mindset. Rather than taking the fastest path to finish your current project, you must invest time to improve the design of the system. ([Location 486](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=486))
- it’s worth taking a little extra time to find a simple design for each new class; rather than implementing the first idea that comes to mind, try a couple of alternative designs and pick the cleanest one. Try ([Location 489](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=489))
- When you discover a design problem, don’t just ignore it or patch around it; take a little extra time to fix it. If you program strategically, you will continually make small improvements to the system design. ([Location 494](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=494))
- huge up-front investment, such as trying to design the entire system, won’t be effective. This is the waterfall method, and we know it doesn’t work. ([Location 498](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=498))
- The ideal design tends to emerge in bits and pieces, as you get experience with the system. ([Location 499](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=499))
- In some environments there are strong forces working against the strategic approach. For example, early-stage startups feel tremendous pressure to get their early releases out quickly. ([Location 516](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=516))
- If you are in a company leaning in this direction, you should realize that once a code base turns to spaghetti, it is nearly impossible to fix. You ([Location 520](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=520))
- 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. ([Location 524](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=524))
- 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. ([Location 525](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=525))
- Facebook is an example of a startup that encouraged tactical programming. For many years the company’s motto was “Move fast and break things.” New ([Location 527](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=527))
- Eventually, Facebook changed its motto to “Move fast with solid infrastructure” to encourage its engineers to invest more in good design. ([Location 533](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=533))
- Google and VMware grew up around the same time as Facebook, but both of these companies embraced a more strategic approach. Both companies placed a heavy emphasis on high quality code and good design, and both companies built sophisticated products that solved complex problems with reliable software systems. ([Location 538](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=538))
- 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. ([Location 544](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=544))
- 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. ([Location 553](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=553))
- In modular design, a software system is decomposed into a collection of modules that are relatively independent. Modules can take many forms, such as classes, subsystems, or services. ([Location 556](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=556))
- In an ideal world, each module would be completely independent of the others: a developer could work in any of the modules without knowing anything about any of the other modules. In this world, the complexity of a system would be the complexity of its worst module. Unfortunately, this ideal is not achievable. Modules must work together by calling each others’s functions or methods. ([Location 557](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=557))
- Dependencies can take many other forms, and they can be quite subtle. The goal of modular design is to minimize the dependencies between modules. ([Location 563](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=563))
- In order to manage dependencies, we think of each module in two parts: an interface and an implementation. ([Location 564](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=564))
- The interface consists of everything that a developer working in a different module must know in order to use the given module. Typically, the interface describes what the module does but not how it does it. ([Location 565](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=565))
- The implementation consists of the code that carries out the promises made by the interface. ([Location 566](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=566))
- Higher-level subsystems and services are also modules; their interfaces may take different forms, such as kernel calls or HTTP requests. Much ([Location 575](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=575))
- The best modules are those whose interfaces are much simpler than their implementations. Such modules have two advantages. First, a simple interface minimizes the complexity that a module imposes on the rest of the system. Second, if a module is modified in a way that does not change its interface, then no other module will be affected by the modification. ([Location 577](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=577))
- The interface to a module contains two kinds of information: formal and informal. ([Location 582](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=582))
- The formal parts of an interface are specified explicitly in the code, and some of these can be checked for correctness by the programming language. ([Location 583](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=583))
- The informal parts of an interface include its high-level behavior, such as the fact that a function deletes the file named by one of its arguments. ([Location 588](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=588))
- there are constraints on the usage of a class (perhaps one method must be called before another), these are also part of the class’s interface. ([Location 589](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=589))
- In general, if a developer needs to know a particular piece of information in order to use a module, then that information is part of the module’s interface. ([Location 590](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=590))
- The informal aspects of an interface can only be described using comments, and the programming language cannot ensure that the description is complete or accurate ([Location 591](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=591))
- An abstraction is a simplified view of an entity, which omits unimportant details. ([Location 597](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=597))
- In modular programming, each module provides an abstraction in form of its interface. The interface presents a simplified view of the module’s functionality; the details of the implementation are unimportant from the standpoint of the module’s abstraction, so they are omitted from the interface. ([Location 599](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=599))
- An abstraction can go wrong in two ways. ([Location 603](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=603))
- First, it can include details that are not really important; when this happens, it makes the abstraction more complicated than necessary, which increases the cognitive load on developers using the abstraction. ([Location 603](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=603))
- The second error is when an abstraction omits details that really are important. This results in obscurity: developers looking only at the abstraction will not have all the information they need to use the abstraction correctly. ([Location 604](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=604))
- An abstraction that omits important details is a false abstraction: it might appear simple, but in reality it isn’t. ([Location 606](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=606))
- Some applications, such as databases, need to know exactly when data is written through to storage, so they can ensure that data will be preserved after system crashes. Thus, the rules for flushing data to secondary storage must be visible in the file system’s interface. ([Location 612](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=612))
- Cars provide a simple abstraction that allows us to drive them without understanding the mechanisms for electrical motors, battery power management, anti-lock brakes, cruise control, and so on. ([Location 617](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=617))
- The best modules are those that provide powerful functionality yet have simple interfaces. I use the term deep to describe such modules. ([Location 619](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=619))
- The best modules are deep: they have a lot of functionality hidden behind a simple interface. ([Location 623](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=623))
- A deep module is a good abstraction because only a small fraction of its internal complexity is visible to its users. ([Location 623](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=623))
- Module depth is a way of thinking about cost versus benefit. The benefit provided by a module is its functionality. The cost of a module (in terms of system complexity) is its interface. ([Location 628](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=628))
- Interfaces are good, but more, or larger, interfaces are not necessarily better! ([Location 630](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=630))
- The mechanism for file I/O provided by the Unix operating system and its descendants, such as Linux, is a beautiful example of a deep interface. ([Location 631](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=631))
- How can a variety of different secondary storage devices, such as disks and flash drives, be incorporated into a single file system? ([Location 651](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=651))
- Another example of a deep module is the garbage collector in a language such as Go or Java. This module has no interface at all; it works invisibly behind the scenes to reclaim unused memory. Adding garbage collection to a system actually shrinks its overall interface, since it eliminates the interface for freeing objects. The implementation of a garbage collector is quite complex, but that complexity is hidden from programmers using the language. ([Location 655](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=655))
- For example, a class that implements linked lists is shallow. ([Location 661](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=661))
- The complexity of a linked list interface is nearly as great as the complexity of its implementation. ([Location 663](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=663))
- Shallow classes are sometimes unavoidable, but they don’t provide help much in managing complexity. ([Location 664](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=664))
- even takes more keystrokes to invoke the method than it would take for a caller to manipulate the data variable directly. ([Location 672](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=672))
- Red Flag: Shallow Module A shallow module is one whose interface is complicated relative to the functionality it provides. Shallow modules don’t help much in the battle against complexity, because the benefit they provide (not having to learn about how they work internally) is negated by the cost of learning and using their interfaces. Small modules tend to be shallow. ([Location 675](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=675))
- Unfortunately, the value of deep classes is not widely appreciated today. The conventional wisdom in programming is that classes should be small, not deep. ([Location 680](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=680))
- This approach results in large numbers of shallow classes and methods, which add to overall system complexity. ([Location 683](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=683))
- The extreme of the “classes should be small” approach is a syndrome I call classitis, which stems from the mistaken view that “classes are good, so more classes are better.” ([Location 684](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=684))
- Classitis may result in classes that are individually simple, but it increases the complexity of the overall system. ([Location 687](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=687))
- The Java language doesn’t require lots of small classes, but a culture of classitis seems to have taken root in the Java programming community. ([Location 692](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=692))
- It is particularly annoying (and error-prone) that buffering must be requested explicitly by creating a separate BufferedInputStream object; if a developer forgets to create this object, there will be no buffering and I/O will be slow. ([Location 703](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=703))
- Providing choice is good, but interfaces should be designed to make the common case as simple as possible ([Location 707](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=707))
- Almost every user of file I/O will want buffering, so it should be provided by default. ([Location 708](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=708))
- In contrast, the designers of the Unix system calls made the common case simple. For example, they recognized that sequential I/O is most common, so they made that the default behavior. Random access is still relatively easy to do, using the lseek system call, but a developer doing only sequential access need not be aware of that mechanism. ([Location 711](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=711))
- The basic idea is that each module should encapsulate a few pieces of knowledge, which represent design decisions. The knowledge is embedded in the module’s implementation but does not appear in its interface, so it is not visible to other modules. ([Location 730](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=730))
- Information hiding reduces complexity in two ways. First, it simplifies the interface to a module. The interface reflects a simpler, more abstract view of the module’s functionality and hides the details; this reduces the cognitive load on developers who use the module. ([Location 738](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=738))
- For instance, a developer using a B-tree class need not worry about the ideal fanout for nodes in the tree or how to keep the tree balanced. ([Location 740](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=740))
- Second, information hiding makes it easier to evolve the system. ([Location 741](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=741))
- If a piece of information is hidden, there are no dependencies on that information outside the module containing the information, so a design change related to that information will affect only the one module. ([Location 741](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=741))
- When designing a new module, you should think carefully about what information can be hidden in that module. If you can hide more information, you should also be able to simplify the module’s interface, and this makes the module deeper. ([Location 744](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=744))
- Note: hiding variables and methods in a class by declaring them private isn’t the same thing as information hiding. ([Location 746](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=746))
- However, information about the private items can still be exposed through public methods such as getter and setter methods. When this happens the nature and usage of the variables are just as exposed as if the variables were public. ([Location 748](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=748))
- The best form of information hiding is when information is totally hidden within a module, so that it is irrelevant and invisible to users of the module. ([Location 750](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=750))
- The opposite of information hiding is information leakage. Information leakage occurs when a design decision is reflected in multiple modules. ([Location 755](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=755))
- any change to that design decision will require changes to all of the involved modules. ([Location 756](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=756))
- If a piece of information is reflected in the interface for a module, then by definition it has been leaked; thus, simpler interfaces tend to correlate with better information hiding. ([Location 757](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=757))
- Back-door leakage like this is more pernicious than leakage through an interface, because it isn’t obvious. ([Location 761](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=761))
- One of the best skills you can learn as a software designer is a high level of sensitivity to information leakage. If ([Location 763](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=763))
- If the affected classes are relatively small and closely tied to the leaked information, it may make sense to merge them into a single class. ([Location 765](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=765))
- the new class exposes most of the knowledge through its interface, then it won’t provide much value (you’ve simply replaced back-door leakage with leakage through an interface). ([Location 767](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=767))
- Red Flag: Information Leakage 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. ([Location 770](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=770))
- One common cause of information leakage is a design style I call temporal decomposition. In temporal decomposition, the structure of a system corresponds to the time order in which operations will occur. Consider ([Location 774](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=774))
- Both the file reading and file writing steps have knowledge about the file format, which results in information leakage. The solution is to combine the core mechanisms for reading and writing files into a single class. This class will get used during both the reading and writing phases of the application. ([Location 777](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=777))
- When designing modules, focus on the knowledge that’s needed to perform each task, not the order in which tasks occur. ([Location 784](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=784))
- Red Flag: Temporal Decomposition In temporal decomposition, execution order is reflected in the code structure: operations that happen at different times are in different methods or classes. If the same knowledge is used at different points in execution, it gets encoded in multiple places, resulting in information leakage. ([Location 786](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=786))
- The most common mistake made by students was to divide their code into a large number of shallow classes, which led to information leakage between the classes. ([Location 807](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=807))
- One team used two different classes for receiving HTTP requests; the first class read the request from the network connection into a string, and the second class parsed the string. This is an example of a temporal decomposition ([Location 808](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=808))
- As a result, both classes needed to understand most of the structure of HTTP requests, and parsing code was duplicated in both classes. ([Location 812](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=812))
- Because the classes shared so much information, it would have been better to merge them into a single class that handles both request reading and parsing. ([Location 814](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=814))
- information hiding can often be improved by making a class slightly larger. ([Location 817](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=817))
- A second reason for increasing the size of a class is to raise the level of the interface; for example, rather than having separate methods for each of three steps of a computation, have a single method that performs the entire computation. ([Location 819](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=819))
- First, they recognized that server applications don’t care whether a parameter is specified in the header line or the body of the request, so they hid this distinction from callers and merged the parameters from both locations together. ([Location 834](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=834))
- Second, they hid knowledge of URL encoding: the HTTP parser decodes parameter values before returning them to the Web server, so that the value of the comment parameter in Figure 5.1 will be returned as “What a cute baby!”, not “What+a+cute+baby%21”). ([Location 836](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=836))
- In both of these cases, information hiding resulted in simpler APIs for the code using the HTTP module. ([Location 838](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=838))
- Rather than returning a single parameter, the method returns a reference to the Map used internally to store all of the parameters. This method is shallow, and it exposes the internal representation used by the HTTPRequest class to store parameters. ([Location 846](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=846))
- Any change to that representation will result in a change to the interface, which will require modifications to all callers. ([Location 848](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=848))
- The HTTP projects also had to provide support for generating HTTP responses. The most common mistake students made in this area was inadequate defaults. ([Location 866](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=866))
- Defaults illustrate the principle that interfaces should be designed to make the common case as simple as possible. ([Location 873](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=873))
- Red Flag: Overexposure If the API for a commonly used feature forces users to learn about other features that are rarely used, this increases the cognitive load on users who don’t need the rarely used features. ([Location 880](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=880))
- Try to design the private methods within a class so that each method encapsulates some information or capability and hides it from the rest of the class. ([Location 886](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=886))
- In addition, try to minimize the number of places where each instance variable is used. ([Location 887](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=887))
- Information hiding only makes sense when the information being hidden is not needed outside its module. If the information is needed outside the module, then you must not hide it. ([Location 890](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=890))
- As a software designer, your goal should be to minimize the amount of information needed outside a module; for example, if a module can automatically adjust its configuration, that is better than exposing configuration parameters. But, ([Location 894](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=894))
- One of the most common decisions that you will face when designing a new module is whether to implement it in a general-purpose or special-purpose fashion. ([Location 908](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=908))
- On the other hand, we know that it’s hard to predict the future needs of a software system, so a general-purpose solution might include facilities that are never actually needed. ([Location 912](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=912))
- Furthermore, if you implement something that is too general-purpose, it might not do a good job of solving the particular problem you have today. ([Location 913](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=913))
- If you take the special-purpose approach and discover additional uses later, you can always refactor it to make it general-purpose. ([Location 916](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=916))
- The most important (and perhaps surprising) benefit of the general-purpose approach is that it results in simpler and deeper interfaces than a special-purpose approach. ([Location 923](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=923))
- Each new user interface operation required a new method to be defined in the text class, so a developer working on the user interface was likely to end up working on the text class as well. ([Location 950](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=950))
- Note: Shotgun Surgery
- A better approach is to make the text class more generic. Its API should be defined only in terms of basic text features, without reflecting the higher-level operations that will be implemented with it. ([Location 953](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=953))
- This API also uses a more generic type Position instead of Cursor, which reflects a specific user interface. ([Location 960](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=960))
- A text class implemented with the general-purpose interface could potentially be used for other purposes besides an interactive editor. As one example, suppose you were building an application that modified a specified file by replacing all occurrences of a particular string with another string. ([Location 978](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=978))
- The general-purpose approach provides a cleaner separation between the text and user interface classes, which results in better information hiding. ([Location 988](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=988))
- The general-purpose interface also reduces cognitive load: a developer working on the user interface only needs to learn a few simple methods, which can be reused for a variety of purposes. ([Location 990](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=990))
- One of the most important elements of software design is determining who needs to know what, and when. ([Location 996](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=996))
- What is the simplest interface that will cover all my current needs? If you reduce the number of methods in an API without reducing its overall capabilities, then you are probably creating more general-purpose methods. ([Location 1001](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=1001))
- In how many situations will this method be used? If a method is designed for one particular use, such as the backspace method, that is a red flag that it may be too special-purpose. ([Location 1007](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=1007))
- Is this API easy to use for my current needs? This question can help you to determine when you have gone too far in making an API simple and general-purpose. ([Location 1010](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=1010))
- Making your modules somewhat general-purpose is one of the best ways to reduce overall system complexity. ([Location 1019](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=1019))
- When adjacent layers have similar abstractions, the problem often manifests itself in the form of pass-through methods. A pass-through method is one that does little except invoke another method, whose signature is similar or identical to that of the calling method. ([Location 1034](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=1034))
- 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. ([Location 1066](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=1066))
- Pass-through methods make classes shallower: they increase the interface complexity of the class, which adds complexity, but they don’t increase the total functionality of the system. ([Location 1068](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=1068))
- Pass-through methods indicate that there is confusion over the division of responsibility between classes. ([Location 1073](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=1073))
- The important thing is that each new method should contribute significant functionality. Pass-through methods are bad because they contribute no new functionality. ([Location 1089](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=1089))
- A dispatcher is a method that uses its arguments to select one of several other methods to invoke; then it passes most or all of its arguments to the chosen method. ([Location 1091](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=1091))
- It is fine for several methods to have the same signature as long as each of them provides useful and distinct functionality. ([Location 1102](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=1102))
- The methods invoked by a dispatcher have this property. ([Location 1103](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=1103))
- such as disk drivers in an operating system. ([Location 1103](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=1103))
- “wrapper”) is one that encourages API duplication across layers. A decorator object takes an existing object and extends its functionality; it provides an API similar or identical to the underlying object, and its methods invoke the methods of the underlying object. ([Location 1108](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=1108))
- The motivation for decorators is to separate special-purpose extensions of a class from a more generic core. However, decorator classes tend to be shallow: they introduce a large amount of boilerplate for a small amount of new functionality. ([Location 1117](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=1117))
- Another application of the “different layer, different abstraction” rule is that the interface of a class should normally be different from its implementation: the representations used internally should be different from the abstractions that appear in the interface. ([Location 1132](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=1132))
- Another form of API duplication across layers is a pass-through variable, which is a variable that is passed down through a long chain of methods. ([Location 1148](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=1148))
- but global variables almost always create other problems. ([Location 1164](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=1164))
- global variables make it impossible to create two independent instances of the same system in the same process, since accesses to the global variables will conflict. ([Location 1165](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=1165))
- A context stores all of the application’s global state (anything that would otherwise be a pass-through variable or global variable). ([Location 1168](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=1168))
- Most applications have multiple variables in their global state, representing things such as configuration options, shared subsystems, and performance counters. There is one context object per instance of the system. The context allows multiple instances of the system to coexist in a single process, each with its own context. ([Location 1169](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=1169))
- The context makes it easy to identify and manage the global state of the system, since it is all stored in one place. ([Location 1187](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=1187))
- The context is also convenient for testing: test code can change the global configuration of the application by modifying fields in the context. ([Location 1188](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=1188))
- Contexts are far from an ideal solution. The variables stored in a context have most of the disadvantages of global variables; for example, it may not be obvious why a particular variable is present, or where it is used. Without discipline, a context can turn into a huge grab-bag of data that creates nonobvious dependencies throughout the system. 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. ([Location 1190](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=1190))
- In order for an element to provide a net gain against complexity, it must eliminate some complexity that would be present in the absence of the design element. Otherwise, you are better off implementing the system without that particular element. ([Location 1196](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=1196))
- The “different layer, different abstraction” rule is just an application of this idea: if different layers have the same abstraction, such as pass-through methods or decorators, then there’s a good chance that they haven’t provided enough benefit to compensate for the additional infrastructure they represent. ([Location 1199](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=1199))
- Which is better: should you let users of the module deal with the complexity, or should you handle the complexity internally within the module? ([Location 1206](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=1206))
- it is more important for a module to have a simple interface than a simple implementation. ([Location 1210](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=1210))
- A character-oriented interface such as the one described in Section 6.3 pulls complexity downward. ([Location 1225](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=1225))
- Configuration parameters are an example of moving complexity upwards instead of down. ([Location 1230](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=1230))
- Advocates argue that configuration parameters are good because they allow users to tune the system for their particular requirements and workloads. ([Location 1234](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=1234))
- However, configuration parameters also provide an easy excuse to avoid dealing with important issues and pass them on to someone else. ([Location 1238](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=1238))
- In many cases, it’s difficult or impossible for users or administrators to determine the right values for the parameters. ([Location 1239](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=1239))
- In other cases, the right values could have been determined automatically with a little extra work in the system implementation. ([Location 1240](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=1240))
- However, the transport protocol could compute a reasonable value on its own by measuring the response time for requests that succeed and then using a multiple of this for the retry interval. ([Location 1242](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=1242))
- Before exporting a configuration parameter, ask yourself: “will users (or higher-level modules) be able to determine a better value than we can determine here?” ([Location 1246](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=1246))
- When you do create configuration parameters, see if you can compute reasonable defaults automatically, so users will only need to provide values under exceptional conditions. ([Location 1247](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=1247))
- Pulling complexity down makes the most sense if (a) the complexity being pulled down is closely related to the class’s existing functionality, (b) pulling the complexity down will result in many simplifications elsewhere in the application, and (c) pulling the complexity down simplifies the class’s interface. ([Location 1252](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=1252))
- It might appear that the best way to achieve this goal is to divide the system into a large number of small components: the smaller the components, the simpler each individual component is likely to be. However, the act of subdividing creates additional complexity that was not present before subdivision: ([Location 1270](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=1270))
- Subdivision can result in duplication: code that was present in a single instance before subdivision may need to be present in each of the subdivided components. ([Location 1281](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=1281))
- Bringing pieces of code together is most beneficial if they are closely related. ([Location 1282](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=1282))
- When two or more modules are combined into a single module, it may be possible to define an interface for the new module that is simpler or easier to use than the original interfaces. ([Location 1301](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=1301))
- Goto statements are generally considered a bad idea, and they can result in indecipherable code if used indiscriminately, but they are useful in situations like this where they are used to escape from nested code. ([Location 1321](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=1321))
- If the same piece of code (or code that is almost the same) appears over and over again, that’s a red flag that you haven’t found the right abstractions. ([Location 1333](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=1333))
- The way to separate special-purpose code from general-purpose code is to pull the special-purpose code upwards, into the higher layers, leaving the lower layers general-purpose. ([Location 1342](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=1342))
- 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. ([Location 1365](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=1365))
- 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. Each method was only invoked in a single place. ([Location 1407](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=1407))
- In this example, it would be better to eliminate the logging methods and place the logging statements at the locations where the errors were detected. ([Location 1411](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=1411))
- However, length by itself is rarely a good reason for splitting up a method. In general, developers tend to break up methods too much. ([Location 1488](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=1488))
- Splitting up a method introduces additional interfaces, which add to complexity. ([Location 1489](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=1489))
- You shouldn’t break up a method unless it makes the overall system simpler; ([Location 1490](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=1490))
- When designing methods, the most important goal is to provide clean and simple abstractions. 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. ([Location 1500](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=1500))
- The method should be deep: its interface should be much simpler than its implementation. ([Location 1502](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=1502))
- This form of subdivision makes sense if there is a subtask that is cleanly separable from the rest of the original method, which means (a) someone reading the child method doesn’t need to know anything about the parent method and (b) someone reading the parent method doesn’t need to understand the implementation of the child method. ([Location 1507](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=1507))
- For example, joining methods might replace two shallow methods with one deeper method; it might eliminate duplication of code; it might eliminate dependencies between the original methods, or intermediate data structures; it might result in better encapsulation, so that knowledge that was previously present in multiple places is now isolated in a single place; or it might result in a simpler interface, ([Location 1525](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=1525))
- 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. ([Location 1530](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=1530))
- A recent study found that more than 90% of catastrophic failures in distributed data-intensive systems were caused by incorrect error handling1. ([Location 1606](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=1606))
- The exceptions thrown by a class are part of its interface; classes with lots of exceptions have complex interfaces, and they are shallower than classes with fewer exceptions. ([Location 1626](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=1626))
- The best way to reduce the complexity damage caused by exception handling is to reduce the number of places where exceptions have to be handled. ([Location 1630](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=1630))
- The best way to eliminate exception handling complexity is to define your APIs so that there are no exceptions to handle: define errors out of existence. ([Location 1633](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=1633))
- The Unix operating system defines file deletion more elegantly. In Unix, if a file is open when it is deleted, Unix does not delete the file immediately. Instead, it marks the file for deletion, then the delete operation returns successfully. The file name has been removed from its directory, so no other processes can open the old file and a new file with the same name can be created, but the existing file data persists. Processes that already have the file open can continue to read it and write it normally. Once the file has been closed by all of the accessing processes, its data is freed. ([Location 1646](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=1646))
- In the error-ful approach, developers must write additional code to avoid or ignore the errors, and this increases the likelihood of bugs; or, they may forget to write the additional code, in which case unexpected errors may be thrown at runtime. ([Location 1676](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=1676))
- Overall, the best way to reduce bugs is to make software simpler. ([Location 1679](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=1679))
- A better approach is to aggregate the exceptions. Instead of catching the exceptions in the individual service methods, let them propagate up to the top-level dispatch method for the Web server, as in Figure 10.2. A single handler in this method can catch all of the exceptions and generate an appropriate error response for missing parameters. ([Location 1718](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=1718))
- Whether or not it is acceptable to crash on a particular error depends on the application. For a replicated storage system, it isn’t appropriate to abort on an I/O error. Instead, the system must use replicated data to recover any information that was lost. The recovery mechanisms will add considerable complexity to the program, but recovering lost data is an essential part of the value the system provides to its users. ([Location 1793](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=1793))
- Defining away exceptions, or masking them inside a module, only makes sense if the exception information isn’t needed outside the module. ([Location 1816](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=1816))
- Documentation also plays an important role in abstraction; without comments, you can’t hide complexity. ([Location 1889](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=1889))
- the process of writing comments, if done correctly, will actually improve a system’s design. ([Location 1890](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=1890))
- 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. ([Location 1899](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=1899))
- The informal aspects of an interface, such as a high-level description of what each method does or the meaning of its result, can only be described in comments. ([Location 1910](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=1910))
- such as the rationale for a particular design decision, or the conditions under which it makes sense to call a particular method. ([Location 1911](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=1911))
- if you write code with the expectation that users will read method implementations, you will try to make each method as short as possible, so that it’s easy to read. If the method does anything nontrivial, you will break it up into several smaller methods. This will result in a large number of shallow methods. ([Location 1915](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=1915))
- it doesn’t really make the code easier to read: in order to understand the behavior of the top-level method, readers will probably need to understand the behaviors of the nested methods. ([Location 1917](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=1917))
- the goal of abstractions is to hide complexity: an abstraction is a simplified view of an entity, which preserves essential information but omits details that can safely be ignored. ([Location 1920](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=1920))
- If users must read the code of a method in order to use it, then there is no abstraction: ([Location 1921](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=1921))
- you want a clean software structure, which will allow you to work efficiently over the long-term, then you must take some extra time up front in order to create that structure. ([Location 1936](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=1936))
- Code reviews provide a great mechanism for detecting and fixing stale comments. ([Location 1950](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=1950))
- 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. ([Location 1957](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=1957))
- Comments are valuable even when the original designer is the one making the changes: if it has been more than a few weeks since you last worked in a piece of code, you will have forgotten many of the details of the original design. ([Location 1962](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=1962))
- Change amplification: a seemingly simple change requires code modifications in many places. Cognitive load: in order to make a change, the developer must accumulate a large amount of information. Unknown unknowns: it is unclear what code needs to be modified, or what information must be considered in order to make those modifications. ([Location 1964](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=1964))
- the primary causes of complexity are dependencies and obscurity. ([Location 1972](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=1972))
- The guiding principle for comments is that comments should describe things that aren’t obvious from the code. ([Location 1979](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=1979))
- reading the code is time-consuming and forces them to consider a lot of information that isn’t needed to use the module. ([Location 1990](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=1990))
- Developers should be able to understand the abstraction provided by a module without reading any code other than its externally visible declarations. ([Location 1990](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=1990))
- Interface: a comment block that immediately precedes the declaration of a module such as a class, data structure, function, or method. The comment describe’s the module’s interface. ([Location 2003](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=2003))
- Data structure member: a comment next to the declaration of a field in a data structure, ([Location 2007](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=2007))
- Implementation comment: a comment inside the code of a method or function, which describes how the code works internally. ([Location 2008](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=2008))
- Cross-module comment: a comment describing dependencies that cross module boundaries. ([Location 2009](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=2009))
- Unfortunately, many comments are not particularly helpful. The most common reason is that the comments repeat the code: all of the information in the comment can easily be deduced from the code next to the comment. ([Location 2017](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=2017))
- Red Flag: Comment Repeats Code 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. ([Location 2068](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=2068))
- A first step towards writing good comments is to use different words in the comment from those in the name of the entity being described. ([Location 2074](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=2074))
- and the fact that padding applies to both sides of each line. Instead of using the term “padding”, the comment explains what padding is, in case the reader isn’t already familiar with the term. ([Location 2082](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=2082))
- Comments augment the code by providing information at a different level of detail. ([Location 2086](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=2086))
- provide information at a higher, more abstract, level than the code; these comments offer intuition, such as the reasoning behind the code, or a simpler and more abstract way of thinking about the code. ([Location 2088](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=2088))
- you want code that presents good abstractions, you must document those abstractions with comments. ([Location 2220](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=2220))
- documenting abstractions is to separate interface comments from implementation comments. ([Location 2221](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=2221))
- If interface comments must also describe the implementation, then the class or method is shallow. ([Location 2224](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=2224))
- Red Flag: Implementation Documentation Contaminates Interface This red flag occurs when interface documentation, such as that for a method, describes implementation details that aren’t needed in order to use the thing being documented. ([Location 2368](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=2368))
- The main goal of implementation comments is to help readers understand what the code is doing (not how it does it). ([Location 2411](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=2411))
- Longer methods have several blocks of code that do different things as part of the method’s overall task. Add a comment before each of the major blocks to provide a high-level (more abstract) description of what that block does. ([Location 2413](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=2413))
- Loop comments are only needed for longer or more complex loops, where it may not be obvious what the loop is doing; many loops are short and simple enough that their behavior is already obvious. ([Location 2423](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=2423))
- In addition to describing what the code is doing, implementation comments are also useful to explain why. ([Location 2424](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=2424))
- In a perfect world, every important design decision would be encapsulated within a single class. Unfortunately, real systems inevitably end up with design decisions that affect multiple classes. ([Location 2436](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=2436))
- The biggest challenge with cross-module documentation is finding a place to put it where it will naturally be discovered by developers. ([Location 2439](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=2439))
- have recently been experimenting with an approach where cross-module issues are documented in a central file called designNotes. The file is divided up into clearly labeled sections, one for each major topic. ([Location 2496](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=2496))
- Then, in any piece of code that relates to one of these issues there is a short comment referring to the designNotes file: // See "Zombies" in designNotes. ([Location 2512](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=2512))
- The goal of comments is to ensure that the structure and behavior of the system is obvious to readers, ([Location 2518](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=2518))
- “obvious” is from the perspective of someone reading your code for the first time (not you). ([Location 2522](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=2522))
- Good names are a form of documentation: they make code easier to understand. ([Location 2543](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=2543))
- The problem was actually quite simple (as are most bugs, once you figure them out). ([Location 2555](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=2555))
- It took a long process of instrumentation, which eventually showed that the corruption must be happening in a particular statement, ([Location 2562](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=2562))
- Take a bit of extra time to choose great names, which are precise, unambiguous, and intuitive. The extra attention will pay for itself quickly, and over time you’ll learn to choose good names quickly. ([Location 2571](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=2571))
- Good names have two properties: precision and consistency. ([Location 2582](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=2582))
- Red Flag: Vague Name If a variable or method name is broad enough to refer to many different things, then it doesn’t convey much information to the developer and the underlying entity is more likely to be misused. ([Location 2624](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=2624))
- If you find it difficult to come up with a name for a particular variable that is precise, intuitive, and not too long, this is a red flag. It suggests that the variable may not have a clear definition or purpose. ([Location 2642](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=2642))
- Red Flag: Hard to Pick Name If it’s hard to find a simple name for a variable or method that creates a clear image of the underlying object, that’s a hint that the underlying object may not have a clean design. ([Location 2647](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=2647))
- Consistent naming reduces cognitive load ([Location 2654](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=2654))
- Consistency has three requirements: first, always use the common name for the given purpose; second, never use the common name for anything other than the given purpose; third, make sure that the purpose is narrow enough that all variables with the name have the same behavior. This ([Location 2655](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=2655))
- Not everyone shares my views about naming. Some of the developers of the Go language argue that names should be very short, often only a single character. ([Location 2667](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=2667))
- If anything, the name count gives a slightly better clue to the behavior of the variable than n. With the first version I ended up reading through the code trying to figure out what n means, whereas I didn’t feel that need with the second version. But, if n is used consistently throughout the system to refer to counts (and nothing else), then the short name will probably be clear to other developers. ([Location 2706](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=2706))
- Gerrand makes one comment that I agree with: “The greater the distance between a name’s declaration and its uses, the longer the name should be.” ([Location 2717](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=2717))
- I use a different approach to writing comments, where I write the comments at the very beginning: For a new class, I start by writing the class interface comment. Next, I write interface comments and signatures for the most important public methods, but I leave the method bodies empty. I iterate a bit over these comments until the basic structure feels about right. At this point I write declarations and comments for the most important class instance variables in the class. Finally, I fill in the bodies of the methods, adding implementation comments as needed. While writing method bodies, I usually discover the need for additional methods and instance variables. For each new method I write the interface comment before the body of the method; for instance variables I fill in the comment at the same time that I write the variable declaration. ([Location 2752](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=2752))
- It’s better to write the interface comment for each method before its body, so you can focus on the method’s abstraction and interface without being distracted by its implementation. ([Location 2761](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=2761))
- Comments provide the only way to fully capture abstractions, and good abstractions are fundamental to good system design. ([Location 2765](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=2765))
- if the interface comment must describe all the major features of the implementation, then the method is shallow. ([Location 2774](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=2774))
- The same idea applies to variables: if it takes a long comment to fully describe a variable, it’s a red flag that suggests you may not have chosen the right variable decomposition. ([Location 2775](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=2775))
- Red Flag: Hard to Describe 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. ([Location 2779](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=2779))
- the design of a mature system is determined more by changes made during the system’s evolution than by any initial conception. ([Location 2809](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=2809))
- in tactical programming, the primary goal is to get something working quickly, even if that results in additional complexity; ([Location 2813](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=2813))
- in strategic programming, the most important goal is to produce a great system design. ([Location 2814](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=2814))
- If you want to maintain a clean design for a system, you must take a strategic approach when modifying existing code. 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. ([Location 2822](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=2822))
- if you invest a little extra time to refactor and improve the system design, you’ll end up with a cleaner system. ([Location 2826](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=2826))
- If you’re not making the design better, you are probably making it worse. ([Location 2830](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=2830))
- If refactoring the system “the right way” would take three months but a quick and dirty fix would take only two hours, you may have to take the quick and dirty approach, particularly if you are working against a tight deadline. ([Location 2832](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=2832))
- Every development organization should plan to spend a small fraction of its total effort on cleanup and refactoring; this work will pay for itself over the long run. ([Location 2838](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=2838))
- The best way to ensure that comments get updated is to position them close to the code they describe, so developers will see them when they change the code. ([Location 2844](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=2844))
- Some might argue that interface comments should go in header files so that users can learn how to use an abstraction without having to look at the code file. However, users should not need to read either code or header files; they should get their information from documentation compiled by tools such as Doxygen or Javadoc. ([Location 2851](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=2851))
- A common mistake when modifying code is to put detailed information about the change in the commit message for the source code repository, but then not to document it in the code. ([Location 2868](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=2868))
- The second technique for keeping comments up to date is to avoid duplication. If documentation is duplicated, it is more difficult for developers to find and update all of the relevant copies. ([Location 2877](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=2877))
- Don’t redocument one module’s design decisions in another module. ([Location 2890](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=2890))
- If information is already documented someplace outside your program, don’t repeat the documentation inside the program; just reference the external documentation. ([Location 2894](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=2894))
- Consistency is a powerful tool for reducing the complexity of a system and making its behavior more obvious. ([Location 2914](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=2914))
- Consistency creates cognitive leverage: once you have learned how something is done in one place, you can use that knowledge to immediately understand other places that use the same approach. ([Location 2916](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=2916))
- An invariant is a property of a variable or structure that is always true. ([Location 2933](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=2933))
- Invariants reduce the number of special cases that must be considered in code and make it easier to reason about the code’s behavior. ([Location 2935](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=2935))
- Consistency is hard to maintain, especially when many people work on a project over a long time. People in one group may not know about conventions established in another group. ([Location 2937](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=2937))
- Document. Create a document that lists the most important overall conventions, such as coding style guidelines. ([Location 2940](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=2940))
- Enforce. Even with good documentation, it’s hard for developers to remember all of the conventions. The best way to enforce conventions is to write a tool that checks for violations, and make sure that code cannot be committed to the repository unless it passes the checker. ([Location 2945](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=2945))
- Code reviews provide another opportunity for enforcing conventions ([Location 2957](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=2957))
- The more nit-picky that code reviewers are, the more quickly everyone on the team will learn the conventions, and the cleaner the code will be. ([Location 2958](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=2958))
- “When in Rome, do as the Romans do.” When working in a new file, look around to see how the existing code is structured. ([Location 2960](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=2960))
- When you see anything that looks like it might possibly be a convention, follow it. ([Location 2963](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=2963))
- Don’t change existing conventions. Resist the urge to “improve” on existing conventions. ([Location 2965](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=2965))
- Having a “better idea” is not a sufficient excuse to introduce inconsistencies. ([Location 2966](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=2966))
- Before introducing inconsistent behavior, ask yourself two questions. First, do you have significant new information justifying your approach that wasn’t available when the old convention was established? Second, is the new approach so much better that it is worth taking the time to update all of the old uses? ([Location 2968](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=2968))
- Consistency means not only that similar things should be done in similar ways, but that dissimilar things should be done in different ways. ([Location 2974](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=2974))
- Consistency only provides benefits when developers have confidence that “if it looks like an x, it really is an x.” ([Location 2977](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=2977))
- Consistency is another example of the investment mindset. ([Location 2979](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=2979))
- It will take a bit of extra work to ensure consistency: work to decide on conventions, work to create automated checkers, work to look for similar situations to mimic in new code, and work in code reviews to educate the team. ([Location 2980](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=2980))
- Obscurity occurs when important information about a system is not obvious to new developers. ([Location 2985](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=2985))
- If code is obvious, a reader doesn’t need to spend much time or effort to gather all the information they need to work with the code. ([Location 2989](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=2989))
- Thus, the best way to determine the obviousness of code is through code reviews. ([Location 2993](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=2993))
- By trying to understand what made the code nonobvious, you will learn how to write better code in the future. ([Location 2994](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=2994))
- Precise and meaningful names clarify the behavior of the code and reduce the need for documentation. ([Location 2997](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=2997))
- If a name is vague or ambiguous, then readers will have read through the code in order to deduce the meaning of the named entity; this is time-consuming and error-prone. ([Location 2998](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=2998))
- Judicious use of white space. The way code is formatted can impact how easy it is to understand. ([Location 3002](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=3002))
- Comments. Sometimes it isn’t possible to avoid code that is nonobvious. When this happens, it’s important to use comments to compensate by providing the missing information. To ([Location 3076](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=3076))
- There are many things that can make code nonobvious; this section provides a few examples. Some of these, such as event-driven programming, are useful in some situations, so you may end up using them anyway. When this happens, extra documentation can help to minimize reader confusion. ([Location 3080](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=3080))
- Event-driven programming makes it hard to follow the flow of control. ([Location 3085](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=3085))
- Red Flag: Nonobvious Code If the meaning and behavior of code cannot be understood with a quick reading, it is a red flag. Often this means that there is important information that is not immediately clear to someone reading the code. ([Location 3098](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=3098))
- Generic containers. Many languages provide generic classes for grouping two or more items into a single object, such as Pair in Java or std::pair in C++. ([Location 3101](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=3101))
- Unfortunately, generic containers result in nonobvious code because the grouped elements have generic names that obscure their meaning. ([Location 3106](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=3106))
- Thus, it’s better not to use generic containers. If you need a container, define a new class or structure that is specialized for the particular use. ([Location 3109](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=3109))
- software should be designed for ease of reading, not ease of writing. ([Location 3112](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=3112))
- Generic containers are expedient for the person writing the code, but they create confusion for all the readers that follow. ([Location 3112](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=3112))
- Different types for declaration and allocation. Consider the following Java example: private List<Message> incomingMessageList; ... incomingMessageList = new ArrayList<Message>(); The variable is declared as a List, but the actual value is an ArrayList. This code is legal, since List is a superclass of ArrayList, but it can mislead a reader who sees the declaration but not the actual allocation. The actual type may impact how the variable is used (ArrayLists have different performance and thread-safety properties than other subclasses of List), so it is better to match the declaration with the allocation. ([Location 3115](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=3115))
- Code that violates reader expectations. ([Location 3124](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=3124))
- code is nonobvious, that usually means there is important information about the code that the reader does not have: in the RaftClient example, the reader might not know that the RaftClient constructor created new threads; in the Pair example, the reader might not know that result.getKey() returns the number of the current term. ([Location 3137](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=3137))
- implementation inheritance reduces the amount of code that needs to be modified as the system evolves; in other words, it reduces the change amplification problem described in Chapter 2. ([Location 3168](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=3168))
- Class instance variables in the parent class are often accessed by both the parent and child classes; this results in information leakage between the classes in the inheritance hierarchy and makes it hard to modify one class in the hierarchy without looking at the others. ([Location 3171](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=3171))
- Before using implementation inheritance, consider whether an approach based on composition can provide the same benefits. ([Location 3177](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=3177))
- For instance, it may be possible to use small helper classes to implement the shared functionality. Rather than inheriting functions from a parent, the original classes can each build upon the features of the helper classes. ([Location 3178](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=3178))
- there is no viable alternative to implementation inheritance, try to separate the state managed by the parent class from that managed by subclasses. ([Location 3180](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=3180))
- One way to do this is for certain instance variables to be managed entirely by methods in the parent class, with subclasses using them only in a read-only fashion or through other methods in the parent class. This applies the notion of information hiding within the class hierarchy to reduce dependencies. ([Location 3181](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=3181))
- Although the mechanisms provided by object-oriented programming can assist in implementing clean designs, they do not, by themselves, guarantee good design. ([Location 3183](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=3183))
- collection of ideas about how to make software development more lightweight, flexible, and incremental; ([Location 3187](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=3187))
- the notion that development should be incremental and iterative. In the agile approach, a software system is developed in a series of iterations, each of which adds and evaluates a few new features; each iteration includes design, test, and customer input. ([Location 3191](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=3191))
- The best way to end up with a good design is to develop a system in increments, where each increment adds a few new abstractions and refactors existing abstractions based on experience. ([Location 3194](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=3194))
- One of the risks of agile development is that it can lead to tactical programming. ([Location 3196](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=3196))
- possible. For example, some agile practitioners argue that you shouldn’t implement general-purpose mechanisms right away; implement a minimal special-purpose mechanism to start with, and refactor into something more generic later, once you know that it’s needed. ([Location 3197](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=3197))
- Although these arguments make sense to a degree, they argue against an investment approach, and they encourage a more tactical style of programming. This can result in a rapid accumulation of complexity. ([Location 3199](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=3199))
- Developing incrementally is generally a good idea, but the increments of development should be abstractions, not features. ([Location 3201](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=3201))
- one of the tenets of agile development is that testing should be tightly integrated with development, and programmers should write tests for their own code. ([Location 3206](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=3206))
- The second kind of test consists of system tests (sometimes called integration tests), which ensure that the different parts of an application all work together properly. ([Location 3211](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=3211))
- Tests, particularly unit tests, play an important role in software design because they facilitate refactoring. ([Location 3214](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=3214))
- With a good set of tests, developers can be more confident when refactoring because the test suite will find most bugs that are introduced. This encourages developers to make structural improvements to a system, which results in a better design. ([Location 3218](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=3218))
- Test-driven development is an approach to software development where programmers write unit tests before they write code. ([Location 3225](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=3225))
- Although I am a strong advocate of unit testing, I am not a fan of test-driven development. The problem with test-driven development is that it focuses attention on getting specific features working, rather than finding the best design. ([Location 3229](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=3229))
- This is tactical programming pure and simple, with all of its disadvantages. ([Location 3230](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=3230))
- One place where it makes sense to write the tests first is when fixing bugs. Before fixing a bug, write a unit test that fails because of the bug. Then fix the bug and make sure that the unit test now passes. ([Location 3236](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=3236))
- A design pattern is a commonly used approach for solving a particular kind of problem, such as an iterator or an observer. ([Location 3240](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=3240))
- Using design patterns doesn’t automatically improve a software system; it only does so if the design patterns fit. ([Location 3247](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=3247))
- Although it may make sense to use getters and setters if you must expose instance variables, it’s better not to expose instance variables in the first place. ([Location 3257](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=3257))
- Exposed instance variables mean that part of the class’s implementation is visible externally, which violates the idea of information hiding and increases the complexity of the class’s interface. ([Location 3258](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=3258))
- Whenever you encounter a proposal for a new software development paradigm, challenge it from the standpoint of complexity: does the proposal really help to minimize complexity in large software systems? Many proposals sound good on the surface, but if you look more deeply you will see that some of them make complexity worse, not better. ([Location 3264](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=3264))
- The most important idea is still simplicity: not only does simplicity improve a system’s design, but it usually makes systems faster. ([Location 3271](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=3271))
- The first question to address is “how much should you worry about performance during the normal development process?” If you try to optimize every statement for maximum speed, it will slow down development and create a lot of unnecessary complexity. Furthermore, many of the “optimizations” won’t actually help performance. On ([Location 3273](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=3273))
- In this “death by a thousand cuts” scenario it’s hard to come back later and improve the performance, because there is no single improvement that will have much impact. ([Location 3277](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=3277))
- The best approach is something between these extremes, where you use basic knowledge of performance to choose design alternatives that are “naturally efficient” yet also clean and simple. ([Location 3279](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=3279))
- Network communication: even within a datacenter, a round-trip message exchange can take 10–50 µs, which is tens of thousands of instruction times. Wide-area round-trips can take 10–100 ms. I/O to secondary storage: disk I/O operations typically take 5–10 ms, which is millions of instruction times. Flash storage takes 10–100 µs. New emerging nonvolatile memories may be as fast as 1 µs, but this is still around 2000 instruction times. Dynamic memory allocation (malloc in C, new in C++ or Java) typically involves significant overhead for allocation, freeing, and garbage collection. Cache misses: fetching data from DRAM into an on-chip processor cache takes a few hundred instruction times; in many programs, overall performance is determined as much by cache misses as by computational costs. ([Location 3282](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=3282))
- If the more efficient design adds only a small amount of complexity, and if the complexity is hidden, so it doesn’t affect any interfaces, then it may be worthwhile (but beware: complexity is incremental). ([Location 3301](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=3301))
- In general, simpler code tends to run faster than complex code. ([Location 3310](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=3310))
- Deep classes are more efficient than shallow ones, because they get more work done for each method call. Shallow classes result in more layer crossings, and each layer crossing adds overhead. ([Location 3311](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=3311))
- Don’t do this! Programmers’ intuitions about performance are unreliable. ([Location 3315](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=3315))
- The second purpose of the measurements is to provide a baseline, so that you can re-measure performance after making your changes to ensure that performance actually improved. If the changes didn’t make a measurable difference in performance, then back them out (unless they made the system simpler). There’s no point in retaining complexity unless it provides a significant speedup. ([Location 3322](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=3322))
- The key idea is to design the code around the critical path. ([Location 3332](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=3332))
- Start off by asking yourself what is the smallest amount of code that must be executed to carry out the desired task in the common case. ([Location 3333](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=3333))
- Performance isn’t as important for special cases, so you can structure the special-case code for simplicity rather than performance. ([Location 3353](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=3353))
- Buffers are designed to reduce overheads from memory copying and dynamic storage allocation. ([Location 3357](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=3357))
- A Buffer stores what appears to be a linear array of bytes, but for efficiency it allows the underlying storage to be divided into multiple discontiguous chunks of memory, ([Location 3358](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=3358))
- The most important overall lesson from this chapter is that clean design and high performance are compatible. ([Location 3425](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=3425))
- Dealing with complexity is the most important challenge in software design. ([Location 3431](https://readwise.io/to_kindle?action=open&asin=B07N1XLQ7D&location=3431))