Magic numbers, locality and readability

coding-styleprogramming practices

Lately I've found myself inserting magic numbers into code to make it more readable. I've done this in situations where the magic number is only used once and its purpose is obvious from the context. An example from a recent project:

/* Extract id from "/toClient/chat/id". */
String channelId = channelIdWithPath.split("/")[3];

Apparently, the "best practice" is to declare the magic number as a constant near the beginning of the class, like this:

private final int NUMBER_OF_DELIMITERS_BEFORE_ID_IN_CHAT_CHANNEL_PATH = 3;

... 100 lines of unrelated code ...

/* Extract id from "/toClient/chat/id". */
String channelId = channelIdWithPath.split("/")[NUMBER_OF_DELIMITERS_BEFORE_ID_IN_CHAT_CHANNEL_PATH];

I can't see any advantage in separating the variable declaration from the location where it's used. We've practically doubled the time it takes to understand this code and anyone reading it has to jump over 100 lines to confirm the variable has the correct value.

We could declare it as a local variable right before it's needed, but that's still a "magic number" at least according to CheckStyle. This is something I do often when I feel like the number needs a description (in this case a description like "number of delimiters…" is harder to understand than just the raw number at use).

Another option is to separate manipulation logic into a function:

String channelId = getIdFrom(channelIdWithPath);

This abstraction hides the details of the String manipulation and removes the need for a comment. Unfortunately we still have to write the function somewhere and the original problems are replicated there (including the need to document an example path with a comment).

If we want to declare the variable as a constant AND keep it close to the function, we need to create some kind of StringManipulator class for it. Now we've added a class to do something that takes 1 line of code. I feel like this type of approach leads to sprawling programs where

  • the individual components of a program become easier to understand
  • the structural complexity and execution flow become harder to understand.

For example, if I wanted to read how channelId is retrieved, I would first have to jump into another method in another class, and then I would have to jump to where the variable is declared. All of this could be on a single line of code.

Edit: this answer on a similar question applies pretty well for my particular question.

Best Answer

In layman's terms:

  • If you will be extracting the channelID in several places, then you should create a function.
  • Such a function, being cohesive, should read from the state.
  • Part of that state would be the constant with a name like DELIMITERS_BEFORE_ID.

I find that this:

String channelId = channelIDdWithPath.split("/")[DELIMS_BEFORE_ID];

...is more readeble that this

String channelId = channelIDWithPath.split("/")[3];

..this is even better:

String channelId = channelIDWithPath.split(PATH_DELIM)[DELIMS_BEFORE_ID];

..but this is best:

String channelId = extractChannelID(channelIDWithPath);
  • You don't have to verify if the value asigned to the constants or class variables are correct everytime you read the code. The idea is that if things pass the test, you can "abstract yourself" from the inner workings of them. Your brain can only handle so much complexity, so you will, in time, have to stop cheking if the value of DELIMS_BEFORE_ID is OK and begin to use the lego pieces without cheking whether or not the lego blocks contain the proper amount of acrylonitrile butadiene styrene.