4

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:

  1. 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.
  2. 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 that exists() 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.
  3. 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?

2

If you have big static initialisers, you're probably not so much defining constants as caching calculated values that will persist over the lifetime of your application.

You may have better luck abstracting on functionality rather than values. See if you can identify patterns in which you use these many constants, and pull these uses out into methods, and put these methods into interfaces. Access your constants through methods as well.

Java example:

interface Platform {
    Image getSpinnerImage();
    // ...
}

// could be a caching proxy
class LocalPlatform implements Platform {
    private Image spinnerImage;
    public Image getSpinnerImage() {
        if ( spinnerImage == null ) {
            spinnerImage = loadSpinnerImage();
        }
        return spinnerImage;
    }
}

This is both testable as injectable, and should take care of large static initialisers.

That's not to say that you should do away with all constants; do keep constants that are just that. Having getXmlFileExtension() is crazy because any other implementation than return "xml" violates the principle of least surprise, but something like getDefaultInstallationPath() is a better candidate.

Given sufficient of these interfaces, you're going to progress to what you've already started to experience: a sort of platform project on which many of your other parts will depend. This is okay; just like Java has java.lang, you're going to have your environment/platform.

I've used this technique to some success in large projects that had similar issues to what you described. It did not drastically reduce the lines of code, but it did encapsulate that code, make it easier to debug and verify, and ended up simplifying much of the rest of the project.

Note: If you feel some parts of your code are problematic, you can always try presenting your actual code to the people on Code Review.

  • I like this: why expose file paths when really all any consumer of that path is going to want is the value at those paths? I'll will try doing what you said. I wonder if I can get away with regexing it... – Groostav May 19 '14 at 17:20

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.