Object-oriented – SOLID vs. Avoiding Premature Abstraction

designobject-orientedsolid

I understand what SOLID is supposed to accomplish and use it regularly in situations where modularity is important and its goals are clearly useful. However, two things prevent me from applying it consistently across my codebase:

  • I want to avoid premature abstraction. In my experience drawing abstraction lines without concrete use cases (the kind that exist now or in the foreseeable future) leads to them being drawn in the wrong places. When I try to modify such code, the abstraction lines get in the way rather than helping. Therefore, I tend to err on the side of not drawing any abstraction lines until I have a good idea of where they would be useful.

  • I find it hard to justify increasing modularity for its own sake if it makes my code more verbose, harder to understand, etc. and doesn't eliminate any duplication. I find simple, tightly coupled procedural or God object code is sometimes easier to understand than very well-factored ravioli code because the flow is simple and linear. It's also much easier to write.

On the other hand, this mindset often leads to God objects. I generally refactor these conservatively, adding clear abstraction lines only when I see clear patterns emerging. What, if anything, is wrong with God objects and tightly coupled code if you don't clearly need more modularity, don't have significant duplication and the code is readable?

EDIT: As far as individual SOLID principles, I meant to emphasize that Liskov Substitution is IMHO a formalization of common sense and should be applied everywhere, since abstractions make no sense if it isn't. Also, every class should have a single responsibility at some level of abstraction, though it may be a very high level with the implementation details all crammed into one huge 2,000 line class. Basically, your abstractions should make sense where you choose to abstract. The principles I question in cases where modularity isn't clearly useful are open-closed, interface segregation and especially dependency inversion, since these are about modularity, not just having abstractions make sense.

Best Answer

The following are simple principles you can apply to helping you understand how to balance your system design:

  • Single Responsibility Principle: the S in SOLID. You can have a very large object in terms of number of methods or the amount of data and still be upholding this principle. Take for instance the Ruby String object. This object has more methods than you can shake a stick at, but it still only has one responsibility: hold a string of text for the application. As soon as your object starts to take on a new responsibility, start thinking hard about that. The maintenance issue is asking yourself "where would I expect to find this code if I had problems with it later?"
  • Simplicity counts: Albert Einstein said "Make everything as simple as possible, but not simpler." Do you really need that new method? Can you accomplish what you need with the existing functionality? If you really think there needs to be a new method, can you change an existing method to satisfy all of what you need? In essence refactor as you build new stuff.

In essence you are trying to do what you can to not shoot yourself in the foot when it comes time to maintain the software. If your large objects are reasonable abstractions, then there is little reason to split them up just because someone came up with a metric that says a class should be no more than X lines/methods/properties/etc. If anything, those are guidelines and not hard and fast rules.

Related Topic