ためすう

例外によるエラーコードの置き換え (リファクタリング-p310)

2019-03-11

目的

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

今回は「例外によるエラーコードの置き換え」について書きます。

「例外によるエラーコードの置き換え」 について

メソッドがエラーを示す特別なコードをリターンしている。

代わりに例外を発生させる

変更前

<?php

class Account
{
    private $_balance;

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

    public function withdraw($amount)
    {
        if ($amount > $this->_balance) {
            return -1;
        } else {
            $this->_balance -= $amount;
            return 0;
        }
    }
}

$a1 = new Account(1000);
echo $a1->withdraw(500) . "\n";
echo $a1->withdraw(1500) . "\n";

変更後 (チェックされない例外)

<?php

// 呼び出し側でチェック
class Account2
{
    private $_balance;

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

    public function withdraw($amount)
    {
        if ($amount > $this->_balance) {
            throw new Exception('出金額が多すぎる');
        }

        $this->_balance -= $amount;
    }

    public function getBalance()
    {
        return $this->_balance;
    }
}

$a2 = new Account2(1000);
$a2->withdraw(500);
echo $a2->getBalance() . "\n";

// 例外発生
// $a2->withdraw(1500);

変更後 (チェックされる例外)

<?php

class BalanceException extends Exception {}

class Account3
{
    private $_balance;

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

    public function withdraw($amount)
    {
        if ($amount > $this->_balance) {
            throw new BalanceException();
        }

        $this->_balance -= $amount;
    }

    public function getBalance()
    {
        return $this->_balance;
    }
}

$a3 = new Account3(1000);
$a3->withdraw(300);
echo $a3->getBalance() . "\n";

try {
    $a3->withdraw(800);

    // doTheUsualThin();
} catch (BalanceException $ex) {
    echo '例外発生: BalanceException' . "\n";
    // handleOverdrawn();
}

FactoryMethod によるコンストラクタの置き換え (リファクタリング-p304)

2019-03-10

目的

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

今回は「FactoryMethod によるコンストラクタの置き換え」について書きます。

「FactoryMethod によるコンストラクタの置き換え」 について

オブジェクトを生成する際に、単純な生成以上のことをしたい。

変更前

<?php

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

    private $_type;

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

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

$e1 = new Employee(1);
echo $e1->getType() . "\n";

変更後

<?php

class Employee2
{
    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('不正なタイプコード');
    }
}

class Salesman extends Employee2
{
    public function __consturct() {}
}

$e2 = Employee2::create(1);
echo get_class($e2) . "\n";

setメソッドの削除 (リファクタリング-p300)

2019-03-09

目的

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

今回は「setメソッドの削除」について書きます。

「setメソッドの削除」 について

setメソッドを提供するということは、そのフィールドが変更される可能性を示しています。

変更前

<?php

class Account
{
    private $_id;

    public function __construct($id)
    {
        $this->setId($id);
    }

    public function setId($arg)
    {
        $this->_id = 'ZZ' . $arg;
    }

    public function getId()
    {
        return $this->_id;
    }
}
$a = new Account(10);
echo $a->getId() . "\n";

変更後

<?php

class Account2
{
    private $_id;

    public function __construct($id)
    {
        $this->initializeId($id);
    }

    private function initializeId($arg)
    {
        $this->_id = 'ZZ' . $arg;
    }

    public function getId()
    {
        return $this->_id;
    }
}
$a = new Account(10);
echo $a->getId() . "\n";

引数オブジェクトの導入 (リファクタリング-p295)

2019-03-08

目的

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

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

「引数オブジェクトの導入」 について

本来まとめて扱うべき一連の引数がある。

このような引数群をオブジェクトにすることは、データをグループ化してまとめる点で価値があります。またこのリファクタリングは、引数リストを短くできる点でも有益です。

変更前

<?php

date_default_timezone_set('Asia/Tokyo');

class Entry
{
    private $_value;
    private $_chargeDate;

    public function __construct($value, DateTime $chargeDate)
    {
        $this->_value = $value;
        $this->_chargeDate = $chargeDate;
    }

    public function getDate()
    {
        return $this->_chargeDate;
    }

    public function getValue()
    {
        return $this->_value;
    }
}

class Account
{
    private $_entiries = [];

    public function __construct()
    {
        $e1 = new Entry(10, new DateTime('2018/10/14'));
        $e2 = new Entry(20, new DateTime('2018/10/18'));
        $e3 = new Entry(30, new DateTime('2018/10/20'));
        $e4 = new Entry(40, new DateTime('2018/10/23'));
        $e5 = new Entry(50, new DateTime('2018/11/14'));

        $this->_entiries = [
            $e1,
            $e2,
            $e3,
            $e4,
            $e5,
        ];
    }

    public function getFlowBetween(DateTime $start, DateTime $end)
    {
        $result = 0;
        foreach ($this->_entiries as $entry) {
            if ($entry->getDate()->getTimeStamp() >= $start->getTimeStamp() &&
                $entry->getDate()->getTimeStamp() <= $end->getTimeStamp()) {
                $result += $entry->getValue();
            }
        }

        return $result;
    }
}

$anAccount = new Account();
$startDate = new DateTime('2018/10/14');
$endDate = new DateTime('2018/10/20');
echo $anAccount->getFlowBetween($startDate, $endDate) . "\n";

変更後

<?php

date_default_timezone_set('Asia/Tokyo');

class Entry2
{
    private $_value;
    private $_chargeDate;

    public function __construct($value, DateTime $chargeDate)
    {
        $this->_value = $value;
        $this->_chargeDate = $chargeDate;
    }

    public function getDate()
    {
        return $this->_chargeDate;
    }

    public function getValue()
    {
        return $this->_value;
    }
}

class Account2
{
    private $_entiries = [];

    public function __construct()
    {
        $e1 = new Entry2(10, new DateTime('2018/10/14'));
        $e2 = new Entry2(20, new DateTime('2018/10/18'));
        $e3 = new Entry2(30, new DateTime('2018/10/20'));
        $e4 = new Entry2(40, new DateTime('2018/10/23'));
        $e5 = new Entry2(50, new DateTime('2018/11/14'));

        $this->_entiries = [
            $e1,
            $e2,
            $e3,
            $e4,
            $e5,
        ];
    }

    public function getFlowBetween(DateRange $range)
    {
        $result = 0;
        foreach ($this->_entiries as $entry) {
            if ($range->includes($entry->getDate())) {
                $result += $entry->getValue();
            }
        }

        return $result;
    }
}

class DateRange
{
    private $_start;
    private $_end;

    public function __construct(DateTime $start, DateTime $end)
    {
        $this->_start = $start;
        $this->_end = $end;
    }

    public function getStart()
    {
        return $this->_start;
    }

    public function getEnd()
    {
        return $this->_end;
    }

    public function includes(DateTime $arg)
    {
        return ($arg->getTimeStamp() >= $this->_start->getTimeStamp()) &&
            ($arg->getTimeStamp() <= $this->_end->getTimeStamp())
        ;
    }
}

$anAccount = new Account2();
$startDate = new DateTime('2018/10/14');
$endDate = new DateTime('2018/10/20');

$range = new DateRange($startDate, $endDate);
echo $anAccount->getFlowBetween($range) . "\n";

メソッドによる引数の置き換え (リファクタリング-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";