I just hope no one denies the great value of following these principles.
I am a great believer that by simply observing them every now and then, the quality of the code you create can improve by several orders of magnitude.
I also tried to do my part and spread the word within my company (and learn a lot in the process) and eat my own dog-food by following them in my code.
Last week, with a deadline hanging over my team's heads they proved to be invaluable (once again).
We were facing a common problem: the invocation of an operation over a number of possible receivers that do not share a common interface.
Over the table there were several design proposals and, being the deadline close, the Big, God-like, Monolithic, Cyclomaticly-ccomplex, Static and Modular-programming-like dispatcher came to our mouths.
We knew for sure that it was going to haunt us back very soon, but thinking sort-term is always an option (invoking KISS, YAGNI, and some other yadi yadi yada acronyms can make the lazy look fancy).
When I was about to implement the feature I could not help feeling dirty. A traitor to my preaches. A filthy programmer. A... ok, enough, you get it.
I took the risk. Educated risk. Controlled risk. The risk of doing what I felt it is the right way instead of the deadline-influence decision.
One Step At A Time
I started small. A simple spike, that can be easily rolled-back and substituted for a working thing. In order to do that substitution you need encapsulation. Since I was creating a dispatcher, a lousy dispatcher, a thousand-times, thousand-flavors classic dispatcher I separated the logic that dispatch the call from the logic to perform the operation. Simple SRSP application.
Divide and Conquer
Once they are split, responsibilities are easier to handle. But hey! the "guy" who performs the action, knows which are the conditions under which it has to execute the operation. So the conditional logic does not belong to the dispatcher, belongs to the "dispatchee". The dispatcher becomes now a simple registry, a chain of responsibility that has very simple logic: create a chain of possible dispatchers and when it has to dispatch, run over that chain and ask each member wether it is able to handle the call and delegate the execution of that call to that member of the chain.
To the last thought, follows the quick proof of concept that makes things real. And it can do it! It works! Heart pumping hard.
Present Covered, Think Ahead
Wicked!! Each dispatchee is autonomous in itself: its dependencies are injected in the constructor (DIP to the rescue), it is able to figure out when should be executed (from the context of the execution and asking its dependencies) and it also executes the operation perfectly.
And the dispatcher knows nothing of the dispatchee, just its mere existence. As a matter of fact none of the actors need to be changed if we have a new case to handle, we have also honored OCP!
Not so fast, cowboy. That is not entirely true. The dispatcher would need to be changed in order to create a new link of the chain.
Bummer! if only someone else could create that chain, the dispatcher would remain untouched and simpler to test (instantiating objects does not make a class nice to test, which smells of sub-optimal design).
But wait! We already have a IoC Container! If only I could twist it so that it automatically could follow a simple set of conventions... Jeepers Captain! It turns out I can "teach" him to scan a given set of types and apply custom registration code when it has to.
Quick safety-net test, some googling, an odd-hour of coding. And voilá. Our dispatcher no longer has to worry about who he has to dispatch to. The container will tell him.
Reaping the Benefits
We had several places in our codebase where this pattern needed to be applied (another sign that custom dispatchers were a no-no both in terms of elegance and quick of development) and after a couple of tweaks in the overall architecture, it was a breeze to deliver the feature.
Besides, the code was very similar in terms of behavior, so I could create a test helper and make sure that we play by the rules in each concrete implementation of the case.
Furthermore, as I was implementing all the cases I could definitely see a common pattern amongst the dispatchees. Another refactoring and I had all my dispatchees implementing a base class that handled the applicability of each. Oops, tests failing. What could it be? Oh God, oh no!! Changing the structure of the classes breaks my registry. I was lucky to have tests, that would have a been a tough one to catch. Fix. Green again.
No, it is not luck, it is called good engineering practices and whoever neglects them, should have very strong arguments on his behalf. I have my experiences on mine.
- Pressure should be no argument for sloppiness. If anything, should be another argument to do the right thing.
- Follow the principles. They are there to help you, they play in your team. Very smart guys thought of them before you, climb on their shoulders.
- I was able to solve my client's problems using less time than following the wrong path, plus making the development (and testing) of instances of the pattern a very quick process.
- Further changes (and we already knew some of those future requirements) can be quickly incorporated by the little framework, cutting-off development time, which will be used to deliver kick-ass useful features instead of boring plumbing hacks