WHERE_TO_PUT.CONSTANTS

When writing software, some conversations happen over and over again. Over the years, I’ve seen different solutions to the same problem that I’d like to share with you.
One of those conversations go something like this, even though the answer might differ depending on the team or component.

Bob: Hey, I have some string literals here and I was told in the code review that I should extract those into constants. But where would I put those?
Alice: Ah, sure. Just put them into our MyModuleConstants class. We put all constants into that class so they are easy to find.

public class MyConstants {

  public static final String V0 = "v0";

  public static final String LABEL_STATUS_OPEN = "Open";

  public static final String UNUSED = "butNobodyWillKnow";
	
  public static final Long MAX_SIZE = 13498368L;

}	

Soooo. Constant classes, eh? Let me share some of the patterns I’ve found (and used myself) over the years and see if we can somehow adapt or improve existing patterns when and how to use constants…and when not to. Take it a bit like “Refactoring away from constants”. First things first, I’m not going to talk about constant interfaces here. Please refer to Item 17: Use interfaces only to define types why that’s a bad idea.

First up, naming constants. Easy, right? Not at all. It’s as hard as naming classes and methods to make the intention really clear. Otherwise, Bad Things™ will happen. Let’s take a look at our first constant.

public static final String V0 = "v0";

It seems to be defining some kind of version number. Let’s assume our project is interacting with a 3rd party API, this seems to be the version number of their REST API. Now, taking a step back, why do we extract constants in the first place? Usually, it’s for two reasons:

  • To make the intention of a value clear (magic numbers as one example)
  • To make the code easier to maintain by factoring out common constants so they can be easily changed in the future

For the above example, the intention is not clear at all. While we can guess it’s a version string, we don’t know how it is supposed to be used. Is it so all API calls use the same version of the REST API? Or is it to determine whether we’re working with the v0 version which requires some special handling? If we now want to upgrade to v1 of the REST API, it’s not as easy as just changing the constant as other code might rely on this exact version. Without proper naming, people tend to reuse constants for different scenarios that actually require two different constructs (which might happen to have the same value for now). Assuming we had the two scenarios above, we slightly better way would be

public static final String REST_API_V0 = "v0";

public static final String REST_API_CURRENT = V0;

Not really great either. We still try to group out constants together using a common prefix (REST_API). Usually when you start using common prefixes, that group usually shares some kind of responsibility (see also this paper and a research prototype for an automated refactoring). Let’s try and extract these constants out into their own entity.

public enum RestApiVersion {

  V0("v0"),

  V1("v1");

  private String apiPath;

  public static RestApiVersion getCurrentApiVersion() {
    return V1;
  }

  private RestApiVersion(String version) {
    this.apiPath = version;
  }

  private String getApiPath() {
    return apiPath;
  }

}

I think the main takeaway from this is that people tend to mix up literals and magic numbers. While everyone is trying to follow the “Extract magic numbers into constants” refactoring, they tend to see every literal as “magic number”. Be aware of the fact that constants usually have no context and thus may form cohesion where none exists. The example doesn’t cover the full story though. One nice side-effect is that we gain compile-safety by using a proper type (be it an enum or a class with the strategy pattern). While enums are usually preferred over constants, take a step back and see if there is even a need for a “list of constant things” or if a higher abstraction actually makes more sense. For example, if there is a need to dynamically influence the current version (e.g. for testing/mocking) or if there is more to handling different versions, I recommend to check out the Strategy design pattern which I’ll leave as an exercise for the reader.


Defining constants by constants is another pattern I’ve seen quite a few times. Take the example above of the RestApiVersion. A slightly different incarnation I’ve seen many times is the following:

public enum RestApiVersion {

  V0(MyConstants.REST_VERSION_V0), // no, no, no

  ....
}

This again goes back to treating every literal as a magic string. There is no additional value gained by defining REST_VERSION_V0 as a separate constant. The opposite is actually true. How do consumers decide when to use the proper facade like RestApiVersion and when to use the “backdoor” by just using the REST_VERSION_V0 constant itself. With the constant exposed as a constant value, there is also no chance for the maintainer of RestApiVersion to dynamically adapt to future needs. With the whole concept modeled with a proper API, we do have a chance to change the implementation without breaking consumers.


Whenever you’re about to create a *Constants class, take a step back, go for a walk, or grab a coffee with a fellow engineer. Try to explain why you want to create such a class and if that is really the right abstraction or if the next engineers on your component might be able to see the patterns easier with another abstraction. There are a lot topics we haven’t touched on yet. When should we actually use constants? What about literals with placeholders (I look at you MessageFormat). And are there any tools to detect unused constants? Let me know in the comments.

Staff Software Engineer @ Tasktop. Friendly troublemaker.

2 thoughts on “WHERE_TO_PUT.CONSTANTS

  1. I’m writing the implementation of a new feature, let’s say MyCoolPayloadParser.
    The purpose of my class is to supplement the features in MyCoolRestSuite. I need to understand information about urls that could be in the payload. But I wonder to myself, “Shouldn’t that have been implemented somewhere else?”
    But MyCoolRestSuite is massive. There’s stuff everywhere. Where do I start searching? And maybe it turns out that MyCoolTransportLayer defines a bunch of constants to do with “http”, “https”, and “ftp”

    I guess the approach will always require case-by-case discussion, when you are trying to convince your friend that you took out for coffee. But I have to now decide how to change MyCoolRestSuite, and if I can’t talk to the developer (he left to start MyCoolProtocolBufferSuite), I have to understand why the constants only live there, resist the temptation to pull the constants up further (and let’s be clear, MyCoolRestSuite will be using urls everywhere obviously), and have lost the ability to easily look for constants in part of the application (or indeed understand why a particular constant which matches the same string pattern as the constant I want to use, but happens to have a different use case (e.g. external name vs internal name) can’t be used).

    I too shudder at constants classes, but it’s rare to have a perfect enum-ready world ;P

  2. Hi Étienne,

    “I guess the approach will always require case-by-case discussion”, totally agree to that. And I don’t believe that turning every constant into an enum actually makes it better. I usually try to keep constants local to their usecase or inline them directly as literals. Enums make sense if you see a grouping like with the prefixes. Other cases certainly require different patterns (e.g. Strategy or Facade). You mentioned constants like “http”, “https”. Even if they live somewhere in a TransportLayer, should your application really depend on those constants e.g. to define a url to a specific server? I think it’s the same problem I mentioned with the V0 that it’s not clear whether it really is a constant for the value HTTP itself or the currently used protocol (what if I want to change my client API to use a different server with HTTPS instead?). And what is the advantage of defining HTTP=”http”? Is it likely to change soon?

Comments are closed.