C H A P T E R  11

image

Simplifying Method Calls

The core of object-oriented programming is the way it gets us thinking in terms of behavior and collaboration instead of algorithms and data. Interfaces are the gateway for this mindset. A good interface communicates intentions and becomes a joining point between different parts of our architecture.

Most of the refactoring techniques shown here address the issue of making an interface clearer to understand, by renaming methods, changing method signatures, and moving and encapsulating parameters. We'll also see some ways to better detect and manage exceptional conditions to avoid tainting our interfaces.

Rename Method

Problem: “A method is not named to be self-explanatory.”

Solution: “Rename the method.”

Motivation

Widely-acknowledged good practices and even entire methodologies like “Domain Driven Design” promote the use of clearly-named class methods to improve both the readability and design of code. You shouldn't forget that if we were writing code for CPUs only, we wouldn't even consider using PHP. We are writing mostly human-oriented code. Indeed, the cost for a CPU to compile a longer method name is many times worth the cost of a bad or misunderstood design. While too many developers still rely on comments to make their intentions clear, we should pay attention to method signatures by good naming, good parameter selection, and good parameter ordering: everything adds up to quality.

Mechanics

  • Check to see if the method is shared with a super/subclass. If so, take care of each of those related methods.
  • Create a new method with the new name and copy the body of the old method into the new one.
  • Make the old method use the new method.
  • Run tests.
  • Replace all references to the old method with references to the new method. Run tests after each change.
  • Remove the old method. Since sometimes classes implement an interface to match some architectural constraint, if the old method is part of the interface and cannot be removed, you can just mark it as deprecated to keep your class compliance.
  • Run tests.

Example

We have an Order class featuring a method to retrieve a discounted price. Here is the class along with its unit test:

class Order
{
  public function __construct($price)
  {
    $this->price = $price;
  }

  public function getPriceRedByDis($discount)
  {
    return $this->price * (100 - $discount) / 100;
  }
}

class OrderTest extends PHPUnit_Framework_TestCase
{
  public function setUp()
  {
    $this->order = new Order(1500);
  }

  public function testGetDiscountedPrice()
  {
    $this->assertEquals(1050, $this->order->getPriceRedByDis(30));
  }
}

We don't like the getPriceRedByDis() method's name and we'd like to change it to something clearer: getDiscountedPrice() would fit.

We start by creating the new method and referencing it in the old one:

public function getDiscountedPrice($discount)
{
  return $this->price * (100 - $discount) / 100;
}

public function getPriceRedByDis($discount)
{
  return $this->getDiscountedPrice($discount);
}

When we have to change all the references to the old method, making them refer to the new method, we must also change the test:

public function testGetDiscountedPrice()
{
  $this->assertEquals(1050, $this->order->getDiscountedPrice(30));
}

At last our Order class looks like the following:

class Order
{
  public function __construct($price)
  {
    $this->price = $price;
  }

  public function getDiscountedPrice($discount)
  {
    return $this->price * (100 - $discount) / 100;
  }
}

Add Parameter

Problem: “A method needs more data.”

Solution: “Add a parameter to pass those data on.”

Motivation

This refactoring technique is very easy to understand and very often used. Code will change, and once in a while you will need a method to do something new with data still not available for it. While this refactoring technique is very common, beware of unconsidered addition of parameters since we consider a long parameter list a bad smell. Other refactoring techniques in this chapter show other ways to overcome a growing parameter list.

Mechanics

  • Check to see if the method is shared with a super/subclass. If so, take care of each of those related methods.
  • Create a new method with the new parameter or parameters and copy the body of the old method into the new one.
  • Make the old method use the new method.
  • Run tests.
  • Replace all references to the old method with references to the new method. Run tests after each change.
  • Remove the old method. If it's part of the interface just mark it as deprecated.
  • Run tests.

Example

Despite its deep effects, this is a very simple refactoring. Briefly, let's look at the Order class and its unit test shown just before and just after the refactoring.

class Order
{
  private $discount = 30;

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

  public function getDiscountedPrice()
  {
    return $this->price * (100 - $this->discount) / 100;
  }
}
class OrderTest extends PHPUnit_Framework_TestCase
{
  public function setUp()
  {
    $this->order = new Order(1500);
  }

  public function testGetDefaultDiscountedPrice()
  {
    $this->assertEquals(1050, $this->order->getDiscountedPrice());
  }
}

We want to refactor this class to manage a new $discount parameter. Following the refactoring steps we finally get the test as follows:

class OrderTest extends PHPUnit_Framework_TestCase
{
  public function setUp()
  {
    $this->order = new Order(1500);
  }

  public function testGetDefaultDiscountedPrice()
  {
    $this->assertEquals(1050, $this->order->getDiscountedPrice());
    // after refactoring
    $this->assertEquals(600, $this->order->getDiscountedPrice(60));
  }
}

Test is satisfied by the Order class modified in the following manner.

class Order
{
  private $discount = 30;

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

  public function getDiscountedPrice($discount = null)
  {
    $actual_discount = is_null($discount) ? $this->discount : (int)$discount;

    return $this->price * (100 - $actual_discount) / 100;
  }
}

Remove Parameter

Problem: “A parameter is no longer used within a method's body.”

Solution: “Remove it.”

Motivation

While an extra, unused parameter seems harmless for our software, we have more than one reason to get rid of it. First, it's just another way for our eye to get distracted and disturbed. Code readability also comes through brevity. Remember that the less code we write, the clearer it will be. Second, for anyone using your class, one more parameter means one more thing to care about: “What's the right value to pass as that parameter?”, “When should I pass that parameter on?”, or “Wait... why should I pass that parameter on?” Third, fewer parameters make any further refactoring often easier and always quicker. Fewer things must be moved back and forth and around. Last, but absolutely not least, by reducing the parameter count you are simplifying the method interface, eventually removing unneeded dependencies.

The only case you should pay more attention to is when coping with polymorphic methods. These interfaces are shared across a hierarchy and any change to the methods signature must be considered with respect to its side effects. Though this is something to be taken into consideration, “Remove Parameter” is such a cheap refactoring technique that we should never miss the chance to perform it.

Mechanics

  • Check to see if the method is shared with a super/subclass. If so, take care of each of those related methods. If one or more of the related methods use the parameter, just stop.
  • Create a new method with the new parameter or parameters and copy the body of the old method into the new one.
  • Make the old method use the new method.
  • Run tests.
  • Replace all references to the old method with references to the new method. Run tests after each change.
  • Remove the old method. If it's part of the interface, just mark it as deprecated.
  • Run tests.

Separate Query from Modifier

Problem: “A method returns a value and changes the internal state of an object too.”

Solution: “Split the method in two methods, one for the query, the other setting the new state.”

Motivation

A method just returning a value with no effect on the internal state of an object is called a function [EVA] and is very easy to manage. Eric Evans [EVA] says “a function can be called multiple times and return the same value each time. A function can call on other functions without worrying about the depth of nesting. Functions are much easier to test than operations that have side effects. For these reasons, functions lower risk.” Therefore we should place as much logic as possible into side effect methods, segregating the unavoidable and even needed side effects into very simple methods not returning data.

While this separation always helps, it shouldn't become your next dogma: if you meet a method that returns a value while also changing the object state, then that's the time for you to try—at least—to separate the query from the modifier.

Mechanics

  • Declare a new query method returning the same value as the original method. Beware of any temp assignment.
  • Make the original method call the new one to return the result.
  • Run tests.
  • Replace all calls of the original method with a call to the query and add a call to the original method at the line before.
  • Run tests.
  • Remove all return expressions from the original method.
  • Run tests.

Example

The following PHPunit test verifies that an instance of the Order class exposes a method called deliver() that sets its state to delivered while returning the expected delivery date.

class OrderTest extends PHPUnit_Framework_TestCase
{
  public function setUp()
  {

$this->order = new Order(strtotime('yesterday'));

  }

  public function testDelivery()
  {
    $this->assertFalse($this->order->isDelivered());
    $this->assertEquals(date('Y-m-d', strtotime('tomorrow')), date('Y-m-d', $this-> image
order->deliver()));
    $this->assertTrue($this->order->isDelivered());
  }
}

The class code to satisfy this test is the following:

class Order
{
  const DELIVERY_TIME = '2 days';

  private $placed_at;
  private $delivered = false;

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

  public function deliver()
  {
    $this->delivered = true;
    return strtotime('+'.self::DELIVERY_TIME, $this->placed_at);
  }

  public function isDelivered()
  {
    return $this->delivered;
  }
}

We perform the refactoring step by step, beginning with the new query method:

public function getDeliveryTime()
{
  return strtotime('+'.self::DELIVERY_TIME, $this->placed_at);
}

We use it in the original method:

public function deliver()
{
  $this->delivered = true;
  return $this->getDeliveryTime();
}

Thereafter we replace each call to the original method with a double call—don't forget the test is a caller too—and remove the return expression:

class OrderTest extends PHPUnit_Framework_TestCase
{
  public function setUp()
  {
    $this->order = new Order(strtotime('yesterday'));
  }

  public function testDelivery()
  {
    $this->assertFalse($this->order->isDelivered());
    $this->order->deliver();
    $this->assertEquals(date('Y-m-d', strtotime('tomorrow')), date('Y-m-d', $this-> image
order->getDeliveryTime()));
    $this->assertTrue($this->order->isDelivered());
  }
}
class Order
{
  const DELIVERY_TIME = '2 days';

  private $placed_at;
  private $delivered = false;

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

  public function deliver()
  {
    $this->delivered = true;
  }

  public function getDeliveryTime()
  {
    return strtotime('+'.self::DELIVERY_TIME, $this->placed_at);
  }

  public function isDelivered()
  {
    return $this->delivered;
  }
}

Parameterize Method

Problem: “More methods implement the same logic with different values.”

Solution: “Declare one method accepting those changing values as parameters.”

Motivation

Code duplication is one of the easiest bad smells to understand and agree upon. Removing duplicated code facilitates the maintenance of existing code while offering more chances to extend the functionalities of our software just by adding parameters. In this refactoring technique, you abstract similar methods into a common one, getting the desired behavior by isolating mutable values into parameters.

Mechanics

  • Declare a parameterized method behaving like the methods we want to remove. If you find you can parameterize just a fragment of a method, use the “Extract Method” technique before the “Parameterize Method” technique.
  • Replace one call to an old method with a call to the new one.
  • Run tests.
  • Repeat, replacing and testing for each remaining old method.

Example

Suppose in our Order class we have a couple of methods to compute two different price discount policies: one applying a 10% discount, the other a 20% discount.

class Order
{
  public function __construct($price)
  {
    $this->price = $price;
  }

  public function getTenPercentDiscountedPrice()
  {
    return $this->price * 0.9;
  }

  public function getTwentyPercentDiscountedPrice()
  {
    return $this->price * 0.8;
  }
}

Class Order is tested by the following unit test.

class OrderTest extends PHPUnit_Framework_TestCase
{
  public function setUp()
  {
    $this->order = new Order(1500);
  }

  public function testGetDiscountedPrice()
  {
    $this->assertEquals(1350, $this->order->getTenPercentDiscountedPrice());
    $this->assertEquals(1200, $this->order->getTwentyPercentDiscountedPrice());
  }
}

After the refactoring mechanics, we create a new method:

public function getDiscountedPrice($discount_factor)
{
  return $this->price * $discount_factor;
}

We replace all calls to our two old methods, updating tests as well:

class OrderTest extends PHPUnit_Framework_TestCase
{
  public function setUp()
  {
    $this->order = new Order(1500);
  }

  public function testGetDiscountedPrice()
  {
    $this->assertEquals(1350, $this->order->getDiscountedPrice(0.9));
    $this->assertEquals(1200, $this->order->getDiscountedPrice(0.8));
  }
}

We change code in the following manner to obtain the final Order class.

class Order
{
  public function __construct($price)
  {
    $this->price = $price;
  }

  public function getDiscountedPrice($discount_factor)
  {
    return $this->price * $discount_factor;
  }
}

Replace Parameter with Explicit Method

Problem: “A method behaves differently depending on an enumerated parameter.”

Solution: “Extract a method for each enumerated value.”

Motivation

Sometimes you find a situation where the caller of the method has to tell the method itself how to behave by setting a parameter, or where the method behaves differently according to the value of the parameter. Both situations smell bad. We don't want a caller to cope with the responsibility of returning the proper result for a given situation, neither do we want a nasty conditional block in our method to manage different cases. Our goal is to provide a self-explaining interface along with a design that is easy to maintain and extend.

Mechanics

  • Declare an explicit method for each enumerated value of the parameter.
  • Make each leg of the conditional call the appropriate method.
  • Run tests after changing each leg.
  • Replace each caller of the original method with a call to the right method.
  • Run tests after changing each caller.
  • When all callers are changed, remove the original method.
  • Run tests.

Example

Our Order class implements two different discounting logics depending on the value passed as the discount. If it's an integer, it lowers the price by that amount; if it's a floating-point number we apply that discount factor.

class Order
{
  public function __construct($price)
  {
    $this->price = $price;
  }

  public function getDiscountedPrice($discount)
  {
    if (is_int($discount))
    {
      return $this->price - $discount;
    }
    elseif (is_float($discount))
    {
      return $this->price * $discount;
    }

    throw new Exception('Invalid discount type'),
  }
}

It works, as stated by the next test:

class OrderTest extends PHPUnit_Framework_TestCase
{
  public function setUp()
  {
    $this->order = new Order(1500);
  }

  public function testGetDiscountedPrice()
  {

$this->assertEquals(1350, $this->order->getDiscountedPrice(0.9));

    $this->assertEquals(1400, $this->order->getDiscountedPrice(100));
  }
}

It works correctly, but we find this logic horrible indeed. Users of this class cannot be confident in passing values to the method since they fear that the behavior will change unexpectedly depending on the value passed on. We want two different methods to manage each of the two discount strategies.

Let's perform the refactoring step by step. First, we add two new methods:

public function getFixedDiscountedPrice($discount)
{
  return $this->price - $discount;
}

public function getPercentDiscountedPrice($discount)
{
  return $this->price * $discount;
}

Then we use those methods in the original one:

public function getDiscountedPrice($discount)
{
  if (is_int($discount))
  {
    return $this->getFixedDiscountedPrice($discount);
  }
  elseif (is_float($discount))
  {
    return $this->getPercentDiscountedPrice($discount);
  }

  throw new Exception('Invalid discount type'),
}

We can run tests at any time, and we still get green tests. Before we can remove the original method, though, we have to change the callers, tests included.

class OrderTest extends PHPUnit_Framework_TestCase
{
  public function setUp()
  {
    $this->order = new Order(1500);
  }

  public function testGetDiscountedPrice()
  {
    $this->assertEquals(1350, $this->order->getPercentDiscountedPrice(0.9));

    $this->assertEquals(1400, $this->order->getFixedDiscountedPrice(100));
  }
}

Now we can remove the method.

class Order
{
  public function __construct($price)
  {
    $this->price = $price;
  }

  public function getFixedDiscountedPrice($discount)
  {
    return $this->price - $discount;
  }

  public function getPercentDiscountedPrice($discount)
  {
    return $this->price * $discount;
  }
}

Preserve Whole Object

Problem: “A method needs many parameters and those values are coming from an object.”

Solution: “Pass the whole object on as a single parameter.”

Motivation

A long parameter list is a bad smell, and having to compute the single values needed by the caller each time it's invoked can be a problem, at least in terms of performance. Instead, passing the whole object containing those values as single parameters simplifies the method's signature and lets it get access to real-time data, by means of the passed object getter and query methods.

On the other hand, passing an object as a parameter means adding a new dependency between the calling class and the called one. Furthermore, a method depending too much on another class's values suggests that method should belong to the called class. The decision depends on the context, and you should always consider “Move Method” before performing this refactoring technique.

Mechanics

  • Create a new parameter to host the new whole object parameter. Assign it a default value if needed to ease the next steps.
  • Run tests.
  • Replace one parameter with a call to the appropriate method on the whole object parameter.
  • Run tests and repeat for each parameter whose value can be got from the whole object.
  • If the calling method features some code to obtain the old parameter values and it doesn't need those values anymore, remove that code.
  • Add whole object parameter type if you want or need a stricter type check.
  • Run tests.

Example

Our Order class features a checkout() method, setting final discounted price and delivery date depending on two parameters, both belonging to the Customer.

class Order
{
  public function __construct($price)
  {
    $this->price = $price;
  }

  public function checkOut($discount, $policy)
  {
    $this->delivery_time = strtotime('+'.$policy);
    $this->final_price = $this->price * $discount;
  }

  public function getFinalPrice()
  {
    return $this->final_price;
  }

  public function getDeliveryTime()
  {
    return $this->delivery_time;
  }
}

In our test we provide a stub class to represent the Customer role:

class MockCustomer implements Customer
{
  private $discount;
  private $delivery_policy;

  public function __construct($discount, $delivery_policy)
  {
    $this->delivery_policy = $delivery_policy;
    $this->discount = $discount;
  }

  public function getDeliveryPolicy()
  {
    return $this->delivery_policy;
  }

  public function getDiscount()
  {
return $this->discount;

  }

}

class OrderTest extends PHPUnit_Framework_TestCase
{
  public function setUp()
  {
    $this->order = new Order(1500);
  }

  public function testDeliver()
  {
    $customer = new MockCustomer(0.8, '2 days'),

    $this->order->checkOut($customer);

    $this->assertEquals(1200, $this->order->getFinalPrice());
    $this->assertEquals(date('Y-m-d', strtotime('+2 days')), date('Y-m-d', $this->image
order->getDeliveryTime()));
  }
}

To satisfy this test we just have to follow the refactoring steps. At first we add the new $customer parameter to checkout().

public function checkOut($discount, $policy, $customer = null)
{
  $this->delivery_time = strtotime('+'.$policy);
  $this->final_price = $this->price * $discount;
}

Note we set $customer to null by default, so that it's easier to manage the transitions towards the final version of the method. After that we make the method use values coming from the whole object:

$this->delivery_time = strtotime('+'.$customer->getDeliveryPolicy());
$this->final_price = $this->price * $customer->getDiscount();

This change of code let us to remove useless parameters and add type hinting.

public function checkOut(Customer $customer)
{
  $this->delivery_time = strtotime('+'.$customer->getDeliveryPolicy());
  $this->final_price = $this->price * $customer->getDiscount();
}

Replace Parameter with Method

Problem: “A receiver method gets a parameter whose value comes from another object the receiver can get access to.”

Solution: “Remove the parameter and make the receiver call the proper method.”

Motivation

If an object is calling a method on itself to get the value to use in another method of its own, or if the object already holds a reference to the object owning the needed value, we should exploit this condition to shorten the parameter list. Any time an object can retrieve a value not using parameters in a method's signature, we should consider the chance to reduce the parameter list, since they are hard to understand.

Mechanics

  • Use the “Extract Method” technique if needed.
  • Replace references to the parameter with references to the method.
  • Run tests after each replacement.
  • Use the “Remove Parameter” technique on the replaced parameter.
  • Run tests.

Example

The next test shows the use of an Order class to get a discounted price, starting from a total price derived from the same object.

class OrderTest extends PHPUnit_Framework_TestCase
{
  public function setUp()
  {
    $this->order = new Order(1500, 2);
  }

  public function testGetDiscountedPrice()
  {
    $this->assertEquals(2400, $this->order->getDiscountedPrice($this->order->image
getOverallPrice(), 0.8));
  }
}

The class making this test green looks like the following:

class Order
{

  public function __construct($base_price, $quantity)
  {
    $this->base_price = $base_price;
    $this->quantity = $quantity;
  }

  public function getOverallPrice()
  {
    return $this->base_price * $this->quantity;
  }
public function getDiscountedPrice($overall_price, $discount)

  {
    return $overall_price * $discount;
  }
}

Executing the few steps described in this refactoring's mechanics, we get the following Order class and unit test.

class Order
{

  public function __construct($base_price, $quantity)
  {
    $this->base_price = $base_price;
    $this->quantity = $quantity;
  }

  public function getOverallPrice()
  {
    return $this->base_price * $this->quantity;
  }

  public function getDiscountedPrice($discount)
  {
    return $this->getOverallPrice() * $discount;
  }
}

class OrderTest extends PHPUnit_Framework_TestCase
{
  public function setUp()
  {
    $this->order = new Order(1500, 2);
  }
  public function testGetDiscountedPrice()
  {
    $this->assertEquals(2400, $this->order->getDiscountedPrice(0.8));
  }
}

Introduce Parameter Object

Problem: “You have a group of parameters that would make more sense collected together.”

Solution: “Collect them in a new object.”

Motivation

This easy refactoring technique would be worth the time spent on it even if it returned only the benefit of reducing the size of the parameter list. We already know how stiff a long parameter list is, and we have tried to shorten it every time we could, especially in this chapter.

By the way, you can get a greater benefit by applying this refactoring technique. Once the parameters have their own shared class, you can start adding new behavior in new methods or moving existing behavior from client classes to this new one, further reducing code duplication.

Mechanics

  • Declare a new class to represent the group of parameters to be replaced.
  • Use the “Preserve Whole Object” technique.
  • Run tests.
  • Inspect your code to detect methods (or fragments) to be moved into the new class with the “Move Method” technique after using the “Extract Method” technique, if needed.

Remove Setting Method

Problem: “An attribute should be set at creation time once for all.”

Solution: “Remove setters for that attribute.”

Motivation

If you don't want a value to change, then don't offer a way to change it. You should remove any unneeded setter, communicating your design and then your intention in a clearer way.

Mechanics

  • Check the setter is called only in the constructor.
  • Make the constructor have access to the attribute.
  • Run tests.
  • Remove the setter.
  • Run tests.

Example

The GoldOrder class still offers a setter for the discount, while the discount factor should be defined only by the Gold policy, as reflected by the DISCOUNT constant.

class GoldOrder
{
  const DISCOUNT = 0.9;

  private $discount;

  public function __construct($price)
  {
$this->price = $price;
    $this->setDiscount(self::DISCOUNT);
  }

  public function setDiscount($discount)
  {
    $this->discount = $discount;
  }

  public function getDiscountedPrice()
  {
    return $this->price * $this->discount;
  }
}

A simple test will provide us with feedback about the correctness of the refactoring.

class GoldOrderTest extends PHPUnit_Framework_TestCase
{
  public function setUp()
  {
    $this->order = new GoldOrder(1500);
  }

  public function testGetDiscountedPrice()
  {
    $this->assertEquals(1350, $this->order->getDiscountedPrice());
  }
}

Executing the refactoring is easy and brings us here:

class GoldOrder
{
  const DISCOUNT = 0.9;

  private $discount;

  public function __construct($price)
  {
    $this->price = $price;
    $this->discount = self::DISCOUNT;
  }

  public function getDiscountedPrice()
  {
    return $this->price * $this->discount;
  }
}

Hide Method

Problem: “No client class uses a method.”

Solution: “Make the method private.”

Motivation

We can hide methods for reasons similar to the ones for removing parameters from a method's signature. All in all, we are just simplifying our interface, making it easier to understand for ourselves and other developers. We should try to spot unused methods as often as we can. A quite common circumstance occurs when adding behavior to a class and making the transition from a simple data holder to a mature class—its setters become useless. However, if a method becomes useless to the owning class as well, we should remove even it.

Mechanics

  • Spot a method not used anymore by other classes.
  • Make the method protected. Make it private if you can.
  • Run tests.

Replace Constructor with Factory Method

Problem: “You want more flexibility in creating instances of a class.”

Solution: “Replace the constructor with a factory method.”

Motivation

The factory method [GOF] is a well-known object-oriented design pattern to implement the concept of factories, according to which we delegate to some object the creation of other objects. In other words, factories are an abstraction of constructors. This pattern then deals with the problem of creating objects without specifying the exact class of object that will be created. Since the constructor is abstracted in a class method, its subclasses can then override it to specify the right class to be created.

Factory methods are commonly used in frameworks where the library code needs to use objects of classes that may be subclassed by applications using the framework. Also, parallel class hierarchies often require objects from one hierarchy to be able to create appropriate objects from the other.

Other benefits in using a factory method lie in having a better interface for the constructor—since constructor methods can be named only __constructor()—and a clearer encapsulation of the construction process, also leading to easier ways to test our classes.

Constructors can return only an instance of the class they belong to. This is too limited when we don't know the exact subclass of a hierarchy that we want to create an instance of. With a factory method we overcome this stiffness and make our code a lot smarter.

Mechanics

  • Create a factory method. Make it call the current constructor.
  • Replace all calls to the constructor with a call to the factory method.
  • Run tests after each replacement.
  • Make the constructor private.
  • Run tests.

Example

A really meaningful example would require a chapter of its own. Refer to Gang of Four Design Pattern [GOF] for a detailed explanation of the pattern called factory method. In our case we will see just a simple example to make the mechanics clear. We have a Person class extended by Male and Female classes.

class Person
{
  public function __construct() {}
}
class Male extends Person {}
class Female extends Person {}

Creation of this class is tested by a very simple test:

class PersonTest extends PHPUnit_Framework_TestCase
{
  public function testInstances()
  {
    $person = new Male();
    $this->assertTrue($person instanceof Male);
    $person = new Female();
    $this->assertTrue($person instanceof Female);
  }
}

According to the first step described in the mechanics we create a new static method in the Person class.

public static function create($subclass)
{
  return new $subclass();
}

Then we finalize the refactoring to obtain PersonTest unit test and the final Person class.

class PersonTest extends PHPUnit_Framework_TestCase
{
  public function testInstances()
  {
    $person = Person::create('Male'),
    $this->assertTrue($person instanceof Male);
    $person = Person::create('Female'),
$this->assertTrue($person instanceof Female);
  }

class Person
{
  public static function create($subclass)
  {
    return new $subclass();
  }

  private function __construct() {}
}

Replace Error Code with Exception

Problem: “A method returns a special code to indicate an error.”

Solution: “Throw an exception instead.”

Motivation

Exceptions are a powerful way to spot and manage errors while not tainting our good design. They let you write how things should go if nothing bad happens, delegating the decision about what to do if something goes wrong to the right agent. More traditional PHP developers rely on error codes returned by methods, standing loyal to a tradition coming from C, Perl, and other good old-fashioned procedural languages. Though valid for decades, we think this approach should now be deprecated in PHP since exceptions allow for a much clearer design.

Mechanics

  • Find all the method callers and make them expect a possible exception by means of a try/catch block. Tests must be updated too.
  • Change the method body to raise an appropriate exception when needed.
  • Run tests.

Example

The BankAccount class lets us safely withdraw any amount of money, returning an error code in case the $amount withdrawn exceeds our availability.

class BankAccount
{
  public $balance = 1500;

  public function safeWithdraw($amount)
  {
    if ($amount > $this->balance)
    {
return −1;
    }
    else
    {
      $this->balance -= $amount;
      return 0;
    }
  }
}

We test this behavior like this:

class BankAccountTest extends PHPUnit_Framework_TestCase
{
  public function setUp()
  {
    $this->account = new BankAccount();
  }

  public function testSafeWithdraw()
  {
    $this->assertEquals(0, $this->account->safeWithdraw(30));
    $this->account->safeWithdraw(30);
    $this->assertEquals(1470, $this->account->balance);

    $this->assertEquals(-1, $this->account->safeWithdraw(1480));
    $this->assertEquals(1470, $this->account->balance);
  }
}

PHPUnit features an elegant way to test for exceptions, so we can first update our test:

class BankAccountTest extends PHPUnit_Framework_TestCase
{
...
  /**
   * @expectedException BalanceException
   */
  public function testSafeWithdraw()
  {
    $this->account->safeWithdraw(30);
    $this->assertEquals(1470, $this->account->balance);

    $this->account->safeWithdraw(1480);
    $this->assertEquals(1470, $this->account->balance);
  }
}

Now, we can perform the refactoring in BankAccount class.

class BankAccount
{
  public $balance = 1500;
public function safeWithdraw($amount)

  {
    if ($amount > $this->balance)
    {
      throw new BalanceException();
    }

    $this->balance -= $amount;
  }
}

At the end we provide our new exception type, class BalanceException extends Exception {}.

Replace Exception with Test

Problem: “An exception is thrown on a condition that could have been checked first.”

Solution: “Change the caller to perform the test first.”

Motivation

Though we think exceptions are a powerful device, they can be overused. Not all bound conditions are to be considered exceptional. For example, an empty record set returned from a query is a peculiar condition, but it should be managed with a simple test, not requiring an exception to be raised.

Mechanics

  • Update tests and copy the code from the catch block into the right conditional fragment.
  • Rethrow any raised exception caught in the catch block to notify you whether the catch block itself is ever executed.
  • Run tests.
  • Repeat and test for any other catch block to be removed.
  • Remove the try block when all catch blocks are gone.
  • Run tests.

Summary

Making method calls easier to understand is a key point in object-oriented programming. By means of a simple “Method Rename” or by adding a new error management device like an exception, we should always put our effort into getting a clear collaboration between objects so that purpose and focus are well exposed. This way any further change in the future becomes clear in its location and its effects easier to predict. Don't ever forget that object-oriented programming is all about interfaces.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.147.58.196