ためすう

メソッドによる引数の置き換え (リファクタリング-p292)

2019-03-07

目的

「リファクタリング」を理解するためにサンプルコードを PHP で書き換えてみました。

今回は「メソッドによる引数の置き換え」について書きます。

「メソッドによる引数の置き換え」 について

あるオブジェクトがメソッドを呼び出し、その戻り値を別のメソッドの引数として渡している。受信側は、そのメソッドを呼び出し可能である。

もしメソッドが、引数で渡される値を別の方法で取得できるならば、そのように修正すべきです。

変更前

<?php

class Sample
{
    private $_quantity;
    private $_itemPrice;

    public function __construct($quantity, $itemPrice)
    {
        $this->_quantity = $quantity;
        $this->_itemPrice = $itemPrice;
    }

    public function getPrice()
    {
        $basePrice = $this->_quantity * $this->_itemPrice;
        $discountLevel = 1;
        if ($this->_quantity > 100) {
            $discountLevel = 2;
        }

        return $this->discountedPrice($basePrice, $discountLevel);
    }

    private function discountedPrice($basePrice, $discountLevel)
    {
        if ($discountLevel === 2) {
            return $basePrice * 0.1;
        }

        return $basePrice * 0.05;
    }
}

$s = new Sample(10, 2000);
echo $s->getPrice() . "\n";

変更後

<?php

class Sample2
{
    private $_quantity;
    private $_itemPrice;

    public function __construct($quantity, $itemPrice)
    {
        $this->_quantity = $quantity;
        $this->_itemPrice = $itemPrice;
    }

    public function getPrice()
    {
        return $this->discountedPrice();
    }

    private function getBasePrice()
    {
        return $this->_quantity * $this->_itemPrice;
    }
    private function getDiscountLevel()
    {
        $discountLevel = 1;
        if ($this->_quantity > 100) {
            $discountLevel = 2;
        }

        return $discountLevel;
    }

    private function discountedPrice()
    {
        $basePrice = $this->getBasePrice();
        if ($this->getDiscountLevel() === 2) {
            return $basePrice * 0.1;
        }

        return $basePrice * 0.05;
    }
}

$s = new Sample2(10, 2000);
echo $s->getPrice() . "\n";

オブジェクトそのものの受け渡し (リファクタリング-p288)

2019-03-05

目的

「リファクタリング」を理解するためにサンプルコードを PHP で書き換えてみました。

今回は「オブジェクトそのものの受け渡し」について書きます。

「オブジェクトそのものの受け渡し 」 について

あるオブジェクトから複数の値を取得し、それらの値をメソッド呼び出しの引数として渡している。

「オブジェクトそのものの受け渡し」は、引数リストを変更に対して頑強にするだけでなく、しばしばコードを読みやすくします。

変更前

<?php

class TempRange
{
    private $_low;
    private $_high;

    public function __construct($low, $high)
    {
        $this->_low = $low;
        $this->_high = $high;
    }

    public function getLow()
    {
        return $this->_low;
    }

    public function getHigh()
    {
        return $this->_high;
    }
}

class Room
{
    public function withinPlan(HeatingPlan $plan)
    {
        $low = $this->daysTempRange()->getLow();
        $high = $this->daysTempRange()->getHigh();

        return $plan->withinRange($low, $high);
    }

    // 現在までの気温範囲
    private function daysTempRange()
    {
        return new TempRange(10, 50);
    }
}

class HeatingPlan
{
    /* @var TempRange */
    private $_range;

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

    public function withinRange($low, $high)
    {
        return (
            $low >= $this->_range->getLow() &&
            $high <= $this->_range->getHigh()
        );
    }
}

// 暖房計画の気温範囲
$temp_range = new TempRange(10, 70);
$hp = new HeatingPlan($temp_range);

$r = new Room();
echo (int)$r->withinPlan($hp) . "\n";

変更後

<?php

class TempRange2
{
    private $_low;
    private $_high;

    public function __construct($low, $high)
    {
        $this->_low = $low;
        $this->_high = $high;
    }

    public function getLow()
    {
        return $this->_low;
    }

    public function getHigh()
    {
        return $this->_high;
    }

    public function includes(TempRange2 $arg)
    {
        return (
            $arg->getLow() >= $this->getLow() &&
            $arg->getHigh() <= $this->getHigh()
        );
    }
}
class Room2
{
    public function withinPlan(HeatingPlan2 $plan)
    {
        return $plan->withinRange($this->daysTempRange());
    }

    // 現在までの気温範囲
    private function daysTempRange()
    {
        return new TempRange2(10, 50);
    }
}

class HeatingPlan2
{
    /* @var TempRange2 */
    private $_range;

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

    public function withinRange(TempRange2 $room_range)
    {
        return $this->_range->includes($room_range);
    }
}

// 暖房計画の気温範囲
$temp_range = new TempRange2(10, 70);
$hp2 = new HeatingPlan2($temp_range);

$r2 = new Room2();
echo (int)$r2->withinPlan($hp2) . "\n";

明示的なメソッド群による引数の置き換え (リファクタリング-p285)

2019-03-04

目的

「リファクタリング」を理解するためにサンプルコードを PHP で書き換えてみました。

今回は「明示的なメソッド群による引数の置き換え」について書きます。

「明示的なメソッド群による引数の置き換え」 について

引数の特定の値によって異なるコードが実行されるメソッドがある。

引数の値に対応する別々のメソッドを作成する

変更前

<?php

class Engineer extends Employee
{
    public function __consturct() {}
}

class Employee
{
    const ENGINEER = 0;
    const SALESMAN = 1;
    const MANAGER = 2;

    public static function create($type)
    {
        switch ($type) {
            case self::ENGINEER:
                return new Engineer();
            case self::SALESMAN:
                return new Salesman();
            case self::MANAGER:
                return new Manager();
        }

        throw new Exception('不正なタイプコード');
    }
}

$e = Employee::create(0);
echo get_class($e) . "\n";

変更後

<?php

class Employee2
{
    const ENGINEER = 0;
    const SALESMAN = 1;
    const MANAGER = 2;

    public static function createEngineer()
    {
        return new Engineer();
    }
}

$e = Employee2::createEngineer();
echo get_class($e) . "\n";

メソッドのパラメータ化 (リファクタリング-p283)

2019-03-03

目的

「リファクタリング」を理解するためにサンプルコードを PHP で書き換えてみました。

今回は「メソッドのパラメータ化」について書きます。

「メソッドのパラメータ化」 について

複数のメソッドが、異なる値に対してよく似た振る舞いをしている。

変更前

<?php

class Employee
{
    private $salary;

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

    public function tenPercentRaise()
    {
        $this->salary *= 1.1;
    }

    public function fivePercentRaise()
    {
        $this->salary *= 1.05;
    }

    public function getSalary()
    {
        return $this->salary;
    }
}

$e1 = new Employee(1000);
$e1->tenPercentRaise();
echo $e1->getSalary() . "\n";

変更後

<?php

class Employee2
{
    private $salary;

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

    public function raise($factor)
    {
        $this->salary *= (1 + $factor);
    }

    public function getSalary()
    {
        return $this->salary;
    }
}

$e2 = new Employee2(1000);
$e2->raise(0.1);
echo $e2->getSalary() . "\n";

問い合わせと更新の分離 (リファクタリング-p279)

2019-03-02

目的

「リファクタリング」を理解するためにサンプルコードを PHP で書き換えてみました。

今回は「問い合わせと更新の分離」について書きます。

「問い合わせと更新の分離」 について

値を返す関数は観測可能な副作用を持っていなければ、非常に価値があります。

ここで従うべき良いルールは、値を返すメソッドはすべて、観測可能な副作用を持たないと決めてしまうことです。

変更前

<?php

class Sample
{
    public function foundMiscreant($people)
    {
        for ($i = 0; $i < count($people); $i++) {
            if ($people[$i] === 'Don') {
                $this->sendAlert();
                return 'Don';
            }
            if ($people[$i] === 'John') {
                $this->sendAlert();
                return 'John';
            }
        }

        return '';
    }

    public function checkSecurity($people)
    {
        $found = $this->foundMiscreant($people);
        $this->someLaterCode($found);
    }

    private function sendAlert()
    {
        echo 'sendAlert' . "\n";
    }

    private function someLaterCode($found)
    {
        echo 'found: ' . $found . "\n";
    }
}
$people = [
    'BBB' , 'John', 'CCC',
];
$s = new Sample();
$s->checkSecurity($people);

$people = [
    'BBB' , 'CCC',
];
$s->checkSecurity($people);

変更後

<?php

class Sample2
{
    public function foundPerson($people)
    {
        for ($i = 0; $i < count($people); $i++) {
            if ($people[$i] === 'Don') {
                return 'Don';
            }
            if ($people[$i] === 'John') {
                return 'John';
            }
        }

        return '';
    }

    public function sendAlertTarget($people)
    {
        if ($this->foundPerson($people) !== '') {
            $this->sendAlert();
        }
    }

    public function checkSecurity($people)
    {
        $this->sendAlertTarget($people);
        $found = $this->foundPerson($people);
        $this->someLaterCode($found);
    }

    private function sendAlert()
    {
        echo 'sendAlert' . "\n";
    }

    private function someLaterCode($found)
    {
        echo 'found: ' . $found . "\n";
    }
}
$people = [
    'BBB' , 'John', 'CCC',
];
$s = new Sample2();
$s->checkSecurity($people);

$people = [
    'BBB' , 'CCC',
];
$s->checkSecurity($people);

表明の導入 (リファクタリング-p267)

2019-03-01

目的

「リファクタリング」を理解するためにサンプルコードを PHP で書き換えてみました。

今回は「表明の導入」について書きます。

「表明の導入」 について

コードのある部分が、そのプログラムの状態について何らかの仮定を持っている。

実際、表明は本番コードにおいては取り除かれるのが普通です。

変更前

<?php

class Project
{
    public function getMemberExpenseLimit()
    {
        return 9999;
    }
}

class Employee
{
    const NULL_EXPENSE = -1.0;

    private $_expenseLimit = self::NULL_EXPENSE;
    private $_primaryProject;

    public function __construct()
    {
        $this->_primaryProject = new Project();
    }

    public function getExpenseLimit()
    {
        return ($this->_expenseLimit !== self::NULL_EXPENSE) ?
            $this->_expenseLimit : $this->_primaryProject->getMemberExpenseLimit();
    }

    public function getWithinLimit($expenseAmount)
    {
        return $expenseAmount <= $this->getExpenseLimit();
    }
}

$e = new Employee();
echo $e->getExpenseLimit() . "\n";

変更後

<?php

assert_options(ASSERT_CALLBACK, 'my_assert');

// 本番コード
assert_options(ASSERT_ACTIVE, false);

// 開発コード
// assert_options(ASSERT_ACTIVE, true);

class Project2
{
    public function getMemberExpenseLimit()
    {
        return 9999;
    }
}

class Employee2
{
    const NULL_EXPENSE = -1.0;

    private $_expenseLimit = self::NULL_EXPENSE;
    private $_primaryProject;

    public function __construct()
    {
        $this->_primaryProject = new Project2();
    }

    public function getExpenseLimit()
    {
        assert($this->_expenseLimit !== self::NULL_EXPENSE ||
            !is_null($this->_primaryProject));

        return ($this->_expenseLimit !== self::NULL_EXPENSE) ?
            $this->_expenseLimit : $this->_primaryProject->getMemberExpenseLimit();
    }

    public function getWithinLimit($expenseAmount)
    {
        return $expenseAmount <= $this->getExpenseLimit();
    }
}

function my_assert($file, $line, $code)
{
    echo 'file: ' . $file . "\n";
    echo 'line: ' . $line . "\n";
    echo 'code: ' . $code . "\n";
    throw new Exception();
};

$e2 = new Employee2();
echo $e2->getExpenseLimit() . "\n";

ヌルオブジェクトの導入 (リファクタリング-p260)

2019-02-28

目的

「リファクタリング」を理解するためにサンプルコードを PHP で書き換えてみました。

今回は「ヌルオブジェクトの導入」について書きます。

「ヌルオブジェクトの導入」 について

null値のチェックが繰り返し現れる。

そのnull値をヌルオブジェクトで置き換える。

ポリモーフィズムの真髄は、オブジェクトのタイプを尋ねてその答えに基づいて振る舞いを呼び出すというものではなく、タイプを尋ねずにその振る舞いをただ呼ぶだけですませようというものです。

こうした振る舞いの移動は、ほとんどのクライアントが同じ応答を要求しているときにのみ意味があるということを忘れないでください。

変更前

<?php

class Site
{
    /* @var Customer */
    private $_customer;

    public function getCustomer()
    {
        return $this->_customer;
    }
}

class Customer
{
    public function getName()
    {
        // 空実装
    }

    public function getPlan()
    {
        // 空実装
    }

    public function getHistory()
    {
        // 空実装
    }
}

class PaymentHistory
{
    public function getWeeksDelingquentInLastYear()
    {
        return 7000;
    }
}

class BillingPlan
{
    private $_amount = 1000;

    private function __construct($amount)
    {
        $this->_amount = $amount;
    }

    public static function basic()
    {
        return new self(1000);
    }
}

$site = new Site();

$customer = $site->getCustomer();
if (is_null($customer)) {
    $plan = BillingPlan::basic();
} else {
    $plan = $customer->getPlan();
}

if (is_null($customer)) {
    $customerName = 'occupant';
} else {
    $customerName = $customer->getName();
}

if (is_null($customer)) {
    $weekDelinquent = 0;
} else {
    $weekDelinquent = $customer->getHistory()->getWeeksDelingquentInLastYear();
}

echo 'customerName: ' . $customerName . "\n";
echo 'weekDelinquent: ' . $weekDelinquent . "\n";

変更後

<?php

class Site2
{
    /* @var Customer */
    private $_customer;

    public function getCustomer()
    {
        return is_null($this->_customer) ?
            Customer2::newNull() :
            $this->_customer;
    }
}

class Customer2
{
    protected function __construct()
    {
    }

    public function getName()
    {
        // 空実装
    }

    public function getPlan()
    {
        // 空実装
    }

    public function getHistory()
    {
        // 空実装
    }

    public function newNull()
    {
        return new NullCustomer2();
    }
}

class NullCustomer2 extends Customer2
{
    public function getName()
    {
        return 'occupant';
    }

    public function getPlan()
    {
        return BillingPlan2::basic();
    }

    public function getHistory()
    {
        return PaymentHistory2::newNull();
    }
}

class PaymentHistory2
{
    public function getWeeksDelingquentInLastYear()
    {
        return 7000;
    }

    public static function newNull()
    {
        return new NullPaymentHistory2();
    }
}

class NullPaymentHistory2 extends PaymentHistory2
{
    public function getWeeksDelingquentInLastYear()
    {
        return 0;
    }
}

class BillingPlan2
{
    private $_amount = 1000;

    private function __construct($amount)
    {
        $this->_amount = $amount;
    }

    public static function basic()
    {
        return new self(1000);
    }
}

$site = new Site2();

$customer = $site->getCustomer();
$plan = $customer->getPlan();

$customerName = $customer->getName();

$weekDelinquent = $customer->getHistory()->getWeeksDelingquentInLastYear();

echo 'customerName: ' . $customerName . "\n";
echo 'weekDelinquent: ' . $weekDelinquent . "\n";

ポリモーフィズムによる条件記述の置き換え (リファクタリング-p255)

2019-02-16

目的

「リファクタリング」を理解するためにサンプルコードを PHP で書き換えてみました。

今回は「ポリモーフィズムによる条件記述の置き換え」について書きます。

「ポリモーフィズムによる条件記述の置き換え」 について

オブジェクトのタイプによって異なる振る舞いを選択する条件記述がある。

条件記述の各アクション部をサブクラスのオーバーライドメソッドに移動する。

元のメソッドはabstractにする

ポリモーフィズムの真髄は、オブジェクトの振る舞いがその型によって変わるとき、 明示的な条件記述を書かなくてもいいようにすることです。

変更前

<?php

abstract class EmployeeType
{
    const ENGINEER = 0;
    const SALESMAN = 1;
    const MANAGER = 2;

    abstract function getTypeCode();

    public static function newType($code)
    {
        switch ($code) {
            case self::ENGINEER:
                return new Engineer();
            case self::SALESMAN:
                return new Salesman();
            case self::MANAGER:
                return new Manager();
            default:
                throw new Exception('不正な従業員コード');
        }
    }
}

class Engineer extends EmployeeType
{
    public function getTypeCode()
    {
        return self::ENGINEER;
    }
}

class Salesman extends EmployeeType
{
    public function getTypeCode()
    {
        return self::SALESMAN;
    }
}

class Manager extends EmployeeType
{
    public function getTypeCode()
    {
        return self::MANAGER;
    }
}

class Employee
{
    private $_type;

    private $_monthlySalary;
    private $_commission;
    private $_bonus;

    public function __construct($type)
    {
        $this->setType($type);
    }

    public function getType()
    {
        return $this->_type->getTypeCode();
    }

    public function setType($type)
    {
        $this->_type = EmployeeType::newType($type);
    }

    public function payAmount()
    {
        $this->_monthlySalary = 2000;
        $this->_commision = 1000;
        $this->_bonus = 3000;

        switch ($this->getType()) {
            case EmployeeType::ENGINEER:
                return $this->_monthlySalary;
            case EmployeeType::SALESMAN:
                return $this->_monthlySalary + $this->_commission;
            case EmployeeType::MANAGER:
                return $this->_monthlySalary + $this->_bonus;
            default:
                throw new Exception('不正な従業員');
        }
    }
}

$e = new Employee(2);
echo $e->payAmount() . "\n";

変更後

<?php

abstract class EmployeeType2
{
    const ENGINEER = 0;
    const SALESMAN = 1;
    const MANAGER = 2;

    abstract function getTypeCode();

    public static function newType($code)
    {
        switch ($code) {
            case self::ENGINEER:
                return new Engineer2();
            case self::SALESMAN:
                return new Salesman2();
            case self::MANAGER:
                return new Manager2();
            default:
                throw new Exception('不正な従業員コード');
        }
    }

    abstract public function payAmount(Employee2 $emp);
}

class Engineer2 extends EmployeeType2
{
    public function getTypeCode()
    {
        return self::ENGINEER;
    }

    public function payAmount(Employee2 $emp)
    {
        return $emp->getMonthlySalary();
    }
}

class Salesman2 extends EmployeeType2
{
    public function getTypeCode()
    {
        return self::SALESMAN;
    }

    public function payAmount(Employee2 $emp)
    {
        return $emp->getMonthlySalary() + $emp->getCommission();
    }
}

class Manager2 extends EmployeeType2
{
    public function getTypeCode()
    {
        return self::MANAGER;
    }

    public function payAmount(Employee2 $emp)
    {
        return $emp->getMonthlySalary() + $emp->getBonus();
    }
}

class Employee2
{
    private $_type;

    private $_monthlySalary;
    private $_commission;
    private $_bonus;

    public function __construct($type)
    {
        $this->setType($type);

        // 動作確認用にセット
        $this->_monthlySalary = 2000;
        $this->_commision = 1000;
        $this->_bonus = 3000;
    }

    public function getType()
    {
        return $this->_type->getTypeCode();
    }

    public function setType($type)
    {
        $this->_type = EmployeeType2::newType($type);
    }

    public function payAmount()
    {
        return $this->_type->payAmount($this);
    }

    public function getMonthlySalary()
    {
        return $this->_monthlySalary;
    }

    public function getCommission()
    {
        return $this->_commision;
    }

    public function getBonus()
    {
        return $this->_bonus;
    }
}

$e2 = new Employee2(2);
echo $e2->payAmount() . "\n";

ガード節による入れ子条件記述の置き換え (リファクタリング-p250)

2019-02-14

目的

「リファクタリング」を理解するためにサンプルコードを PHP で書き換えてみました。

今回は「ガード節による入れ子条件記述の置き換え」について書きます。

「ガード節による入れ子条件記述の置き換え」 について

メソッドに正常ルールが不明確な条件つき振る舞いがある。

逆にガード節は「めったに起きないが、起きたときには、何もしないで出て行く」ことを伝えます。

変更前

<?php

class Sample
{
    private $_isDead;
    private $_isSeparated;
    private $_isRetired;

    public function __construct($isDead, $isSeparated, $isRetired)
    {
        $this->_isDead = $isDead;
        $this->_isSeparated = $isSeparated;
        $this->_isRetired = $isRetired;
    }

    public function getPayAmount()
    {
        $result = 0;

        if ($this->_isDead) {
            $result = $this->deadAmount();
        } else {
            if ($this->_isSeparated) {
                $result = $this->separatedAmount();
            } else {
                if ($this->_isRetired) {
                    $result = $this->retiredAmount();
                } else {
                    $result = $this->normalPayAmount();
                }
            }
        }

        return $result;
    }

    private function deadAmount()
    {
        return 9000;
    }

    private function separatedAmount()
    {
        return 8000;
    }

    private function retiredAmount()
    {
        return 7000;
    }

    private function normalPayAmount()
    {
        return 6000;
    }
}

$s = new Sample(false, false, false);
echo $s->getPayAmount() . "\n";

変更後

<?php

class Sample2
{
    private $_isDead;
    private $_isSeparated;
    private $_isRetired;

    public function __construct($isDead, $isSeparated, $isRetired)
    {
        $this->_isDead = $isDead;
        $this->_isSeparated = $isSeparated;
        $this->_isRetired = $isRetired;
    }

    public function getPayAmount()
    {
        if ($this->_isDead) {
            return $this->deadAmount();
        }

        if ($this->_isSeparated) {
            return $this->separatedAmount();
        }

        if ($this->_isRetired) {
            return $this->retiredAmount();
        }

        return $this->normalPayAmount();
    }

    private function deadAmount()
    {
        return 9000;
    }

    private function separatedAmount()
    {
        return 8000;
    }

    private function retiredAmount()
    {
        return 7000;
    }

    private function normalPayAmount()
    {
        return 6000;
    }
}

$s2 = new Sample2(false, false, false);
echo $s2->getPayAmount() . "\n";

制御フラグの削除 (リファクタリング-p245)

2019-02-13

目的

「リファクタリング」を理解するためにサンプルコードを PHP で書き換えてみました。

今回は「制御フラグの削除」について書きます。

「制御フラグの削除」 について

一連の論理型の式に対して制御フラグとして機能する1つの変数がある。

代わりにbreakかreturnを使う。

変更前

<?php

function sendAlert()
{
    echo 'sendAlert' . "\n";
}

function checkSecurity($people)
{
    $found = false;
    for ($i = 0; $i < count($people); $i++) {
        if (!$found) {
            if ($people[$i] === 'Don') {
                sendAlert();
                $found = true;
            }
            if ($people[$i] === 'John') {
                sendAlert();
                $found = true;
            }
        }
    }
}

$people = [
    'BBB' , 'John', 'CCC', 'Don',
];
checkSecurity($people);

$people = [
    'BBB' , 'CCC',
];
checkSecurity($people);

変更後 (breakによる簡単な制御フラグの置き換え)

<?php

function checkSecurity2($people)
{
    for ($i = 0; $i < count($people); $i++) {
        if ($people[$i] === 'Don') {
            sendAlert();
            break;
        }
        if ($people[$i] === 'John') {
            sendAlert();
            break;
        }
    }
}

$people = [
    'BBB' , 'John', 'CCC', 'Don',
];
checkSecurity2($people);

$people = [
    'BBB' , 'CCC',
];
checkSecurity2($people);

変更後 (制御フラグの結果を伴うreturnの使用)

<?php

function checkSecurity3($people)
{
    $found = foundMiscreant3($people);

    // someLaterCode($found);
}

function foundMiscreant3($people)
{
    for ($i = 0; $i < count($people); $i++) {
        if ($people[$i] === 'Don') {
            sendAlert();
            return 'Don';
        }
        if ($people[$i] === 'John') {
            sendAlert();
            return 'John';
        }
    }

    return '';
}

$people = [
    'BBB' , 'John', 'CCC', 'Don',
];
checkSecurity3($people);

$people = [
    'BBB' , 'CCC',
];
checkSecurity3($people);