DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Refcards Trend Reports Events Over 2 million developers have joined DZone. Join Today! Thanks for visiting DZone today,
Edit Profile Manage Email Subscriptions Moderation Admin Console How to Post to DZone Article Submission Guidelines
View Profile
Sign Out
Refcards
Trend Reports
Events
Zones
Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Partner Zones AWS Cloud
by AWS Developer Relations
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Partner Zones
AWS Cloud
by AWS Developer Relations
  1. DZone
  2. Coding
  3. Languages
  4. Practical PHP Refactoring: Replace Magic Number with Symbolic Constant

Practical PHP Refactoring: Replace Magic Number with Symbolic Constant

Giorgio Sironi user avatar by
Giorgio Sironi
·
Sep. 07, 11 · Interview
Like (0)
Save
Tweet
Share
9.36K Views

Join the DZone community and get the full member experience.

Join For Free

In the scenario of today, we have a literal number, such as 42, scattered across the code base. Alternatively, this number may be just written in a single place, but buried into many lines of code that make difficult to understand if that's the single place where a change to its value can be made.

The refactoring hides the number behind a constant, for documentation purposes and to eliminate duplication. It also targets all the other literal or calculated values which depend on the original magic number.

Why should I introduce an additional constant?

For starters, when you want to eliminate the duplication of the literal value in different place. If the number is complex enough (3.14, 0xCAFEBABE) you may think that you can grep for it in case it changes; however, the only acceptable magic numbers to use directly in code are 0, 1, and 2 in some cases.

The usage of literal values is problematic especially when there are dependencies between them. An old example is that in a data structure representing a deck of 52 cards, to access the last element you will write:

$deck[51]

which won't be found by grep or any other tool without artificial intelligence. Another issue is the different versions of the number:

3.14
3.1415
3.14159

In short, replacing all the numbers instances and appearances in dependent expressions with a constant will centralize the value and make it far easier to change.

The second motivation for introducing a constant is that it expresses a concept (second rule of simple design, XP) that serves to render the code self-explaining: a constant has a name that describes its usage. In a sense, you focus on the role the constant plays (number of cards) instead of its particular implementation (52).

When we say that we don't want comments, we want however code with symbolic constants instead of numbers pulled out of an hat: otherwise comments are necessary to explain their meaning.

This refactoring usually comes with no cost in performance, so focusing on this issue would be a micro-optimization. The refactoring cites a "constant" instead of a private field or something similar, just because of performance optimizations that can be made on constants in many languages, substituting them in the code at compile time.

Alternatives

Fowler suggests looking for alternatives before creating a constant. An alternative means that we could write code that does not assume a particular value of the magic number, but it's still correct:

$deck[count($deck)-1] // access the last element of an array

Other examples of alternatives include replacing a type code with a class (with a refactoring we will see later in this series) and use already existing constants. The PHP core provides constants for many mathematical quantities:

M_PI
M_LN2
M_PI_2
M_SQRT2

Steps

  1. Declare a constant: its value should be the magic number.
  2. Find all occurrences of the literal number: pay attention also to literals that may be a function of the magic number, like 51 in the above example. Unfortunately, there are no algorithmic ways for finding them (apart from tests that fail when you change the constant.)
  3. Check if the literal is an instance of the magic number or a collision. In our example, 52 may also be the number of allowed vehicle types or the Italian tax rate. Change the matching numbers to a reference to the constant.

After the changes, the test should work flawlessly. This is a small scale refactoring, so no test should change.

You can also try, as said in step 2, to change the value of the constant containing the magic number and verify that the whole system still passes the tests. In that case, it means nor the tests nor unreviewed parts of the application still have the literal (or a function of it) embedded in their code.

Example

In the example, we start from a class with various magic numbers hidden in it: 4 suits, 13 cards for each suit, and 52 as the total number.

<?php
class ReplaceMagicNumberWithSymbolicConstant extends PHPUnit_Framework_TestCase
{
    public function testDeckIsFilledWithCardsInitially()
    {
        $deck = new Deck();
        $this->assertEquals(52, count($deck));
    }
    public function testDeckCanDrawAllItsCards()
    {
        $deck = new Deck();
        for ($i = 0; $i < 52; $i++) {
            $card = $deck->draw();
            $this->assertGreaterThanOrEqual(1, $card);
            $this->assertLessThanOrEqual(13, $card);
        }
        $this->assertEquals(0, count($deck));
    }
}
class Deck implements Countable
{
    private $cards;
    public function __construct()
    {
        $this->cards = array();
        for ($i = 0; $i < 4; $i++) {
            $this->cards = array_merge($this->cards, range(1, 13));
        }
    }
    public function count()
    {
        return count($this->cards);
    }
    public function draw()
    {
        return array_shift($this->cards);
    }
}

We create the constants, and plan to derive 52 from the other values:

class Deck implements Countable
{
    const RANGE = 13;
    const SUITS = 4;
    private $cards;

    public function __construct()
    {
        $this->cards = array();
        for ($i = 0; $i < 4; $i++) {
            $this->cards = array_merge($this->cards, range(1, 13));
        }
    }
    public function count()
    {
        return count($this->cards);
    }

    public function draw()
    {
        return array_shift($this->cards);
    }
}

We replace the occurrences with references to the constant. In the test, we use a quick calculation to find out the total number of cards.

<?php
class ReplaceMagicNumberWithSymbolicConstant extends PHPUnit_Framework_TestCase
{
    private $totalCards;

    public function setUp()
    {
        $this->totalCards = Deck::SUITS * Deck::RANGE;
    }

    public function testDeckIsFilledWithCardsInitially()
    {
        $deck = new Deck();
        $this->assertEquals($this->totalCards, count($deck));
    }

    public function testDeckCanDrawAllItsCards()
    {
        $deck = new Deck();
        for ($i = 0; $i < $this->totalCards; $i++) {
            $card = $deck->draw();
            $this->assertGreaterThanOrEqual(1, $card);
            $this->assertLessThanOrEqual(Deck::RANGE, $card);
        }
        $this->assertEquals(0, count($deck));
    }
}

class Deck implements Countable
{
    const RANGE = 13;
    const SUITS = 4;
    private $cards;

    public function __construct()
    {
        $this->cards = array();
        for ($i = 0; $i < self::SUITS; $i++) {
            $this->cards = array_merge($this->cards, range(1, self::RANGE));
        }
    }
    public function count()
    {
        return count($this->cards);
    }

    public function draw()
    {
        return array_shift($this->cards);
    }
}
PHP Magic number (programming)

Opinions expressed by DZone contributors are their own.

Popular on DZone

  • How To Build a Spring Boot GraalVM Image
  • Comparing Map.of() and New HashMap() in Java
  • 5 Steps for Getting Started in Deep Learning
  • Top 10 Best Practices for Web Application Testing

Comments

Partner Resources

X

ABOUT US

  • About DZone
  • Send feedback
  • Careers
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 600 Park Offices Drive
  • Suite 300
  • Durham, NC 27709
  • support@dzone.com
  • +1 (919) 678-0300

Let's be friends: