poniedziałek, 19 stycznia 2015

Drawing clear lines in software architecture

While reading the web I've stumbled upon an interesting article "Reusable Software? Just Don't Write Generic Code" that instructed:

"Do not introduce an abstraction layer unless it is clear that you will have multiple implementations (YAGNI principle)."

This comes on strong for one important reason:
  • it explicitly tells you not to do something, which in my opinion needs strong arguments in software architecture.

Evil interfaces!

According to Jos de Jong introducing an interface for every implementation is bad because:
  • it violates YAGNI principle
    it says not to program something, just for the sake of it. Write something only if you are going to need it.
  • it violates RAP principle
    it says that adding an interface for every implementation adds "indirection and code clutter, which just makes the code harder to understand"
  • it breaks encapsulation
    it exposes external classes to internal implementation.

YAGNI is of course a good and sensible principle, but not something to be followed blindly. The RAP principle description gives you a thesis but no proof. It says that adding an interface for one implementation is bad, but doesn't tell you why - other than it might look bad. It breaks encapsulation, I agree but it's not necessarily bad, which I'll explain later.

What about unit test mocks and dependency injection ? Shouldn't we use interfaces to get these working? According to Jos: not necessarily. He says you can still inject concrete classes and test with concrete implementations, instead of mocks.

but: "
When you test that code path with the actual dependency, you are not unit testing; you are integration testing. While that's good and necessary, it isn't unit testing."

That means that if you want to unit test... you have to use mocks (just don't overuse them!)

Well, at least in some languages. In dynamically typed languages like Objective-C you can create stubs and mocks without creating a concrete class (which I strongly advise you to).

Good interfaces!

By injecting concrete classes instead of interfaces you break two of the five very important SOLID principles.

If your project will be maintained for a long time, there's going to be a huge probability that you will need a different implementation for some module you didn't expect. You're going to break the open/close principle and create a nasty code smell by going through every reference to implementation A and changing it by hand to implementation B. 

You also break the dependency inversion principle that says one should "Depend upon Abstractions. Do not depend upon concretions".

Sure, you also kind of break the YAGNI rule, but ask yourself, what the lesser of the evils here ? A question you have to ask yourself very often in programming.

Component-based software engineering

Why do we inject interfaces, instead of concretions, even though it introduces code clutter and breaks encapsulation? I've found a great explanation that's hard to argue with on Stackoverflow.

"...When we use IoC/dependency injection, we're not using OOP concepts. Admittedly we're using an OO language as the 'host', but the ideas behind IoC come from component-oriented software engineering, not OO..."

Great out of the box thinking. Of course, we're breaking OO a little bit here, but we don't care, because we're using another concept that's meta OO to have our software modularised.

Lesser evil

So we have extremely contrary points of view on software architecture. The best way out of this mess would be to find common characteristics between all of our modules and abstract their behavior into shared modules (example). You can't always do that, though.

As I said: programming is very often about choosing the lesser of two evils. It's mostly never a clear line like "this is bad", and "this is good". These are just tools, and we have to use them for the right job. How? Experience, time, and patience.