A Philosophy of Software Design - APOSD, is a book originally released in 2018. It’s not nearly as popular as Clean Code (2008), but it is slowly gaining popularity. I think it’s a refreshing update for all the Clean Coders out there.
John Ousterhout tries to change software development from a craft/talent to more of an engineering discipline. Where a set of repeatable techniques and tools exist that anyone can learn. So he comes up with a course on it, and this book summarizes the learnings.
Complexity
Is the core issue and consideration. If code is not obvious to the reader, it’s not the readers fault - it’s the writers shortcoming.
The book mainly lists the causes of complexity, and how to handle them. In my opinion it’s a bit less clear-cut than Clean Code. Sometimes the optimal solution appears somewhere in the middle, and its up to the developer to figure it out case by case. Sounds like real life.
Some notes
- Deep modules - Your unit of code (function, module, etc) has a public part (interface, public fields, etc.), and a private part (the implementation). It all comes down to the ratio of whole interface to the implementation. If they have similar level of complexity (and/or size), this means the interface is bad, it’s not hiding anything from the consumers. To keep complexity down, the interface should be very small, simple and generic. And it’s fine to have complex and long implementation to accomplish that. Following POSD you should rarely need to read it.
- Generic classes reduce complexity - Common advice when building a new feature is to not over-engineer it. Just write the specialized solution and later generalize it if the need arrives. Experience from APOSD is different. Taking a bit of time to create a more generalized interface, even if it’s only to support that one case (and nothing else). Leads to less complexity because there are less implementation specific details floating around the codebase. You can keep the details private at your feature, and generic part at some library.
- Comment your code - Again, consumers should not need to read the implementation. But the interface itself may still be lacking context. Comments complete this abstraction, they can provide extra information for the interface.
- Keep interface and implementation comments separate - Interface shouldn’t leak implementation details, this also applies to comments. However, inside the class, the implementation comments are welcome. It reduces the effort for future modifications. If you comment what and why is going on (but not how), it quickly becomes obvious for future developers what to keep in mind.
- Splitting or combining modules to make them deeper - if you have classes that operate on same data or are in some other way very coupled. You might reduce the combined interface surface if you just combine them to one class. This results in less interface, more implementation - a deeper module! On the other hand if you have a complex class that does a lot of different things and thus results in a big complex interface. By splitting it apart you might get a few smaller classes with much cleaner (unrelated) interfaces, therefore increasing the deepness again!
- Keep levels of abstraction consistent and separate - Code in a feature module is in a different level of abstraction compared to some common library assisting it. If you notice that these modules become very similar, implying similar level abstraction, you may have an issue and need a redesign.
- Avoid obscurity - if you change a piece of code, is there another piece of code somewhere that should also be changed? Is that obvious to developers? If not, you have obscurity!
- Exceptions, configuration - Throwing exceptions, or requiring to specify some configuration parameters to use your module. Both are similar as they require someone outside your module to do decisions for you. Ideally handle exceptions yourself, or come up with a different model that has less exception states. With configuration: you should be able to provide defaults, or determine the configuration on the fly. Make the common use of the module as simple as possible.
APOSD VS Clean Code
John Ousterhout and Robert C. Martin have been civil enough to discuss the differences and publish the outcome. Generally they agree, but APOSD does differ in 3 ways:
- Method length - Methods should generally be long enough to contain the whole solution. This reduces the interface to implementation ratio and thus makes the method deeper. Clean Code famously recommends keeping them very short, but the trouble with that is you end up with a huge amount of methods.
- Comments - APOSD considers structured comments necessary to give the context. Clean Code recommends very detailed naming instead of comments, assuming that the team is smart enough to know the context.
- Test Driven Development - APOSD considers TDD a bit too short-sighted. If you only focus on fixing the next test case you might not spend time on designing the component as a whole.
Conclusion
Recommended - I find this book very helpful and believable, mostly as it aligns with my experience. I’ve read codebases where I’m not sure what’s going on, I’ve seen innocent changes in one area blow up another. Good to confirm it might be the design itself that still causes problems.