Don’t put constants everywhere

Small and obvious post about constants

Michael Spitsin
6 min readNov 23, 2016
Kevin Constant — a Guinean international footballer who is currently free agent

Recently I had a small conversation with my colleague about Constants and he was on a side “Always declare constants when you can”. I tried to explain that they are not necessary everywhere. To be clear I decided to write small post about using constants in code and do we really need to always determinate constant somehow when we see hardcoded string or number like in this snippet: timer.wait(toMs(4)). I want to share some thoughts and call you guys to help me find answer for that tricky (from my point of view) question.

Little digression: there is an G25 (General) item in Chapter 17 of Bob Martin’s book “Clean Code”, which explains that for increase readability and maintainability we need to ”Replace Magic Numbers with Named Constants”. So I think if you aren’t familiar with it, it is better to firstly read that paragraph (placed on page 300, length about an page).

Not necessary to use constants everywhere

Constants are great, constants are beautifull and of course there are situations, where 403 for example means nothing compare to word ERROR_FORBIDDEN, or for the eye of developer is much easier to understand passportDatabase.getBy(PASSPORT_ID) than passportDatabase.getBy(1012437434). But there are cases where they can be in my opinion not much useful and somewhere redundant.

For example, you have next snipped:

timer
.notification(importantNotification)
.after(SECONDS.toMillis(3))
.run();

There is no need to exude SECONDS.toMillis(3) to explicit constant, because this code is self explanatory. Okey, toMillis function make some computationall stuff. But in this case making constant only for precomputations will be a microoptimization and microoptimization is important when numbers say it is. So there is no problems in performance. The only one significant reason for transfering SECONDS.toMillis(3) to constant is to have uniformity. For example, you have 3–4 places in application, where timer is needed to be sent. And 3 seconds delay is common delay time for all timers in application. But even in this case we can think about architecture and providing, for example, some general mechanism for building such delay or even such timer. So delay will be encapsulated in implementation of some builder and will be defined as default behavior, but user will able to change it in some circumstances.

timer
.notification(importantNotification)
.delayed()
.run();

And in docs of delayed() you will tell that this method provides default delay for any timer in your application, but you can use after() as alternative to accommodate appropriate time. Thus constant will be redundant in this case.

Okey, even if we will declare constant, how will we name it? I agree that it is not so simple to come up with a good name, but what names? For example, we can define DEFAULT_TIME, but first it not so meaningfull. What default time? Does it specified somewhere? And second we lost measurement pointer: is it DEFAULT_TIME in millis or in seconds or in hours? So more appropriate name could be DEFAULT_TIME_IN_MILLIS but what it differs from SECONDS.toMillis(3)?

Constants are not enough lazy

All we know, that java uses lazy class loading mechanism. In short, class will be loaded only in first occurence in code (e.g. you call some static method or initialize an object). Consider we have class Locale (same to official java class), that have different predefined locales. These fields are constants since Locale is immutable and fields are instantiated when declared and marked with access modifier and static final keywords. So if you write some simple test that prints in console “hello world” you will not touch Locale class and thus none of objects will be created. But if you for example will try to access to only one locale (let us say Locale.US) through whole application, then every predefined locales will be created.

public class Locale {
public static final Locale RU = new Locale("1");
public static final Locale US = new Locale("2");
public static final Locale UK = new Locale("3");
public static final Locale GE = new Locale("4");
public static final Locale JA = new Locale("5");

private Locale(String t) {
System.out.println("Example Lazy class init with t = " + t);
}
}
...//somewhere in main functionSystem.out.println("START");
Locale.RU.toString();
System.out.println("END");
...//output:START
Example Lazy class init with t = 1
Example Lazy class init with t = 2
Example Lazy class init with t = 3
Example Lazy class init with t = 4
Example Lazy class init with t = 5
END

Yes, we defined constants for better usability and convenience, but now we create all of them when we want to use only one. In this case lazy object initialization is much better, especially in aplications and systems that want to eat less memory. So we can change above example to:

public class Locale {
private static final Map<String, Locale> cache = new HashMap<>();

private Locale(String t) {
System.out.println("Example Lazy class init with t = " + t);
}

public static Locale ru() {
return create("1");
}

public static Locale us() {
return create("2");
}

public static Locale uk() {
return create("3");
}

public static Locale ge() {
return create("4");
}

public static Locale ja() {
return create("5");
}

private static ExampleLazyClass create(String t) {
if (!cache.containsKey(t)) {
cache.put(t, new ExampleLazyClass(t));
}

return cache.get(t);
}
}
...//somewhere in main functionSystem.out.println("START");
Locale.us().toString();
System.out.println("END");
...//output:START
Example Lazy class init with t = 2
END

Okey, we need to adapt it to concurrent systems, but generally we can change list of constants to list of factory methods, that allows us to use additional computations. In our case it is a caching mechanism. So same we have meaningful name and access (Locale.GE vs Locale.ge()), but no constants. And I agree that it can be small and meaningless optimization in small and primitive cases but sometimes it can help to save more memory space.

One usage per type or per instance

There are cases when we need to use classes that contains some static information and their task to give it to us.

public abstract class Child {
public void print() {
System.out.print("Child with type: " + type());
}

public abstract String type();
}

public final class Son extends Child {
@Override
public String type() {return "son";}
}

public final class Daughter extends Child {
@Override
public String type() {return "daughter";}
}

Here we don’t need to declare constants for type, because they a strictly defined by inheritors and hidden there. We can convert this example to:

public abstract class Child {
public void print() {
System.out.print("Child with type: " + type());
}

public abstract String type();
}

public class Children {
private Children() {}

public static Child son() {
return new Child() {
@Override
public String type() {return "son";}
}
}

public static Child daughter() {
return new Child() {
@Override
public String type() {return "daughter";}
}
}
}

Or:

public abstract class Child {
public void print() {
System.out.print("Child with type: " + type());
}

public abstract String type();
}

public final class ConcreteChild extends Child {
private final String type;

public static Child son() {
return new ConcreteChild("son");
}

public static Child daughter() {
return new ConcreteChild("daughter");
}

private ConcreteChild(String type) {
this.type = type;
}

@Override
public String type() {
return null;
}
}

In those cases you also don’t need to declare constants for hardcoded string because they strongly linked to static factory methods.

Of course those examples are primitive and possibly in most cases if you have something like that, you will want to rethink your architecture, but those solutions can be used in number of cases and we dont need to use constants for them.

Tests

Tests are great and often we write them to not only check functionality but cover it for increasing stability of application to futher development. And often for my opinion constants are useless in test cases.

@RunWith(Parameterized.class)
public class SomeTest {
...
@Parameters
public static Collection<Object[]> data() {
return Arrays.asList(
new Object[]{0, 0},
new Object[]{0, 0},
new Object[]{0, 0},
new Object[]{0, 0},
new Object[]{daysAgo(20), 0},
new Object[]{0, monthsAgo(4, 0)},
new Object[]{daysAgo(13), 0}
new Object[]{daysAgo(13), 0},
new Object[]{daysAgo(14), 0},
new Object[]{0, monthsAgo(2, 27)},
new Object[]{0, monthsAgo(2, 27)},
new Object[]{0, monthsAgo(3, 0)},
new Object[]{0, monthsAgo(3, 15)});
}

...
}

For instance, for Parameters it is not necessary to declare constants. Or consider this:

public class DateFormatterTest {
@Test
public void rssDateStrToMsSmockTest() {
Assert.assertEquals(1478149321000L, DateFormatter.toMs("Thu, 3 Nov 2016 06:02:01 +0100"));
}

@Test
public void wrongFormat() {
Assert.assertEquals(-1, DateFormatter.toMs("Thu Nov 3 2016 06:02:01 +0100"));
}
}

Often tests are set of determined steps, so we don’t need constants for them, because we need to straightforward readable tests (of course it is not touched to checking some returned constant values from application code and e.t.c).

Afterwords

Through this small post I just wanted to convey the idea that constants are not always necessarity and, yes, we can use them to increase readability, maintainability and stability. But in some cases they are redundant and sometimes can affect to performance. So always remember that there is no silver bullet for everything in developing. Each solution have its pros and cons. So always think about alternatives.

Now that’s all. Thank you everybody for reading. Best code for everyone.

--

--

Michael Spitsin
Michael Spitsin

Written by Michael Spitsin

Love being creative to solve some problems with an simple and elegant ways

No responses yet