• Complexity is not about how sophisticated a system is or how many features it has (though it is true that big systems tend to be complex), but how easy it is to maintain.
  • Complexity typically manifests itself in a software project in three major ways ordered in terms of increasing severity:
    • When a simple change requires modifying code in many places.
    • When developers need to carry a lot of information in their heads to complete a task.
    • When it is not obvious what information or changes are needed in order to carry out a task (unknown unknowns).

How do software systems become complicated?

  • Complexity is incremental. It’s usually not one thing that makes a system complicated but an accumulation of bad decisions over a period of time.
    • Dependencies - dependency between two or more parts of a system is only desirable if the dependency is clear and obvious.
    • Obscurity - This occurs when some important information about the system is not obvious.

Cultivating the right mindset

  • The author distinguishes between programming with a tactical or strategic mindset. The former is the default in most companies, and it focuses on getting features working as fast as possible. This mindset is usually in favour of taking a shortcut or two in order to get something working quickly.
  • A strategic programmer on the other hand recognises that “working code” is not a good enough standard if long-term maintainability is a paramount concern. Instead of trying to ship something as quickly as possible, this mindset encourages taking some time to find the best design that also happens to work.

How to design modules

  • According to the book, a module is a relatively independent unit of code with an interface and an implementation. It can take many forms such as a function, class, package, or service.
  • The interface of a module describes what the module does, but not how it is implemented.
  • **Provide good defaults **
    • The interface of a module should make its common use cases as simple as possible.
    • When almost every user of a module requires a certain behaviour, it should be provided by default, with an easy way to opt out.
  • Favour deep modules over shallow ones
    • Deep modules are those that provide simple interfaces to complex functionality, while shallow ones are those that have a complicated interface without hiding much complexity.
    • Shallow modules add complexity to a system because of the cost of learning and using their interfaces, but without providing a compensating benefit by hiding complicated implementation details.
    • The author is specifically against the idea that classes or methods should always be small which is the prevalent view in the industry. He argues that this philosophy tends to produce a large number of shallow modules.
  • Hide unimportant information
    • Each module should omit details about its implementation from a its interface so that it is invisible to other modules.
    • When applying this principle, ensure that only unimportant details are omitted from the interface.
    • If details that are important to the abstraction are left out of an interface, it will obscure the correct use
  • Eliminate unnecessary errors
    • A good way to reduce exception handling complexity is to consider if an exceptional case can be eliminated entirely.

The practice of in-code documentation

  • Comments aim to capture the information that was in the mind of the module designer, but couldn’t be represented in the code. This can be the reasoning behind a key design decision, or a specific quirk that motivates a piece of code.
  • Debunking the myths of commenting code
    • Good code is self-documenting — No matter how good the code is, it cannot capture the informal aspects of a module’s interface such as why a particular design decision was made
    • No time to write comments — Taking the time to write comments is part of the investment mindset discussed earlier.
    • Outdated comments become misleading — Comments can sometimes get stale, but it shouldn’t be a huge effort to keep it up to date especially if comments are positioned next to the code they describe.
    • All comments are worthless — Many software developers question the usefulness of comments due to how many useless comments they’ve seen in the wild.
  • **Some guidelines for writing good comments **
    • Comments should describe things that cannot be inferred from the code.
    • Interface comments (those that provide a high-level information on how to use a class or method) should not describe the implementation
    • When documenting implementation details, focus on the what and why of the code.
    • Write comments when writing the code.
  • **Choosing identifier names **
    • The quality of names in a software system can affect its perceived complexity in non-trivial ways.
    • Good names provide clarity by telling the reader what an entity is and what it is not, which reduces the need for extensive comments.

Test Driven Development considered harmful

  • While the author is in favour of writing unit tests, especially because they aid in catching problems while refactoring, he is not a fan of Test Driven Development (TDD).
  • TDD encourages tactical programming through its emphasis on getting specific features working instead of finding the right design
  • The only time he recommends using TDD is when fixing bugs that can be reproduced using a test beforehand

Conclusion

  • A Philosophy of Software Design presents some fresh ideas on the practice of designing software at a relatively high level. The author is not afraid to go against conventional wisdom, and he does a good job of explaining how he came about the ideas