Book Review: A Philosophy of Software Design


TLDR

Rating: 4 out of 5.

This book is a great read and quite remarkable in its brevity. 

The author made multiple insightful points; I found myself nodding vigorously in agreement with the numerous insightful gems spread across the book.

Ultimately, the book’s core thesis is minimizing complexity in software development by adopting complexity-eliminating approaches. The upfront investment in learning and adopting better designs pays off because it leads to high-quality software.

Things I like about the book

  • Short and sweet: Concise with little cruft.
  • Clarity: Expressive terminology, examples, and diagrams for succinctly conveying complex concepts; lucid diagrams simplified the explanation of complicated sections like pass-through methods and shared variables.

Things that could be better

  • More examples: having a wide variety of real-life examples from multiple projects would have helped.
  • Broader scope: The book does not discuss other aspects of software development: testing, reviews, release agility, etc.

Who should read this book?

I recommend this book to software engineers and line managers because of its tactical focus and valuable advice. I do not think upper-level managers would find it very useful since they are usually farther away from low-level details.

Get the book from Amazon here.

Don’t miss the next post!

Subscribe to get regular posts on leadership methodologies for high-impact outcomes.

Join 1,229 other followers

Chapter Notes

Chapter 4: Modules Should Be Deep

  • The best modules have interfaces that are simpler than their implementations.
  • The safest interfaces are fully explicit; module specifications should contain all the information a developer needs. Implicit interface aspects lead to tribal knowledge, especially since these esoteric details can’t be checked by tools, compilers, or programming languages.
  • Prioritize the most common use cases during design and provide specialized handling for non-common scenarios.

Chapter 7: Different Layer, Different Abstraction

  • A pass-through method does nothing except pass its arguments to another method. Having multiple functions with the same signature typically indicates a design smell (there is not a clean separation of responsibility between classes).
    • Notes: This resonated because I have committed this design flaw in the past; pass-through methods are challenging to debug and understand.
  • The interface to some piece of functionality should be in the same class that implements the functionality.
  • Pass-through variables are tougher to handle – using a shared context is a great way to avoid this.

Chapter 10: Define errors out of existence

  • Exceptions breed more exceptions; for example, retries can necessitate more code to handle new conditions, e.g., thundering herds, duplicate messages, and rate-limiting. It is also tricky to replicate certain exceptions in test environments, e.g., IOExceptions.
  • Throwing exceptions is easy; handling them is hard. If you are having difficulty figuring out how to handle a case, punting that decision to callers is also suboptimal since they probably will find it doubly tricky too.
  • Exceptions lead to complex interfaces; some argue that defining errors out of existence will miss out on catching bugs; the counterargument is that handling exceptions necessitates writing more code to handle such exceptions. Given the complexity of software, less is more, simple > complex. More code, more bugs. 
  • I like RAMCloud’s approach of crashing a server whenever a corrupt object is discovered. This technique significantly increases the recovery cost; however, simplification and frequent code execution benefits make up for that cost.

Chapter 11: Design it Twice

  • Having at least two designs before implementation leads to better architecture – allows for objectively comparing and contrasting approaches.
  • Maintaining well-designed software is cheaper than reworking poor architectural choices.

Chapter 12: Why Write Comments? The Four Excuses

  • I like his approach of estimating how much time engineers spend writing code (versus designing, reviewing, compiling, testing, etc.) and using that to set an upper bound for writing documentation.
  • Overall, this chapter felt light on detailed examples.

Chapter 13: Comments Should Describe Things that aren’t obvious from the code

  • Cross-module documentation wherein updating a single file (e.g., adding a new status code) leads to multiple changes across multiple files. The suboptimal but widespread solution for this ubiquitous challenge is to consult the experts and leverage their tribal knowledge. I love his recommendation of using comments to signal what files to change; this empowers the entire team and reduces cognitive overload.
  • Don’t Repeat Yourself (DRY) applies to documentation, especially for scenarios involving disparate files. Consider creating a central file to hold all the details and referring to that file from consuming locations.
  • I can’t entirely agree with the pedantic approach to over-commenting everything. Comments are not an end to themselves; instead, they serve to cognitive overload. 
  • Examples from RAMCloud dominated the chapter; a variety of samples would have been more useful.

Chapter 14: Choosing Names

  • Poor naming compounds negatively since software projects have thousands of variable, method, and class names. A single lousy name has a limited impact; however, many bad labels will lead to obscurity and increase complexity.
  • Names are abstractions; thus, great names are precise, unambiguous, and intuitive. 
  • If you struggle to name a variable, that might indicate underlying design issues.
  • Consistency in naming: if it looks like a duck, swims like a duck, and quacks like a duck, then it better be is a duck.

Chapter 15: Write The Comments First

  • Putting off documentation as the last thing in the development process leads to poor quality docs; frontload it and do it first.
  • Struggling to document a method or class might indicate poor design.

Chapter 16: Modifying Existing Code

  • If you are not making the overall system design better with each change, you are probably making it worse. Entropy is the default state of systems.
  • I disagree that keeping comments next to code mitigates the code rot problem fully. It ameliorates it but does not solve it significantly.
  • I like the heuristic: the farther away a comment block is from its implementation, the more abstract it should be
  • DRY applies to comments and documentation too! The author breaks the rule by regurgitating some past work in this section.

Chapter 17: Consistency

  • Consistency reduces cognitive overload; it creates mental leverage because developers can pattern-match. Naming and coding conventions help (as well as a healthy dose of nit-picky code reviews and documenting team engineering expectations).
  • Rarely introduce new styles – the disruptive cost of losing consistency rarely justifies the price. Stick with established styles even if they are not the most perfect.

Chapter 18: Code Should be Obvious

  • Obscure code requires a lot of time and energy to understand and raises the chances of bugs and misunderstandings. The long-term cost of maintenance outweighs any short-term benefits to the code author.
  • C# tuples and Java Pairs return multiple values from a method but increase obscurity, especially since their extractors are generic (e.g., getValuegetKey). It is better to introduce new strongly-typed types that eliminate all ambiguity.
  • I do not fully agree with the recommendation to always use the same types for allocation and declaration. While I agree with the obscurity; I think using interfaces helps with deeper modules; a good example is using IEnumerable in method signatures but casting it to a List in the method body. 

Chapter 19: Software Trends

  • The author posits that TDD focuses on getting features working (tactical) at the expense of finding the best design (strategic).
  • I like his perspective that getters/setters mostly add clutter.
  • Whenever a new software development paradigm pops up, challenge it; does it minimize complexity? Think about obscurity, complexity, cognitive overload, bloat, etc.

Chapter 20: Performance

  • This chapter read like a tack-on to meet book-length requirements. 
  • Examples of expensive calls:
    • A round trip network call within a data center can take 10 – 50μs (which is 10k x instruction times)
    • I/O to secondary storage (disks) can take 5 – 10ms (millions of instructions time); newer storage can be as fast as 1μs (which is about 2k instruction times).
  • Performance might not be essential for special cases, so ensure the critical path is clear and handle edge cases with simplicity.

Don’t miss the next post!

Subscribe to get regular posts on leadership methodologies for high-impact outcomes.

Join 1,229 other followers

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.