I suspect this is something that many of you have dealt with, and I'm certain there's articles on how to do it, but I can't seem to find them.
My problem is that my various constant classes in Java (read: classes containing lists of static constant values) are too many and too big, and some of them have some annoying complex static initializers.
We've got a file paths class, a file name class, a constants class, a strings class, a colors class (for the swing components) and a bunch of css for the JavaFX components... it goes on and on. The CSS is manageable, the rest of it is quickly becoming not-so.
Having classes like this:
public class Constants {
public static final String something = "Something";
public static final Path someResourcePath = staticMethodCall(RootString, dirTraversal);
public static final Image spinner = iconFindingStaticMethod(dirTraversal)
//and on and on for hundreds (not yet thousands thankfully) of lines
//and then often we have
static{
//code that determines our environment and fudges some paths as appropriate
//sometimes we ask the class loader to find some things for us
//nothing too scary, but still, static initializers are never friendly.
}
}
is good for one very powerful reason: Every java programmer out there knows exactly what this class does and how it works. The ones with static initializers might be a little confusing, but for the most part, the above is PO-Static-JO. I like that.
But it also introduces a number of problems. I'll try to enumerate the ones I know of here:
- the most obvious code smell of all: these classes are multiplying and they're all annoyingly large. We've got one for file extensions thats got a couple dozen entries, including things like
public static final String XMLFileExtension = "xml"
. That might be a bit much, but I'm not really willing to fault the dev who wrote it. It wouldn't be hard to replace references to that with the string value, but I'd like to encourage this kind of code, so I'd feel stupid doing it. But these classes are getting large and annoying. - use of static constants is not testable. This in itself isn't too often a problem since the values in those classes are supposed to be simple, but it does turn into a problem when you do something like
Constants.someFilePath.exists() ? a() : b()
(since there's no way to intercept thatexists()
call, from any environment we're going to ask the actual file system if that path exists, which is a problem for testing.) We're on guice, so I'd really like to inject these values like everything else, so they can be modified by our fixtures with fake/stub/mock values. - It also introduces a dependency problem. Right now we have a couple of IntelliJ & Guice Modules (semi-separate code bases) under the same git repo. They all use our original module's constant files, which means all modules have a dependency with that module, but with static constants whats the solution? Copy-paste?
Some solutions I've thought of:
- we could leverage guice and simply inject every single needed constant with an
@Named
parameter. This addresses problems 2 & 3, but compounds problem 1 since the binding logic would have to either be incredibly long and contain a huge swath of duplicate registration, or employ some pretty complex composition. - we could go the auto-generate-source-java-from-XML-or-JSON route, which I've used with Android and I believe some C# component I worked on briefly. The extra step in the build process adds a level of complexity in an area (that is: devops) that not a lot of developers are familiar with. It was nice to have just a big table in eclipse containing all my values, but the added complexity to the build process is unwanted. Furthermore the static initializer logic couldn't fit into this model.
- stay the course with Java, maybe extract a package near the route called "Environment" or "constants", and then give that package a number of constant classes that can inherit other classes with shared fields. Regarding testability, we run into java inlining final fields. We could replace each field with a simple getter, but that makes an already overly verbose problem more verbose.
I really don't know how I want to tackle this. I've been telling myself to let it become a problem for a few months now, thinking that some kind of solution would emerge. It hasn't, and know we have a kind've gnarly dependency graph, hacks around testing code with file paths, and large constant classes.
What kind of solutions have you employed for managing these files? Am I being overly concerned and what we've got now is the best solution?