Agile Principles, Patterns and Practices in C# by Robert Martin, aka Uncle Bob, is another good, accessible read on how to design code. This book is aimed at a higher level of abstraction than Clean Code and with that comes more good insights into ways to design code.
If you have heard about the SOLID principles and have not read this book (or the earlier Agile Software Development Principles, Patterns and Practices), you should. You may learn some interesting insights into what Uncle Bob thinks these things mean which you may or may not have thought through yourself. In the murky world of software development where things mean a half a dozen different things to different people, going back to the person who originally wrote about the terms can often generate insights that may have been diluted or diverged from the original thinking – for good or bad.
Emergent Design and TDD
The emergent design / TDD example is an inspirational sample of the art. The bowling ball kata is focused on a pairing session that results in far different – and potentially far simpler – code than attempting to do full OO analysis up front. The bowling ball example does not get implemented with objects representing classic nouns from bowling, but instead a much simpler design evolves that meets the needs of the requirements. I feel that this is the best example that I’ve seen of where TDD works to simplify code and allows the design to emerge instead of being dictated up front. This is achieved by initially doing the simplest thing; and then being able to refactor freely underneath the API that is set up to meet the requirements.
A chapter is dedicated to each part of SOLID. Despite knowing a reasonable amount about SOLID from both Clean Code and other reading, I still picked up several new insights along the way.
In dependency inversion – the idea of the consumer owning the interface – not the implementation was new to me. It is interesting to experiment with this idea and try it out to determine what it means to the code I work on.
I enjoyed the fact that Uncle Bob does talk about caution / moderation in using SOLID and patterns. For instance – when do you apply the Open / Closed principle? Well a reasonable answer is when you are making a change to the code – then close it against changes of the same kind in the future. Don’t proactively attempt to protect the code against all potential and fantasized possible changes as most likely you’ll miss the actual way it will need to change, while complicating the code.
When does something violate SRP? When it needs to change for more than one reason. But if it isn’t changing perhaps it doesn’t matter right now if you can look at the code and potentially see multiple responsibilities – as long as the code is concise enough and the design isn’t getting in the way now. When it does change then perhaps extract the responsibility that is changing using SRP to guide that decision.
I can see some enthusiastic zealots reading this book and the SOLID chapters in particular and going out into the world with the One True Way to write code – with interfaces on everything and closed to all possible changes in the future and so on. This isn’t what is being suggested, but I can see some who desire the Answer™ coming away from the book with that as the Answer. Based on my reading – particularly on people commenting on Dependency Inversion overuse – this has already been done in spades. That isn’t the idea. The idea is to understand the principles so that they can be used effectively to help you build code that can change more easily in the future.
Coffee Maker example
The Coffee Maker example is an example of what a zealot may take away from this book. The end result is an awesomely designed pluggable thing using all the principles and several patterns to the max. It is a thing of beauty. And it is something that I have never experienced needing to implement and would be massive overkill for most business applications that I’ve worked on. The beautiful polymorphic design is often excessive overkill in real world business apps – and excessive excitement about plugability that isn’t a requirement often leads to convoluted messy code bases – something that I don’t think Uncle Bob is pro and something that I have seen (and sometimes done…) while search for “good design”.
A later chapter in the book discusses how to slowly refactor towards a better design as requirements come in. The example shows the process of refactoring towards a given pattern. That is the key and most important take away from this book that I suspect some may miss. The coffee maker example should not launch itself onto the world in its final perfect polymorphic form. The requirements should drive it there. And the beautiful design that emerges based on those requirements is really interesting to analyze and appreciate – assuming the requirements needed it to emerge.
UML… still is boring to me
There are several chapters in the middle of the book that are around UML. I know why they are there. I appreciate it and thank Uncle Bob for the effort. However I had to drag myself through those chapters. For some reason I find UML really boring. Though I know I shouldn’t. I know I should use it, and I know I should use it right. Maybe when I grow up more I’ll get back to those chapters and use UML better.
However – the key take away is that design is not dead. Drawing pictures as conversation pieces to convey design is useful. And UML is a tool to help developers communicate more effectively. Communication is very important. So do it. But don’t write too much of it… rather code.
Patterns, patterns, patterns!
The final chapters of the book are dedicated to different patterns – such as the bridge and adapter patterns. There are several concrete examples of how these patterns can be used as well as an example of refactoring, based on incoming requirements, towards a specific pattern which ends up with a more SOLID design.
Again, the patterns shown are end goals, driven by requirements that make the design change. As the design changes it becomes obvious that one pattern or another may help to keep the code clean. At that point, the code is refactored towards a pattern. It may occur that other design changes make the pattern no longer useful. At that time, refactor away. The idea isn’t to build the code out of the blocks to support a selection of hand picked patterns. That would be possible, but the requirements haven’t driven their need and so the code may be far less simple and effective than otherwise.
Drive from the requirements
This was a great read for me. It gave me new insights and drove my understanding of some things. I always enjoy that. It also highlighted for me the continued need to drive from requirements and refactor towards useful patterns rather than trying to force them up front.