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).
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 (the red one):
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:
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:
It is relevant to note that the index used to access a specific position of the IPv4 packet (P) 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:
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:
To avoid the employment of a magic number, we can use a variable or constant defining the desired number of connection attempts. See below:
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.