1. Introduction

Design patterns work as a map for software development. These patterns suggest how to tackle recurrent programming challenges efficiently. In this way, a pattern is generic enough to fit in several different programming projects, solving the same problem in heterogeneous contexts.

Antipatterns, however, are frequent practices in software development that are not really efficient in practice. Thus, instead of improving the final software, antipatterns make software development and maintenance harder.

In this tutorial, we’ll study the antipattern called magic numbers. At first, we’ll have a brief review of patterns and antipatterns. So, we’ll explore the magic numbers antipattern. Finally, we’ll see some practical examples of magic numbers and practices to remove them from a code.

2. Design Patterns

Several challenges and problems recurrently occur in different programming projects. So, at some point, software engineers and computing researchers summarized these problems and challenges, proposing design patterns to solve or avoid them.

Design patterns are not the code itself. Actually, these designs indicate ways to organize and develop the code.

The great benefit of adopting design patterns consists of they being already employed and tested by many developers. In this way, the further developers use and approve a design pattern, the better is the guarantees that it works to solve potential problems and challenges of programming projects.

Some common advantages of adopting design patterns in programming projects are:

  • Productivity: as design patterns avoid programming problems, software development will require less debugging and problem-solving stages, thus improving the overall productivity
  • Maintainability: most of the design patterns rely on low-coupled and standardized coding. So, modifying, removing, or adding functionalities to the software is simple
  • Support: there exist several open communities of particular design patterns practitioners. In these communities, there exist several forums and Q&A pages to get information and support

We have multiple design patterns with heterogeneous purposes. However, we can cite as famous design patterns the abstract factory (creational pattern), flyweight (structural pattern), and memento (behavioral pattern).

2.1. Antipatterns

In summary, antipatterns are patterns that do not present benefits and can harm the benefits achieved from other design patterns.

Antipatterns can be understood as bad habits or repetitive behaviors that initially seem inoffensive or beneficial. However, with the programming project progress, antipatterns become a problem for the team.

Furthermore, antipatterns are resolvable. So, there is a standard and reproducible process of refactoring to remove an antipattern from a source code.

Some examples of famous antipatterns are bleeding edge (organizational antipattern), gold plating (software design antipattern), and magic numbers (programming antipattern).

We’ll investigate the magic numbers antipattern in detail in the following sections.

3. Magic Numbers

In general terms, magic numbers are numeric values directly used in the processing routines of a source code. There exist multiple scenarios of magic numbers employments. The most common of these scenarios are:

  • Unnamed constant values with one or more occurrences in the source code
  • Unspecified values that actually are identifiers of protocols or file formats
  • Unique values employed only in a particular source code with no global meaning

Regardless of the scenario of magic numbers, their occurrence can harm the maintainability and simplicity of programming projects.

The first potential problem of magic numbers occurs when a programmer non-familiarized with the source code starts working on it.

Magic numbers don’t have a clear explanation of what they are. Thus, the programmer will need to track the meanings and occurrences of each magic number in the code.

Another problem is changing a magic number with multiple occurrences. In this case, the programmer will execute several changes in different places of the code. This process requires much more effort and time than just changing the value of a variable or constant.

Finally, using magic numbers makes the programming process much more susceptible to typos. It is especially relevant when magic numbers are big numbers, and detecting only one or two wrong digits is harder.

Besides avoiding the previously stated problems, do not use magic numbers brings other benefits, for example:

  • Improves the usage of code completion provided by IDEs and text editors
  • Allows the creation of a block in the source code with all the variables and constants replacing the magic numbers
  • Facilitates the code documentation

We’ll see examples of the most common scenarios where magic numbers are used in the following subsections.

3.1. Unnamed Constant Values

We typically find unnamed constants values defining the stop criteria of loopings in a source code. These constants, in general, will not change for a long time in the code. For example, if we are inspecting a SHA1 hash byte by byte, we will always work with 20 bytes.

Let’s see an example where the SHA1 hash inspection is done with a magic number (20):

algorithm SHA1InspectionMagic(X):
    // INPUT
    //     X = data
    // OUTPUT
    //     Returns hashed data
    for index <- 1 to 20:
        inspect X[index - 1]
    return X

However, if we change the SHA1 hash for SHA2 in the system, we’ll have to track every SHA1 length magic number in the code and change them to the SHA2 length value (32).

To avoid that, we can define a constant of hash length and use it instead of the magic numbers, see the example below:

algorithm SHA1InspectionNoMagic(X):
    // INPUT
    //     X = data
    // OUTPUT
    //     Returns hashed data
    hash_length <- 20
    for index <- 0 to hash_length - 1:
        inspect X[index]
    return X

3.2. Unnamed Identifiers Values

Actually, unnamed identifiers are also constant values. However, in this specific case, they represent a protocol, file format, or any other particular identifier in the code.

An example scenario where these identifiers occur is when we process headers of network traffic. A particular example is the field Protocol of the IPv4 or Next Header of IPv6, which carries the code of the next header layout in a packet.

Let’s consider an algorithm to check if the following header in an IP packet is the expected one. So, if we use a magic number to implement that, we’ll have something like this:

algorithm CheckProtocolMagic(P):
    // INPUT
    //     P = Packet IPv4 IP
    // OUTPUT
    //     Checks if the header is correct
    if P[9] = 1:
        return True
    else:
        return False

It is relevant to note that the index used to access a specific position of the IPv4 packet (P[9]) also works as a magic number. This kind of magic number is discussed in the following subsection.

To avoid magic numbers and enable an easy change of protocol verification when necessary, we can define a constant to keep the protocol identifier value as shown in the following example:

algorithm CheckProtocolNoMagic(P):
    // INPUT
    //     P = Packet IPv4 IP
    // OUTPUT
    //     Checks if the header is correct
    protocol_id <- 1
    protocol_pos <- 9
    if P[protocol_pos] = protocol_id:
        return True
    else:
        return False

3.3. Unique Values With No Global Meaning

Unique values with no global meaning usually occur as specific routines of particular algorithms. We can see as an example the maximum number of attempts that a client algorithm executes to connect with a server.

The algorithm next shows the previously described example:

algorithm ServerConnectionMagic(server):
    // INPUT
    //     server = IP server
    // OUTPUT
    //     Connection with limited attempts
    for attempt <- 0 to 2:
        connection <- connect server
        if connection != null:
            break
    return connection

To avoid the employment of a magic number, we can use a variable or constant defining the desired number of connection attempts. See below:

algorithm ServerConnectionNoMagic(server):
    // INPUT
    //     server = IP server
    // OUTPUT
    //     Connection with limited attempts
    attempts_max <- 3
    for attempt <- 1 to attempts_max:
        connection <- connect server
        if connection != null:
            break
    return connection

4. Conclusion

In this tutorial, we studied the antipattern called magic numbers. At first, we saw a brief review of design patterns, focusing on the concept of antipatterns. Thus, we particularly studied the magic numbers antipattern, showing examples of different scenarios where it typically occurs.

We can conclude that using antipatterns usually presents several future challenges in a programming project. Specifically, magic numbers can cause problems with the source codes’ maintainability and comprehensibility.

In such a way, to avoid these potential problems, we can replace the magic numbers with variables and constants in an organized and intuitive way.