Help us understand the problem. What is going on with this article?

今日から始められるリファクタリング10選 (2)

More than 3 years have passed since last update.

概要

前回 は、第6章「メソッドの構成」に属する 1〜3 について書きました。

今回は、第9章「条件記述の単純化」に属する 4〜7 について書きます (詳しくは前回の目次を参照)。

主に if/else の書き方に関するリファクタリングですね。

4. 条件記述の分解 (238)

概要

条件記述部とthen部およびelse部から、メソッドを抽出する。

サンプルコード(リファクタリング前)

要件が複雑であるために、色んな値の組み合せによって処理が分岐しているけど、実態は似たような処理のバリエーションでしかない、ということがあります。サンプルコード、ちょっといい具合にアレンジできなかったので分かりにくいとは思いますが。

DataBuilder.php
$data = [];
if ($code1 == 'A' && $code2 == '01' && in_array($code3, ['1', '3', '5'])) {
    // この条件に一致するデータ生成処理
} elseif ($code1 == 'A' && $code2 == '02' && in_array($code3, ['2', '4', '6'])) {
    // この条件に一致するデータ生成処理
// 続く } elseif (...)
}

手順

  1. 条件記述部をメソッド化する
  2. then部とelse部をそれぞれメソッド化する

サンプルコード(リファクタリング後)

データの組み合わせパターンによって処理が異なる箇所が他にもあるんだとしたら、最終的には「Template Methodの形成 (345)」を適用して if-else ブロック自体を別クラスに移動した方がいいかもしれないですね。

DataBuilder.php
// パターンの判定に使う変数はひとまとめにしてもいいかも
$codes = [$code1, $code2, $code3];
if ($this->isPatternA($codes)) {
    $data = $this->buildDataPatternA();
} elseif ($this->isPatternB($codes)) {
    $data = $this->buildDataPatternB();
// 続く } elseif (...)
}

5. 条件記述の結合 (240)

概要

同じ結果を持つ一連の条件判定を1つの条件記述にまとめてから抽出する。

よく見るのはエラー判定する箇所で、ある条件に一致したらreturn / continue する、みたいなところです。
似たような条件分岐が何度も現れる箇所はリファクタリングの機会であることが多いです。

サンプルコード(リファクタリング前)

if ($dataType === self::TYPE1) {
    if ($status === self::STATUS1) {
        return new Error('error');
    } else {
        // 正常系の処理
    }
} elseif ($dataType === self::TYPE2) {
    if ($status === self::STATUS2) {
        return new Error('error');
    } else {
        // 正常系の処理
    }
}
// 長い条件分岐が続く

手順

  1. 同一の処理を行っている箇所の条件記述部を抜き出してひとつにまとめる
  2. 「メソッドの抽出 (110)」を行ってメソッド化する。

サンプルコード(リファクタリング後)

パターンが複雑だったら「条件記述の分解 (238)」を適用してもいいですね。
このくらいの複雑さだったら直接条件判定してしまってもいい気がしたのでそのままにしてあります (&& や || がひとつだけならいいかな、となんとなく)。

    if (!$this->validate($dataType, $status)) {
        return new Error('error');
    }

    if ($dataType === self::TYPE1 && $status !== self::STATUS1) {
        // 正常系の処理
    }
    if ($dataType === self::TYPE2 && $status !== self::STATUS2) {
        // 正常系の処理
    }
    // 長い条件分岐が続く
}

// エラーのパターンだけ抜き出してメソッド化する
private function validate($dataType, $status)
{
    return ($dataType === self::TYPE1 && $status === self::STATUS1)
        ||  ($dataType === self::TYPE2 && $status === self::STATUS2);
}

6. 重複した条件記述の断片の統合 (243)

概要

条件式のすべての分岐に同じコードの断片がある。それを式の外側に移動する。

重複を排除するメリットのひとつは、対象コードを変更する際に、もれなく変更しないといけなくなることです。
サンプルコードでいうと、send() メソッドに引数が必要になったら、二箇所を変更しないといけなくなるわけです。
二箇所ならいいけど、割引率の異なる別のDeal がいくつもあったら大変です。

サンプルコード(リファクタリング前)

いいコード片が見つからなかったので本に載っているコードをそのまま載せます (JavaではなくPHPに変えてあります)。

if ($this->isSpecialDeal()) {
    $total = $price * 0.95;
    $this->send();
} else {
    $total = $price * 0.98;
    $this->send();
}

手順

  1. 共通部分を識別する
  2. 共通部分を条件記述部の外に出す

サンプルコード(リファクタリング後)

簡単ですね。

if ($this->isSpecialDeal()) {
    $total = $price * 0.95;
} else {
    $total = $price * 0.98;
}
$this->send();

さらに進めると、条件によって異なる部分はマジックナンバーの値だけなので、以下のようにも書けます (「シンボリック定数によるマジックナンバーの置き換え (204)」を適用しましょう)。

    const GENERAL_RATE = 0.98;
    const SPECIAL_RATE = 0.95;

    public function deal()
    {
        if ($this->isSpecialDeal()) {
            $rate = self::SPECIAL_RATE;
        } else {
            $rate = self::GENERAL_RATE;
        }
        $total = $price * $rate;
        $this->send();
    }

「メソッドの抽出 (110)」をしてもいいかも。

    const GENERAL_RATE = 0.98;
    const SPECIAL_RATE = 0.95;

    public function deal()
    {
        $total = $price * $this->getRate();
        $this->send();
    }

    protected function getRate()
    {
        if ($this->isSpecialDeal()) {
            return self::SPECIAL_RATE;
        }
        return self::GENERAL_RATE;
    }

7. ガード節による入れ子条件記述の置き換え (250)

概要

特殊ケースすべてに対してガード節を使う。

ガード節というのは、

プログラム内のある分岐で処理を続けるために真 (true)と評価されなければならないブーリアン型の式

ガード (プログラミング) - Wikipedia

処理を続けるためには true になっている必要がある、逆に言うと、false になったら処理がそこで終わるようにする、ということですね。

深いネストを避けるために使われるリファクタリングです。
関数内で return したり、ループ内で continue したり、あるいは例外を投げたりします。

サンプルコード(リファクタリング前)

ネストが4段になっている以下の関数を、関数内で return する、ループ内で continue するガード節を追加して、ネストを極力浅くしてみます。

/**
* $dir 内にある $pattern に一致するファイル名のリストを取得する
*/
public function getFiles($dir, $pattern)
{
    $files = [];
    if (is_dir($dir) && $handle = opendir($dir)) {
        while (($file = readdir($handle)) !== false) {
            if (filetype($dir . DIRECTORY_SEPARATOR . $file) == "file") {
                if (preg_match($pattern, $file)) {
                    $files[] = $file;
                }
            }
        }
    }
    return $files;
}

手順

  1. 条件判定している箇所をガード節で置き換える

サンプルコード(リファクタリング後)

ループ内の処理が短いので元のコードでも何をやっているのか分かりにくいということはないですが、仮に条件文が増えたり、処理が長くなってくると辛くなるので、普段からガード節を意識して使っておくといいんじゃないかと思います。

/**
* $dir 内にある $pattern に一致するファイル名のリストを取得する
*/
public function getFiles($dir, $pattern)
{
    $files = [];
    if (!is_dir($dir) || ($handle = opendir($dir)) === false) {
        // 呼び出し元でエラーと判定したいなら例外を投げる方がいい
        return [];
    }
    while (($file = readdir($handle)) !== false) {
        if (filetype($dir . DIRECTORY_SEPARATOR . $file) !== "file" || !preg_match($pattern, $file)) {
            continue;
        }
        $files[] = $file;
    }
    return $files;
}

GW中になんとか完結できそうです、以下次号。

書きました、今日から始められるリファクタリング10選 (3)

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした