Recognizing good object-oriented code scales better, the truant officers at Sun Microsystems dictated that nothing in Java should roam the halls or hang out at the mall but should instead stay sealed in a class. Methods replaced functions. Fields replaced global variables. Java elevates object orientation to something of a fetish.
Unfortunately Java’s structure seduces the unwary programmer into excessive abstraction. Between demanding a class for everything and enforcing all types at compile time, it encourages you to lay down layer after layer of formalism in class and interface declarations.
You’re tempted, or perhaps worn down, to imagine the result a tasty baklava of order and adaptability. Every new abstraction layer is an asset, a joint at which you’ll be able to pivot your code in the future. Any one class you turn into two, you imagine vaguely, will pay off someday in better reusability or fewer bugs. Routinely hiding your fields behind getters and setters will give you degrees of freedom to change them out in the future.
However, with those design principles, you’re heading for trouble. If you never meet an abstraction you don’t like, over time your logic will get atomized into a fine mist of tiny classes and methods, none of which individually does anything. You’ll find the code impossible to follow. Debugging it or even reading through it will involve pogo-sticking up and down 15 stack frames, over and over again, trying to understand the logic, until dizziness overcomes you and you forget why you started. Only project veterans will really understand how all the pieces fit together.
Meanwhile the adaptability you envisioned will prove to be a mirage.
The new features people ask for never seem to line up with the layers in your code.
You can’t just swap this implementation for that one, you have to change six different modules at once and patch around some unfortunate external dependencies. And although you’re ready to reimplement employee.getEmployeeID() in an eyeblink, no one ever asks for that.
Your code base will earn a reputation for being difficult to work with and impervious to refactoring, because any given class is pinned down, Gulliver-like, by an army of Lilliputian dependencies. You and your teammates will dread having to touch it and talk dreamily about the Big Rewrite you never seem to have time for.
Are you suffering from Thousand Layer Syndrome today? Or showing early symptoms? Look around your code. Do you see 2- and 3-line methods by the screenful? Classes that hold data but don’t own any decisions, like a C struct? Interfaces with only one implementation? How about bewildering class names? If you can’t figure out what the ParameterInfoBuilderWrapper is supposed to do, or how it differs from the ParameterInfoBuilder, the disease may be advancing on you.
But fear not. Even if you’re suffering an advanced case, your condition is treatable.
First, forget about the Big Rewrite. A rewrite is a risky gambit that can take a lot longer than you think and make the rest of the company feel like your hostages in the meantime. And if your current version accreted too many layers, your next version will likely do the same, unless you and your team first master a better approach to design. So don’t wait for the Big Rewrite, start improving both your code and your team incrementally.
Second, refresh your vision of what good health looks like.
Remember that software is at its core procedural.
Computers do this thing, then they do that thing, then sometimes some other thing. High-level languages and object-oriented design are fanfare and pageantry distracting from our fundamentally humble jobs: pecking out if-then statements.
Procedural logic should therefore take center stage in your code. Good Java should resemble C. A method should have a clear, nontrivial job to do, and it should show its work. You should be able to read a method from top to bottom and understand how it does its job without chasing into implementations of 20 other methods.
Likewise a class should have some meat on its bones and take on some real work. A well-considered class should own an interesting decision and the data needed to make it, not just one or the other. It should be easy to name, ideally with a noun that corresponds to something in the real world with a lifecycle of its own. If you’re struggling to think up a name that distinguishes this class from that, you’re probably rolling too many layers. And it should have a testable surface. Give the class a finite job to do, independent of its peers, and write tests to ensure it does it.
Third, start an exercise routine. Extend your index finger in front of you, then bend it down on your Delete key, then bring it back up. Excellent. Do a single rep on every checkin. Flatten two two-line methods into one. Find a tiny class to delete. Scrap needless getters and setters. You’re not a Victorian lady covering her ankles; join those swinging Pythonistas and let your fields hang out.
Put every interface and protected method to the test: “do we need multiple implementations of this today?” If voices in your head plead “but we might want to…,” remain calm and drive them away with the Extreme Programming chant: “It won’t be needed. It won’t be needed. It won’t be needed.” Future proof your code by making it short, clear, and well-tested, not by gambling on your clairvoyance.
Finally, recruit your colleagues to the cause, because you’re all in this together. Challenge each other in code reviews. Could this method be named more clearly? Is it obvious what this class represents? Does this code serve today’s real use cases or tomorrow’s hypothetical ones? Will next summer’s intern understand this routine right away?
Design is an art. You can read the advice in this sermon, then read a book that will tell you just the opposite. What’s important is to hone your own sense of craftsmanship. Read those books. Experiment. Debate. The investment will more than pay off in productivity gains and incrementally improving code. You’ll have your thousand layers down to a manageable few before you know it, you’ll enjoy your improved velocity, and you’ll be smiling down on a codebase you’re proud of.