Are You Guilty of Over-Engineering?
It's very easy to use too many complexities to do something simple to use the latest styles, frameworks and libraries. KISS is very tough.
Join the DZone community and get the full member experience.Join For Free
If you listen to other language communities, such as Python or Ruby, it seems Java developers have a strong tendency for over-engineering.
Perhaps they’re just jealous of our superior platform (wink), perhaps there is some very slight reason that they believe so. I believe so. And it’s quite interesting that I realized it by doing code review — while I may be guilty of over-engineering myself when writing code. But I’m working on it.
Of course, you’re a “simple” developer, and only architects have the power to design, so only they can be guilty of over-engineering, can't they? I’m afraid that’s not the case: Developers do that all the time. In this article, I’ll focus on one symptom that I commented a lot in my reviews, but it can be extended to many others.
I’ve was taught early in my career to design my class hierarchy to be as extensible as possible. It translates into a parent interface, implemented by a concrete class. Sometimes, there might even be an abstract class in between. It looks like the following diagram:
It looks great, it makes for an extensible hierarchy, and it realizes the programming to interface paradigm.
For example, the Spring framework uses this design a lot throughout its packages. One great example is the ViewResolver interface, which has a rich children hierarchy and a lot of different implementing classes; e.g. InternalResourceViewResolver and VelocityLayoutViewResolver, among others. Other examples abound (bean registry, context, etc.) in different parts of the framework.
However, please note a few important facts about Spring:
- There are a lot of different children classes organized within a structured hierarchy.
- It’s a framework, and it’s meant to be open for extension and closed to modification by definition.
Back to our current project — let’s say a regular web app. Locate one of the defined interfaces. From this point, it’s quite easy to find its children implementations; in most cases, there’s only one, and it’s prefixed with Default (or alternatively suffixed with Impl). Also, most of the time, the interface resides in the x.y.z package and the implementation in the x.y.z.impl. One might wonder about such utter lack of imagination. Well, coming up with a relevant name for the implementation is hard because there is no semantic difference between it and the interface i.e. the former doesn’t specialize the latter. The only possible conclusion is that the interface is not necessary!
Some architects might argue that even if it's useless now, the abstraction might be useful at a later time. Yet, adding unnecessary code only because it might become useful in the future just decreases the signal to noise ratio of the code base — it hinders readability for no reason. Moreover, this extra code needs to be created, tested, and maintained, hence increased costs and waste. It’s also the opposite of the Agile way of “just enough” to achieve the goal. Finally, if (and only if) the need arises, introducing a parent interface is quite easy by just changing the code of the app. It’s a no-brainer thanks to refactoring features of any IDE worthy of the name.
I would qualify the above approach as over-engineering if one designs it on purpose to prepare for a hypothetical future but as Cargo Cult if one does it just because it has always been done like this.
Useless interfaces are but one simple example of over-engineering. There are many more. I’d happy to read the ones you’ve found (or practiced yourself).
Published at DZone with permission of Nicolas Fränkel, DZone MVB. See the original article here.
Opinions expressed by DZone contributors are their own.