Over a million developers have joined DZone.

Practical PHP Refactoring: Inline Temp

· Agile Zone

Reduce testing time & get feedback faster through automation. Read the Benefits of Parallel Testing, brought to you in partnership with Sauce Labs.

Inline Temp is a refactoring whose purpose is to substitute a temporary variable with the expression that was used to calculate it or access its value.

While for methods we started seeing the extraction first, and then the complementary Inline refactoring, for temps we'll follow the opposite order. The complementary design for a Temp variable is a Query, which is instead a method, encapsulating for example whether the value is calculated or stored.

Why?

Inline Temp is commonly used as a starting point for other refactorings. In fact, Inline refactorings have always two possible reasons for happening:

  • undoing a previous extraction, making you free to start extracting again in different places.
  • undoing a previously introduced abstraction, which has become too thin to be useful.

In the case of a Temp, in the latter case often you throw away a Query method which just returns a value which would already be accessible to the client code.

The former case is more interesting: I often inline a Temp calculation because I want to change its behavior in different places. With TDD you may start with a single Temp that is copied in various places to produce a result, but after adding more and more tests substitute it with different derivations. We'll see this case in the example.

Steps

  1. Copy the right-hand side of the assignment operation.
  2. Substitute references to the Temp with this expression.

Remember to run unit tests after each stage. You don't have unit tests? That's a different problem.

This refactoring is really simple, but the devil's in the details; plus I do not want you to slack today, so I have prepared a code sample just as well.

Example

The code sample starts from a simple object that should produce an HTML bit:

<?php
class AccountViewTest extends PHPUnit_Framework_TestCase
{
    /**
     * I know, CSS classes named with colors are a smell. But we're focusing
     * on the PHP side for now.
     * This test checks that AccountView visualizes the amount of money
     * in it as red when appropriate. We'll change this requirement and as
     * a result perform a refactoring.
     */
    public function testIsDisplayedAsRedWhenNegative()
    {
        $account = new AccountView(-20);
        $this->assertEquals('<div class="red">-20</div>', $account->render());
    }
}

class AccountView
{
    private $total;

    public function __construct($total)
    {
        $this->total = $total;
    }

    public function render()
    {
        $negative = $this->total < 0;
        $class =  $negative ? 'red' : 'green';
        return "<div class=\"$class\">$this->total</div>";
    }
}

Then we add a requisite, and since the temporary variables used before are inadequate, the code starts exploding:

<?php
class AccountViewTest extends PHPUnit_Framework_TestCase
{
    /**
     * I know, CSS classes named with colors are a smell. But we're focusing
     * on the PHP side for now.
     * This test checks that AccountView visualizes the amount of money
     * in it as red when appropriate. We'll change this requirement and as
     * a result perform a refactoring.
     */
    public function testIsDisplayedAsRedWhenNegative()
    {
        $account = new AccountView(-20);
        $this->assertEquals('<div class="red">-20</div>', $account->render());
    }
    
    /**
     * We add an allowed overdraft as parameter.
     */
    public function testIsDisplayedAsMixedWhenNotTooMuchNegative()
    {
        $account = new AccountView(-20, 1000);
        $this->assertEquals('<div class="red">-20 (<span class="green">maximum overdraft: 1000</span>)</div>', $account->render());
    }
}

class AccountView
{
    private $total;

    public function __construct($total, $maximumOverdraft = 0)
    {
        $this->total = $total;
        $this->maximumOverdraft = $maximumOverdraft;
    }

    /**
     * This implementation doesn't work: $negative have different meanings in 
     * case of negative total or negative total larger than the maximum overdraft.
     */
    public function render()
    {
        $negative = $this->total < 0;
        $class =  $negative ? 'red' : 'green';
        if ($negative && $this->maximumOverdraft) {
            $overdraftNotice =" (<span class=\"$class\">maximum overdraft: $this->maximumOverdraft</span>)";
        } else {
            $overdraftNotice = '';
        }
        return "<div class=\"$class\">{$this->total}{$overdraftNotice}</div>";
    }
}

Getting rid of the temporary variables makes the logic more clear, and paves the way for the extraction of the conditions in dedicated methods. With the former level of indirection, extracting a method directly would require passing the booleans as parameters, or some cowboy-coding where we cut and paste around lines until the tests pass again.

<?php
class AccountViewTest extends PHPUnit_Framework_TestCase
{
   // ...
}

class AccountView
{
    private $total;

    public function __construct($total, $maximumOverdraft = 0)
    {
        $this->total = $total;
        $this->maximumOverdraft = $maximumOverdraft;
    }

    /**
     * We get rid of $negative and inline. Since the code now sucks, we're ready
     * for other refactorings that weren't possible while referring to
     * $negative: it contained too few information for us to display the right
     * colors.
     */
    public function render()
    {
        $class =  $this->total < 0 ? 'red' : 'green';
        if ($this->maximumOverdraft && $this->total < 0) {
            $overDraftClass = 'green'; // this will become a computed value after we add more tests
            $overdraftNotice =" (<span class=\"green\">maximum overdraft: $this->maximumOverdraft</span>)";
        } else {
            $overdraftNotice = '';
        }
        return "<div class=\"$class\">{$this->total}{$overdraftNotice}</div>";
    }
} 

The Agile Zone is brought to you in partnership with Sauce Labs. Discover how to optimize your DevOps workflows with our cloud-based automated testing infrastructure.

Topics:

Opinions expressed by DZone contributors are their own.

The best of DZone straight to your inbox.

SEE AN EXAMPLE
Please provide a valid email address.

Thanks for subscribing!

Awesome! Check your inbox to verify your email so you can start receiving the latest in tech news and resources.
Subscribe

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}