読み手を意識した複雑なif文の書き方について考えてみる

 

Webでは表示の仕方をページごとに変えたりフォームの入力結果を確認したりと大活躍なif文ですが、コードを見てみるとあれこの分岐の条件は何だろうって状態になることがありますよね。

 

ですので、if文をきれいに書くアイデアをまとめて見ることにしました。状況に合わせて使えるものを検討するのが良いと思います。

 

当記事のサンプルコードはPHPになっております。他言語の場合は必要に応じて読み替えていただければと思います。

 

複雑条件を簡略化できるか考える

 

以下のようなコードを見てみましょう。

if ($a === 1 || !($b === 2 || $c === 3)) {
    //処理
}
//...

 

ぱっと見で$a, $b, $cがどんな条件で分岐しているか分かりづらくなっています。

 

こんな時に使えるのが論理に関する演算です。これらは同値変形で、同じ真偽を返すので、しっかり変形できていればプログラムの実行結果が異なることもありません。

 

①分配則

  • \(P∧(Q∨R) = (P∧Q)∨(P∧R)\)
  • \((P∨Q)∧R = (P∧R)∨(Q∧R)\)
  • \(P∨(Q∧R) = (P∨Q)∧(P∨R)\)
  • \((P∧Q)∨R = (P∨R)∧(Q∨R)\)

 

②ドモルガンの法則

  • \(!(P∧Q) = \;!P\;∨\;!Q\)
  • \(!(P∨Q) = \;!P\;∧\;!Q\)

 

これを使って書き換えてみると、

$a === 1 || !($b === 2 || $c === 3)

⇔(ドモルガン)

$a === 1 || ($b !== 2 && $c !== 3)

⇔(分配)

($a === 1 || $b !== 2) && ($a === 1 || $c !== 3)

 

どこまで変形するか、変形しないかは条件の読みやすさ次第かと思いますので、臨機応変に対応していきましょう。(この例だと分配しないほうがわかりやすそうですね。)

 

複雑条件は変数かメソッドを使う

 

if (複雑な条件) {
    //処理
}
//...

 

のように条件部分に難解な条件が来てしまう場合があります。

このような時は、何の条件を判定しているのかをわかるように

 

  • boolean型の変数に代入する
  • boolean型を返り値に持つメソッドを作成し利用する

 

のどちらかにすることで、読みやすさを向上させることができます。

 

変数を利用する場合

$isAdminUser = // 複雑な条件
if ($isAdminUser) {
    //処理
}
//...

 

メソッドを利用する場合

if ($this->isAdminUser()) {
    //処理
}
//...

これらの命名には「is OO」, 「has OO」, 「can OO」, 「OO exists」当たりを使うことで読み手に真偽判定をしていることが伝わりやすくなります。

 

使い分けの方法は、

 

  • 今回1回きりの条件 ⇒ 変数
  • 同じ条件を何度も使用する条件 ⇒ メソッド

 

みたいな感じでいいかと思いますが、メソッド化しておいたほうが後でまた使いたくなった時に呼び出せるのでいいと思います。

 

ガード節を利用する

 

一般的にif文をバリデーションのために使用する際にはガード節という書き方を採用することを検討します。

 

ガード節とは正常系と異常系に分かれる処理をする際に先に異常系を検出し、正常系の処理ができるパターンを減らして書くやり方になります。

 

エラー処理なんかだと以下のように深くネストされてしまっている場合もあるみたいです。ネストが深くなるとコードを読むのが大変になる傾向があるので、できるだけネストは浅いほうがいいです。

 

if (isset($a)) {
    if (is_int($a)) {
        if ($a === 1) {
            //正常な処理
            return true;
        }
        else {
            //エラー処理:$aが1ではありません。
            return false;
        }
    }
    else {
        //エラー処理:$aが整数型ではありません。
        return false;
    }
}
else {
    //エラー処理:$aが定義されていません。
    return false;
}

 

これを書き換えてみると、

if (!isset($a)) {
    //エラー処理:$aが定義されていません。
    return false;
}
if (!is_int($a)) {
    //エラー処理:$aが整数型ではありません。
    return false;
}
if ($a !== 1) {
    //エラー処理:$aが1ではありません。
    return false;
}

//正常な処理
return true;

 

と非常に理解しやすくなります。なので、正常系と異常系を判定したい際はガード節の導入をおすすめします。

 

参照用連想配列を利用

 

ある規則を表すようなif文には連想配列を使用することが有効です。

if ($categoryId === 1) {
    $categoryName = 'news';
}
else if ($categoryId === 2) {
    $categoryName = 'amine';
}
else if($categoryId === 3) {
    $categoryName = 'variety';
}
//...

 

この例ではカテゴリのIDによって名前をセットする、というように規則をif文で表しています。ですので、連想配列で辞書を作成してあげましょう。

 

$categoryNames = [
    1 => 'news',
    2 => 'anime',
    3 => 'variety'
    //...
];

if (!array_key_exists($categoryId, $categoryNames)) {
    return false;
}

$categoryName = $categoryNames[$categoryId];

 

こんな風にたくさんあるif文を1つにまとめることができます。

 

また、PHPでは可変関数というものがサポートされていますので、メソッドによりフローを制御したい場合にも同じようにできます。(他言語でできるかどうかはわかりません。)

 

$methods = [
    1 => 'getNewsInfo',
    2 => 'getAnimeInfo',
    3 => 'getVarietyInfo'
    //...
];

if(!array_key_exists($categoryId, $methods)){
    return false;
}

$method = $methods[$categoryId]; //変数に移さないとエラーになる
$categoryInfo = $this->$method();

 

変数からのクラスの呼び出しもできるので、クラスの呼び出し分けもできます。(これも他言語でできるかどうかはわかりません。)

 

$classes = [
    1 => NewsClass::class,
    2 => AnimeClass::class,
    3 => VarietyClass::class
    //...
];

if (!array_key_exists($categoryId, $classes)) {
    return false;
}

$class = new $classes[$categoryId]();

 

if文で規則やフローを制御したいときは連想配列を使うことを検討することで見やすくなるかもしれません。

 

最後に

 

いくつかif文を読みやすくする方法を取り上げてみましたが、こういう書き方もあるというのを頭に入れてコードを書くことで、読みやすさも向上するかと思います。(読みやすいコードを書きたいという気持ちも大切ですね。)

また、今回は取り上げていませんが、単純な3項演算子やswitch文で読みやすくなる場合はそちらを検討しましょう。

 

以下の記事を参考にさせていただきましたありがとうございます。

https://qiita.com/Nossa/items/3fb1f1e4c429cacd3365

https://qiita.com/8845musign/items/f233b0c6e1398e350864