Robert C. Martin (Uncle Bob) wrote something in The Clean Coder that has rocked my little world:
Much has been written about the principles and patterns of software design that support structures that are flexible and maintainable. Professional software developers commit these things to memory and strive to conform their software to them. But there’s a trick to this that far too few software developers follow: If you want your software to be flexible, you have to flex it!
The only way to prove that your software is easy to change is to make easy changes to it. And when you find that the changes aren’t as easy as you thought, you refine the design so that the next change is easier.
I’m seeing the benefits of this kind of thinking with interface_enforcement, a library I’m developing for elective test enforcement in Ruby.
The wall of doubt
I started out by implementing interface enforcement as a proxy. This produced code that seemed fine. However, I had a few doubts about distribution of responsibility and clarity of abstraction, and I couldn’t think my way through them.
Experimental refactoring helped a lot. I extracted ad absurdum from one bloated family of classes, and gained insight into the true nature of the family. Then I inlined most of what I’d abstracted, but now into fewer, more clearly defined collaborators.
Still, there remained an opaque core in there, that I couldn’t break into.
So I took Uncle Bob’s advice one step further and started working on an alternative enforcement strategy prematurely. I say prematurely because I was pretty sure I would never ship it, and the primary strategy of proxying wasn’t finished by a long shot.
The additional strategy is enforcement by injection. How it works doesn’t contribute to my point. What matters is that it’s a different way to apply an interface to an object. It’s a different delivery mechanism for the core value of the library.
This additional use case applied pressure to my code from a different angle. All of a sudden, inappropriate responsibilities and missing abstractions leaped out at me. Being forced to distinguish an interface from the method of applying it exposed opportunities for valuable refactorings toward cleaner code in surprising places.
I’m pretty sure that the injection strategy is a dead end. Sure, it will make automatic interface enforcement possible. But I’m not sure how desirable automatic enforcement is, and injection is brittle with dynamic test subjects. Proxying is verbose, but I have a strong intuition that it’ll be bullet proof.
No, the primary value of an additional delivery mechanism is not in its usefulness as product. The value is in its usefulness as design constraint. Having pressure applied from a different angle highlights inappropriate coupling and complicated decoupling.
If you want your software to be flexible, you have to flex it!
I know Uncle Bob’s advice was intended to support continual refactoring. But sometimes great advice extends to multiple levels. And on this young project, I’m already benefiting from developing two delivery strategies, even though I plan to ship only one. And who knows? Maybe the second strategy will turn out better than I can imagine right now. Only more use cases and refactorings will tell.
P.S. Okay, I lie. I don’t plan to ship interface_enforcement at all. It’s an exploration, and clearly disclaims itself as such on the front page. Shipping isn’t the point. :-P