dimanche 11 juillet 2010

Repeat after me: Interface is no more the silver bullet

One week ago, I started a new mini-project at my new job. This is not a big one, around one man-month, so that I can become more familiar with my new ecosystem.
So far, everything is fine and working great. Everything, except one little sentence my mentor told me just before I started crafting my first line of code.
He basically told me:
Oh! And don't forget to use Interfaces, it's easier to debug and better if we need to upgrade the project later. We did not do it previously, and it causes us some hard time with maintenance of legacy code.
So what? Nothing is wrong with what he says, right? They experienced some bad situation and learn from theirs errors. That's a good point. But let's go deeper.

We basically learn via two stimuli: failures and success.

Success is great, it improves your self-esteem, help you gain more leadership and rewards from your colleagues. There are two types of successes: the "classical" success, which basically is a "not a failure", the outstanding one, where you might have try to innovate and took some risk to achieve your goal.
As we are generally lazy, lots of us got satisfied with the classical success.

On the other hand, Failure is a very powerful stimuli, because it hurts. Physically or emotionally. Nobody likes to be blame for his/her job, and avoiding that is a strong motivator to change our behaviors. As a side effect, this motivator is most of the time driven by fear, and fear has the major drawback that it strongly restrains your horizon once you found something that works.

It seems to me that the good practices in the Java world is currently mostly driven by failure. Sun itself did big mistakes (Hashtable? AWT? Date? File? Don't get me started...), and not using the interfaces was one of the pain that most of the current developers did face at least once in it's carrier.

Right, we learned. Interfaces are good and we should use them. But please, use them when it makes sense!

So, here are some of the common often inaccurate arguments I've heard about using Interfaces.

And what if we need to target another database? 

Sort answer: YAGNI!

How often did you ever had to switch from a database to another in a single project?
Changing the database back-end is a heavy requirement change, and it's quite rare that you'll be confronted to it.
It might happened, but then either it will be a minor shift (a newer release of the same DBMS, or a close concurrent), and it should probably not affect you at all; either it might be a radical change (Hello NoSQL!) and then the the chance that your interface fits the new paradigms is quite low.
Layer isolations is the key there. Interface are most of the time a irrelevant extra step.

It helps me to test my code!

Short answer: Are you from the past?

At least, you're performing test, that's a good point. And you probably even try to perform test in isolation, that's even better!
Ok, but didn't you ever heard about all this wonderful set of Mocking frameworks out there? It's been a long time that we can now Proxy classes and instances of a class in order to isolate the unit we want to test. Proxy class exist in Java since version 1.3! Sure it's not the most user friendly, but the idea is present in the Java environment at least since year 2000!

I need it to perform Dependency Injection!

Short answer: Read the previous answers.

Ok, Dependency Injection is a good practice. DI is easy. DI is cool (at least it was two years ago). But seriously, do you really need it everywhere?
I've seen too many open source projects applications that were build that way: a typical MVC pattern with layer isolation. Interfaces everywhere and only ONE specific implementation for each Interface. And worse, most of these classes where only having a 1:1 dependency.
My advice? Use simple classes and be dependency injection aware! Prepare your constructors or accessors so that if you need it, the introduction of DI will be easy. IDE's are there to help you to seamlessly transform your class into an interface and its default implementation. Prepare the work, but don't go too far too early.

It's a contract that we can rely on. 

Short answer: Your contract sit in your tests, not in your implementation.

Well, you're right. It's a contract (at weak one, but still...). But unless you're writing a library (or module) or a public API that other will use, you are probably binding your hands and brain without any need.
The real contract you have to rely on is the one that ensure that you satisfy the customer specifications. How to do that? By relying on tests that verify you're still providing the right behavior that correspond to the requirements.
And customer requirements change, often. Especially if you're working in an agile manner (and if you don't, I can only strongly suggest you to do so). Who cares that your implementation fit to the contract you put in your Interfaces, if at the end it does not meet the customer's needs (or poorly)?!
Most of the times, interfaces will be refined and adapted along your project. A class is a contract too. Start with that. Need a new one? Look what both have in common and if it (or a subset) corresponds to a logical unit. If not, maybe that you already mixed some business concept and that it is time for a little refactoring. If you've written your tests focusing on the behaviors, it'll go smoothly.

---

Now, don't get me wrong. I'm not advocating that Interfaces are evils and we should throw them away in favor of classes! Yes, using Interfaces work, but we should think twice before putting them everywhere. Use them when you need them, not just-in-case, or it will become more a burden than a help.
Oh! And have also a look at the annotations ecosystem. They sometimes replace the use of interface in a really elegant way.