# John Ousterhout - A Philosophy of Software Design (Highlights) ![rw-book-cover|256](https://readwise-assets.s3.amazonaws.com/static/images/article4.6bc1851654a0.png) ## Metadata **Review**:: [readwise.io](https://readwise.io/bookreview/33099217) **Source**:: #from/readwise #from/reader **Zettel**:: #zettel/fleeting **Status**:: #x **Authors**:: [[John Ousterhout]] **Full Title**:: A Philosophy of Software Design **Category**:: #articles #readwise/articles **Category Icon**:: 📰 **URL**:: [milkov.tech](https://milkov.tech/assets/psd.pdf?utm_source=substack&utm_medium=email) **Host**:: [[milkov.tech]] **Highlighted**:: [[2023-10-13]] **Created**:: [[2023-10-14]] ## Highlights - This means that the greatest limitation in writing software is our ability to understand the systems we are creating. ([View Highlight](https://read.readwise.io/read/01hchkxt9x5qjan8fq3q0tpapj)) ^609268628 - The first approach is to eliminate complexity by ([View Highlight](https://read.readwise.io/read/01hchm32325t98h56yvhjm6hjw)) ^609269410 making code simpler and more obvious. - 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. This approach is called modular design. ([View Highlight](https://read.readwise.io/read/01hchm4vcw64gr0ez9yj322a6d)) ^609269474 ### Chapter 4 Modules Should Be Deep - An abstraction is a simplified view of an entity, which omits unimportant details. ([View Highlight](https://read.readwise.io/read/01hck56mdaxqhpvrsw7nnp81n4)) ^609596452 - The key to designing abstractions is to understand what is important, and to look for designs that minimize the amount of information that is important. ([View Highlight](https://read.readwise.io/read/01hck593jrd0m6sp4c2b3kedmr)) ^609596575 - To visualize the notion of depth, imagine that each module is represented by a rectangle, as shown in Figure 4.1. The area of each rectangle is proportional to the functionality implemented by the module. The top edge of a rectangle represents the module’s interface; the length of that edge indicates the complexity of the interface. The best modules are deep: they have a lot of functionality hidden behind a simple interface. A deep module is a good abstraction because only a small fraction of its internal complexity is visible to its users. ([View Highlight](https://read.readwise.io/read/01hck5dhy8pz5dvtwntr9dh7z6)) ^609596876 - Unfortunately, the value of deep classes is not widely appreciated today. The conventional wisdom in programming is that classes should be small, not deep. Students are often taught that the most important thing in class design is to break up larger classes into smaller ones. ([View Highlight](https://read.readwise.io/read/01hck5ktr6e4zhtn03h2gz31vy)) ^609597014 ### Chapter 5 Information Hiding (and Leakage) - 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. ([View Highlight](https://read.readwise.io/read/01hchmat6ymqmgf6qd5ja0rj58)) ^609269815 - Information leakage is one of the most important red flags in software design. ([View Highlight](https://read.readwise.io/read/01hchmgkyafwvrf1j0ryya2b9a)) ^609270143 #caveat - When designing modules, focus on the knowledge that’s needed to perform each task, not the order in which tasks occur. ([View Highlight](https://read.readwise.io/read/01hcj029s5kecs64env754h20j)) ^609318642 - 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. ([View Highlight](https://read.readwise.io/read/01hcj0379wess9ycwna2skk32g)) ^609318706 - information hiding can often be improved by making a class slightly larger ([View Highlight](https://read.readwise.io/read/01hcj07tx508jd9h0e703sr5e5)) ^609319452 - Whenever possible, classes should “do the right thing” without being explicitly asked. Defaults are an example of this. ([View Highlight](https://read.readwise.io/read/01hcj0gq6mnng4mwcx9qgp5zg8)) ^609320443 - 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. ([View Highlight](https://read.readwise.io/read/01hcj0j9btm7rvewx6cfgmvq9q)) ^609321077 <!-- New highlights added October 19, 2023 at 8:09 PM --> ### Chapter 11 Design it Twice - Even if you are certain that there is only one reasonable approach, consider a second design anyway, no matter how bad you think it will be. It will be instructive to think about the weaknesses of that design and contrast them with the features of other designs. ([View Highlight](https://read.readwise.io/read/01hd41jsb1dbah0bc400j7ysd3)) ^612857151 - The best choice may be one of the alternatives, or you may discover that you can combine features of multiple alternatives into a new design that is better than any of the original choices. ([View Highlight](https://read.readwise.io/read/01hd41pzk220d273axhhce9d11)) ^612857622 ### Chapter 12 Why Write Comments? The Four Excuses - Nonetheless, there is still a significant amount of design information that can’t be represented in code. ([View Highlight](https://read.readwise.io/read/01hd421g2p1nr7tbvmgv53v3jh)) ^612860217 - If users must read the code of a method in order to use it, then there is no abstraction: all of the complexity of the method is exposed. ([View Highlight](https://read.readwise.io/read/01hd423f66jwjrcsex5z3rm5xw)) ^612860274 - many of the most important comments are those related to abstraction ([View Highlight](https://read.readwise.io/read/01hd427gnp0vhtpyay281jgfna)) ^612860440 - 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. ([View Highlight](https://read.readwise.io/read/01hd429f7bc6afe2ff8450rg44)) ^612860542 <!-- New highlights added October 22, 2023 at 5:51 AM --> ### Chapter 2 The Nature of Complexity - Complexity is caused by two things: dependencies and obscurity. ([View Highlight](https://read.readwise.io/read/01hd8xdamhznwwckdtbsy4n6t1)) ^613799515 ### Chapter 10 Define Errors Out Of Existence - Furthermore, exception handling code creates opportunities for more exceptions. ([View Highlight](https://read.readwise.io/read/01hd5x2w1vjasdz4bfe2248vm0)) ^613351110 - 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. ([View Highlight](https://read.readwise.io/read/01hd5xbk7zkj8q4rtxwtynwmpy)) ^613351736 #y - Creating a recovery mechanism for crashed servers was unavoidable, so RAMCloud uses the same mechanism for other kinds of recovery as well. This reduced the amount of code that had to be written, and it also meant that server crash recovery gets invoked more often. As a result, bugs in recovery are more likely to be discovered and fixed. ([View Highlight](https://read.readwise.io/read/01hd5xtc5mqz2n8qkwzb1psazs)) ^613353093 ### Chapter 13 Comments Should Describe Things that Aren’t Obvious from the Code - Comments augment the code by providing information at a different level of detail. Some comments provide information at a lower, more detailed, level than the code; these comments add precision by clarifying the exact meaning of the code. Other comments provide information at a higher, more abstract, level than the code; these comments offer intuition ([View Highlight](https://read.readwise.io/read/01hd94wk6qp24m3s7n2zdpx144)) ^613870187 ### Chapter 14 Choosing Names - Good names have two properties: precision and consistency. ([View Highlight](https://read.readwise.io/read/01hd8xywhcank98b602s6jm5ga)) ^613803226 - The process of choosing good names can improve your design by identifying weaknesses. ([View Highlight](https://read.readwise.io/read/01hd93rqd9pw2kr084s0px5znt)) ^613865313 <!-- New highlights added October 26, 2023 at 2:37 AM --> ### Chapter 3 Working Code Isn’t Enough - In the tactical approach, your main focus is to get something working, such as a new feature or a bug fix. ([View Highlight](https://read.readwise.io/read/01hdggz2e20dxc3d8dsq75hj5c)) ^615347186 - Your primary goal must be to produce a great design, which also happens to work. This is strategic programming. ([View Highlight](https://read.readwise.io/read/01hdgh95j0fmrsd14yebtqg7sd)) ^615347724 - Thus, the best approach is to make lots of small investments on a continual basis. I suggest spending about 10– 20% of your total development time on investments. This amount is small enough that it won’t impact your schedules significantly, but large enough to produce significant benefits over time. ([View Highlight](https://read.readwise.io/read/01hdghajq464tac1x2dn7nw11v)) ^615347786 - Another thing to consider is that one of the most important factors for success of a company is the quality of its engineers. The best way to lower development costs is to hire great engineers: they don’t cost much more than mediocre engineers but have tremendously higher productivity. However, the best engineers care deeply about good design. If your code base is a wreck, word will get out, and this will make it harder for you to recruit. ([View Highlight](https://read.readwise.io/read/01hdghdpqmdfbx5wwr0nywhzz5)) ^615348092 - Over time the company realized that its culture was unsustainable. Eventually, Facebook changed its motto to “Move fast with solid infrastructure” to encourage its engineers to invest more in good design. ([View Highlight](https://read.readwise.io/read/01hdghf13ftz8qng9zwn143610)) ^615348138 ### Chapter 6 General-Purpose Modules are Deeper - In my experience, the sweet spot is to implement new modules in a somewhat general-purpose fashion. The phrase “somewhat general-purpose” means that the module’s functionality should reflect your current needs, but its interface should not. Instead, the interface should be general enough to support multiple uses. ([View Highlight](https://read.readwise.io/read/01hdk6978xjbqy9kc3n0mydr8g)) ^615961986 ### Chapter 7 Different Layer, Different Abstraction - 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. ([View Highlight](https://read.readwise.io/read/01hdk6nycq3j0e8gvz86xaraxe)) ^615963693 - 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. ([View Highlight](https://read.readwise.io/read/01hdk725vym8xr95cb17z78kxa)) ^615964466 ### Chapter 15 Write The Comments First - 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. ([View Highlight](https://read.readwise.io/read/01hdgg89gxntnte97qz30m2q3y)) ^615345179 ### Chapter 16 Modifying Existing Code - 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. ([View Highlight](https://read.readwise.io/read/01hdggejvhmmrqa9dqem0583t7)) ^615345384 However, it messes up the bug fixings and features with refactoring. - Whenever you modify any code, try to find a way to improve the system design at least a little bit in the process. If you’re not making the design better, you are probably making it worse. ([View Highlight](https://read.readwise.io/read/01hdggfw7nxayk7y7xnqzskm2k)) ^615345412 - Ask yourself “Is this the best I can possibly do to create a clean system design, given my current constraints?” ([View Highlight](https://read.readwise.io/read/01hdggkpf0489ccad6sqnv2204)) ^615345538 Apply this rule by considering the trade-offs under the constraints. - 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. ([View Highlight](https://read.readwise.io/read/01hdggqnyf7pk5rkcytwvcwez3)) ^615346233 - 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. ([View Highlight](https://read.readwise.io/read/01hdggtmxt1r0ws55j722y247d)) ^615346871 <!-- New highlights added October 28, 2023 at 6:46 AM --> ### Chapter 8 Pull Complexity Downwards - it is more important for a module to have a simple interface than a simple implementation. ([View Highlight](https://read.readwise.io/read/01hdnw4tc6jk44rhmf3yzvfz0g)) ^616523234 ### Chapter 9 Better Together Or Better Apart? - 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 ([View Highlight](https://read.readwise.io/read/01hdnwdt5dp9b2ddep59443xr7)) ^616524093 - If the components are truly independent, then separation is good: it allows the developer to focus on a single component at a time, without being distracted by the other components. On the other hand, if there are dependencies between the components, then separation is bad: developers will end up flipping back and forth between the components. ([View Highlight](https://read.readwise.io/read/01hdnwen5733yfp57rpq5q4mky)) ^616524112 Trade-off between inner complexities in modules and inter dependencies between modules. - Bring together if information is shared ([View Highlight](https://read.readwise.io/read/01hdnwgse6k0x15txqyazcv4ga)) ^616524205 - Bring together if it will simplify the interface ([View Highlight](https://read.readwise.io/read/01hdnwh4tcn5r4crvznrtagav1)) ^616524213 - Bring together to eliminate duplication ([View Highlight](https://read.readwise.io/read/01hdnwhacz7zfwx9j5sa4jbh4e)) ^616524216 - Red Flag: Special-General Mixture 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. ([View Highlight](https://read.readwise.io/read/01hdnwpxfw64y0ejc1kkgejqpy)) ^616530188 ### Chapter 17 Consistency ### Chapter 18 Code Should be Obvious ### Chapter 19 Software Trends - The problem with test-driven development is that it focuses attention on getting specific features working, rather than finding the best design. ([View Highlight](https://read.readwise.io/read/01hdr7va6evsr52sg4fb24mrc5)) ^617143054 - Once you discover the need for an abstraction, don’t create the abstraction in pieces over time; design it all at once (or at least enough to provide a reasonably comprehensive set of core functions). This is more likely to produce a clean design whose pieces fit together well. ([View Highlight](https://read.readwise.io/read/01hdr7wffra35pdtmjtctckqqx)) ^617144190 - 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. This is the best way to make sure you really have fixed the bug. ([View Highlight](https://read.readwise.io/read/01hdr7xekfacq6vyrm15k3ear1)) ^617144236 ### Chapter 20 Designing for Performance - 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. The key is to develop an awareness of which operations are fundamentally expensive. ([View Highlight](https://read.readwise.io/read/01hdr81dy3w7efmbpghqz443sq)) ^617144502 - Assume that you could completely redesign the system in order to minimize the code that must be executed for the critical path. Let’s call this code “the ideal.” ([View Highlight](https://read.readwise.io/read/01hdr8c3vd549w8pacmht4ysdz)) ^617144948 - The next step is to look for a new design that ([View Highlight](https://read.readwise.io/read/01hdr8d0rj372j8xms72jbqyng)) ^617144972 (cont.) comes as close as possible to the ideal while still having a clean structure. - One of the most important things that happens in this process is to remove special cases from the critical path. When code is slow, it’s often because it must handle a variety of situations, and the code gets structured to simplify the handling of all the different cases. ([View Highlight](https://read.readwise.io/read/01hdr8b73yk3xc6a43q59t1zjb)) ^617144917 ### Chapter 21 Conclusion