3 min read

Simplifying Magic Literals

Literals should be either variables or constants.
Simplifying Magic Literals

What Are Magic Literals?

Magic literals are literal values in code. They are called magic literals because there is no meaning behind them. To simplify magic literals, you want to define them as a variable. This creates a concept of the value in code. Keep it in the scope that they are needed. If it is only needed in a method, declare it in the method. If it is needed in multiple methods, define a constant and declare it at the class level.

Magic Numbers

Magic numbers are number literals. For example:

// Java code
public User convert(final Object[] row) {
    final var result = new User();

    result.setUserId(row[0]);
    result.setFirstName(row[1]);
    result.setLastName(row[2]);

    return result;
}

This example has three magic numbers. What do they mean? Given the context, it is obvious in this case, but in other cases not so much. This could still be simplified with the following:

// Java code
public User convert(final Object[] row) {
    final var userIdIndex = 0;
    final var firstNameIndex = 1;
    final var lastNameIndex = 2;

    final var result = new User();

    result.setUserId(row[userIdIndex]);
    result.setFirstName(row[firstNameIndex]);
    result.setLastName(row[lastNameIndex]);

    return result;
}

The only time a number literal should be used is with -1, 0 or 1. For example, converting something from one to zero based, like you may do in a for loop.

// Java code
for (int i = 0; i != list.size() - 1; i++) {
}

Or getting the first item of a collection.

// Java code
final var firstItem = list.get(0);

These are obvious and don't need to be changed.

Magic Strings

Magic strings are just like magic numbers but are strings instead. In this example, "my-key" is a magic string.

// Java code
map.get("my-key");

Constant Classes

When cleaning up magic literals, generally you will create a constant. Constants could be any scope of accessibility. It may feel like a good idea to create a class and put all of your constants in there.

// Java code
public final class AppConstant {
    public static final String DARK_THEME = "dark";
    public static final String LIGHT_THEME = "light";
    
    // ... other constants
}

Don't do this. Constants are not a concept of your application but a feature of your programming language. Constants should be located where they make the most sense. In this case, a Theme class that has these two constants would make more sense.

// Java code

// Located in AppConstant
load(AppConstant.DARK_THEME);

// Located in Theme class.
load(Theme.DARK);

By having a central location for constants, you will coupling things together that do not belong together and make it harder to put your code into modules.

Code Refactor Example

The following code contains both a magic number and a magic string. This code takes a list and converts it to a comma separated string.

// Java code
final var numbers = List.of(1, 2, 3);
final var result = new StringBuilder();

numbers.forEach(
    number -> result.append(number)
                    // Magic String
                    .append(", ")
);

result.replace(
    // Magic Number
    result.length() - 2, 
    result.length(),
    ""
);

System.out.println(result);

The magic number can be converted to the string literal that is used to separate the list and then use that string literal's length. This gives clarity to the code as to what you are trying to remove.

// Java code
result.replace(
    result.length() - ", ".length(), 
    result.length(), 
    ""
);

Taking just this piece of code, the magic number is removed, and developers can now see the trailing comma and space are what are being removed. The full code though now has a duplicated magic string. To fix this, a variable will be declared for what is used to separate each item in the collection with.

// Java code
final var numbers = List.of(1, 2, 3);
final var result = new StringBuilder();
final var separator = ", ";

numbers.forEach(
    number -> result.append(number)
                    .append(separator)
);

result.replace(
    result.length() - separator.length(), 
    result.length(),
    ""
);

System.out.println(result);

Last, to further simplify this code, this could be added into a method. This code would have to be changed to account for zero length collections, but to keep things simple, we are going to keep the existing code and just move it into a method.

// Java code
public String join(
        final List<Integer> numbers,
        final String separator) {

    final var result = new StringBuilder();

    numbers.forEach(
        number -> result.append(number)
                        .append(separator)
    );

    result.replace(
        result.length() - separator.length(), 
        result.length(), 
        ""
    );

    return result.toString();
}

join(
    List.of(1, 2, 3),
    ", "
);

By putting this in a method, the magic literals have been cleaned up by using a method parameter.

Conclusion

Don't use magic literals with the exception of -1, 0 and 1. Always create a variable or constant depending on the scope and use that instead. This helps document the code and adds clarity.