The Persistence Trap

The Persistence Trap

Java was my first serious introduction to Object-oriented Programming (OOP). I was enamored with the simplicity and expressiveness of the Java compared to other languages at that time, but it was OOP that really made it click. Once I made it past the mental barrier of the transition from procedural style to using objects, I never wanted to look back.

To be successful at OOP, you also needed to learn about Object-oriented Analysis and Design (OOA&D). OOP gave us the power of inheritance, polymorphism and encapsulation, but OOA&D gave us the higher level thinking of how we assign responsibility to create highly cohesive, loosely coupled systems.

Unfortunately, a lot of current Java development has lost sight of the principals of OOA&D, resulting in brittle, hard-to-maintain systems becoming the norm. The biggest cause is designing for persistence instead of designing for responsibility.  ORM tools like Hibernate and JPA create a “Persistence Trap”, making it incredibly alluring to design objects based on how easily the tooling supports them rather than taking into account proper abstraction and assignment of responsibility.

There are several common code smells I see frequently which are associated with this dumbing down of design: excessive switch blocks, static methods on concrete classes and the utility package graveyard.

Excessive Switch Blocks

This is the most common of the design flaws I see. Let’s say we have an Employee class with a property called employeeType which holds an integer value. An integer is really easy to persist, but then, all throughout the code, you’ll find switch statements that look at the employeeType and do different things based on the value. I have seen classes where nearly every method has a switch block over a “type” field, checking upwards of four or five values and doing different things for each.

In this case, we’re hiding the fact that employeeType should really have behavior to it, and is not just a simple value. The Strategy pattern is one typical way to represent this sort of behavior. While it complicates persistence, it greatly improves the understandability and testability of the application.

Static Methods on Concrete Classes

This one pops up frequently when a developer doesn’t recognize an inheritance relationship between objects. They implement some fancy logic in a class, then discover they need to reuse the same logic in another class. Rather than analyze the relationship between the objects and look at subclassing or creating an abstract class, they add the magic ‘static’ keyword to the method and directly call it from the other class.

This creates a maintenance nightmare of tightly coupled, but low cohesion, objects. While the intent of reducing copy-paste reuse is noble, the end result is an only slightly less painful problem. This also happens just to avoid dealing with the complexity of persisting an inheritance graph. Rather than come up the proper relationships, the developer ends up doing what will be easiest for the persistence framework to deal with.

Any time a Java developer adds the static keyword to a method, they should really stop and think if they’re putting responsibility in the correct place.

The Utility Package Graveyard

This is the ultimate “give up” design anti-pattern. If you open up a project and find a utility package with numerous low-cohesion classes consisting of only static methods, you’re found the Utility Package Graveyard. This will often show up with the first code smell above. The frequent, long switch blocks will call static methods in the Utility Graveyard.

A Utility Package Graveyard is a cry for help. Teams needs to nip this in the bud quickly, as once the graveyard builds up, it becomes a lot harder to refactor. The solution is, again, proper object design and assignment of responsibility.

Summary

Too often, modern Java developers sacrifice proper object-oriented design to appease the persistence framework gods. While that simplifies the initial stages of a project, it puts significant drag on the long term maintainability of a system. As Java developers, we need to fight that urge and remember our roots in proper OOA&D and design patterns.

Great post and dead-on. I think a big part of the problem is that engineers only learn OOP in college (at best) or pick it up as they move from one job or language to Java. Most people are rarely taught OOA&D in school and they're only lucky if they land a mentor or good architect that explains those concepts. OOA&D is very hard to pick up by simply implementing business functionality in some class or method(s).

"Persistence Trap" is an excellent perspective on the issue. Well written! On that note, I am glad for smart,configurable code analyzers like SonarQube existing! If a project has a decent build pipeline in place, the ridiculously low amount of effort it takes to integrate SonarQube for a disproportionately high ROI in terms of raising the team's overall awareness about code smells and technical debt (and therefore the the code quality over time), always blows my mind.

To view or add a comment, sign in

More articles by Tim Sporcic

  • Extreme Ownership for Agile Engineering Teams

    Something was wrong. It was another long stand-up.

    10 Comments
  • Control Reliability Engineering

    One of the offshoots of the ever-increasing complexity in software engineering has been the emergence of new…

  • Micro Services: No Silver Bullet

    Micro Service architectures, with simple JSON REST services exposed over HTTP, have become all the rage recently…

    2 Comments

Others also viewed

Explore content categories