do-while(false) を見つけたときの雑感

よくある「自分は初めて知った」系です。

```php

do {
    //
    if ($error) {
        break;
    }
    //
} while (0);

```

# <i class="fa fa-arrow-down"></i> 遷移

1. `do while`を使わないコード群中にぽつりと現れる
2. 一瞬意味がわからない
3. `break`出来ることに気づく
4. 感動する[^1]
5. 調べる
6. C方面のテクニックらしい
7. ちょっと賛否あり？
8. クラス-メソッドでちゃんと作ってるといらないっぽい？

# <i class="fa fa-question"></i> どんなときに使うか

`if`とエラーフラグによるメイドインアビス状態のときに

* 関数にしたくない(グローバル汚染したくない[^2])
 * 関数内じゃないので早期リターンできない
* 例外に忌避的
* `goto`論外

な感じのとき、苦肉の策として。
新規に作る場合は関数化するとしても、
現状のレガシーコードに対しては(最終的に関数化するとして)リファクタリングの第一歩として使えそう。
**ファイルもクラスも関数も増えずに**、うんざりするネストや変数チェックが減るのは歓迎。

(あくまでPHPしか書けない人間の感想です)

# <i class="fa fa-file-text-o"></i> 参考記事

[c - do { ... } while (0) — what is it good for? - Stack Overflow],
[c++ - Do you consider this technique "BAD"? - Stack Overflow]
良し悪しについて。
後者のアンサーがいい感じっぽい。

[PHP: do-while - Manual]
PHPマニュアルにもありました。
Cの利用法として解説がありますね。

[c - do...while(false)の利点は何ですか - スタック・オーバーフロー]
gotoは嫌だけど関数に分離できない

[do-while(0) …そういうものもあるのか…でも使えないかなあ - はてダ]
はい

この時は[break 2]とか無理なんでしたっけ。

>でも、ネスト地獄に比べたらだいぶマシなのかなあ。

と思いました。

>それに、これで条件分岐によるネストが浅くなっても、結局 do-while(0) によって実現される早期リターンもどきは、モンスターメソッド状態なわけだし、普通に関数書いてやるべきですわなあ。

はい


[PHP - try～catch使うほどじゃないけどgotoは嫌だってときのための便利な書き方 - Qiita]
Qiitaで「do while false」を調べてもあんまり出ない程度のテクニックなのかな？[^3]
こちらはさらに `do if() while(false)`という構文が紹介されています。
一見、コメントにもあるように`while($_POST){… break;}`でいいじゃんと思いましたが、そこは好み？
でも、[モンスターメソッド]やモンスターファイルには
**`if`から抜けてぇ！**ってことが多々あるので、抜けられる`if`と見るといい感じに思えてしまいます。


# <i class="fa fa-code"></i> コード

無駄にややこしく書いているので読まなくていいです。

* Aを行う
* Aが失敗でなければ、Bを行う
* B中ではB1,B2,B3を行う
* B2はB1が成功の場合行う
* B3はB1が失敗の場合行う
* Cは今までで失敗がなければ行う
* 失敗はどれも同じエラーとして変数`$error`で管理する(B1失敗はエラーではない)

実行される成功パターンとしては

1. Aのみ成功     (B1,B3失敗)
2. A,B1成功      (B2失敗)
3. A,B1,B2成功   (C失敗)
4. A,B3成功      (B1,C失敗)
5. A,B1,B2,C成功
6. A,B3,C成功    (B1失敗)

がある。

## フラグ管理(悪い意味でノーマル)

```php

$error = false;
// do A
if (!$result) {
    // エラー処理
    $error = true;
}

if (!$error) {
    // do B1
    if ($result) {
        // do B2
        if (!$result) {
            // エラー処理
            $error = true;
        }
    } else {
        // do B3
        if (!$result) {
            // エラー処理
            $error = true;
        }
    }
}

if (!$error) {
    // do C
    if (!$result) {
        // エラー処理
        $error = true;
    }
}

if ($error) {
    echo "エラーがありました";
}

```

雑でちょっとたとえが悪いですが、大体こんな感じのコードを数倍にしたものがあります。
いつまでもエラーフラグ`$error`がひっついてきてネストを深くします。
そして正常処理を読むときに「エラーが無いとき」という状態を頭でスタックしつづけないといけません。
もしかして`else`で「エラーがあるとき」の処理があるかも？という可能性も`if(!$error)`の終わりまで頭から離れません。[^4]
そもそも`$error`が`true`になったから後は全部処理しないとは言い切れない。[^5]
エラーチェックだけでこれだけあり、もちろん処理にはループやifでさらにややこしくなります。



## `do-while(false)`


```php

do {
    // do A
    if (!$result) {
        // エラー処理
        $error = true;
        break;
    }
    // 以下ではAの成功は保証されている
    // do B1
    if (!$result) {
        // do B2
        if (!$result) {
            // エラー処理
            $error = true;
            break;
        }
    } else {
        // do B3
        if (!$result) {
            // エラー処理
            $error = true;
            break;
        }
    }
    // do C
    if (!$result) {
        // エラー処理
        $error = true;
        break;
    }

} while (false);
if ($error) {
    echo "エラーがありました";
}

```

これだけだと…あんまりぱっとしませんが、BやCの処理のときに、`$error`のことを考えずによくなりました。
「エラー = 以後の処理スキップ」が明確になります。

## 例外処理

```php

try {
    // do A
    if (!$result) {
        // あれば固有エラー処理
        throw new Exception('Aでエラー');
    }
    // 以下ではAの成功は保証されている
    // do B1
    if (!$result) {
        // do B2
        if (!$result) {
            // あれば固有エラー処理
            throw new Exception('B2でエラー');
        }
    } else {
        // do B3
        if (!$result) {
            // あれば固有エラー処理
            throw new Exception('B3でエラー');
        }
    }
    // do C
    if (!$result) {
        // あれば固有エラー処理
        throw new Exception('Cでエラー');
    }

} catch (Exception $e) {
    // 共通エラー処理
    echo $e->getMessage();
}

```

例外も単純に使うならば、`do-while`と変わりませんが、
パフォーマンス面で例外は推奨しない方も多いようですので、
`break`代わりには良くないかもしれません。[^6]
[例外に依存するロジックは駄目ですよ],
[シンクリッジ - try ～ throw ～ catch の乱用を避けるべし]

個人的には`throw`まで関数の中に入れれば、この中で例外処理が一番いいと思うのですが…


***

## <i class="fa fa-frown-o"></i> 人によってはこう書いてある

あと、これも自分で書くと起こらないのですが、Aの処理チェックの
`if (!$error) {}`内に処理がすべて書かれていることがよくあります。
そっちと比較すると、`break`の有り難みが増すかも

```php

$error = false;
// do A
if (!$result) {
    // エラー処理
    $error = true;
}
if (!$error) {
    // do B1
    if ($result) {
        // do B2
        if (!$result) {
            // エラー処理
            $error = true;
        }
    } else {
        // do B3
        if (!$result) {
            // エラー処理
            $error = true;
        }
    }

    if (!$error) {
        // do C
        if (!$result) {
            // エラー処理
            $error = true;
        }
        if (!$error) {
            // do D
            if (!$result) {
                // エラー処理
                $error = true;
            }
            if (!$error) {
                // do E
            }
        }
    }
}

/* 関数内なら
if (!$error) {
    return $result;
} else {
    return  false;
}
*/


```

`}`4連続なぞ、まだましな方で、今までは関数内なら早期リターンで崩してきていたのですが、
グローバル領域に書かれていては迂闊に`return`や`exit`を使うわけにもいかず、手が出せませんでした。
そんなわけで`return`を使わずともグローバル領域に書かれたネスト地獄を解消できる可能性を持つこの`do-while(false)`にいたく感動したのでした。


[PHP - try～catch使うほどじゃないけどgotoは嫌だってときのための便利な書き方 - Qiita]:http://qiita.com/mpyw/items/1dd65881368632dc0fc0
[モンスターメソッド]:http://d.hatena.ne.jp/asakichy/20130823/1377209322

[do-while(0) …そういうものもあるのか…でも使えないかなあ - はてダ]:http://hateda.hatenadiary.jp/entry/2012/11/05/do_while

[break 2]:http://php.net/manual/ja/control-structures.break.php

[c - do...while(false)の利点は何ですか - スタック・オーバーフロー]:http://ja.stackoverflow.com/questions/1510/do-whilefalse%E3%81%AE%E5%88%A9%E7%82%B9%E3%81%AF%E4%BD%95%E3%81%A7%E3%81%99%E3%81%8B

[PHP: do-while - Manual]:http://php.net/manual/ja/control-structures.do.while.php

[c - do { ... } while (0) — what is it good for? - Stack Overflow]:http://stackoverflow.com/questions/257418/do-while-0-what-is-it-good-for/257421#257421

[c++ - Do you consider this technique "BAD"? - Stack Overflow]:http://stackoverflow.com/questions/243967/do-you-consider-this-technique-bad

[シンクリッジ - try ～ throw ～ catch の乱用を避けるべし]:http://www.thinkridge.com/modules/tinyd1/content/index.php?id=7

[例外に依存するロジックは駄目ですよ]:http://blogs.wankuma.com/ognac/archive/2009/12/06/183569.aspx

[^1]: 他のエラーフラグ系の多重ネストコードもこれに置き換えられる！
[^2]: 名前空間もなく、関数名にファイル名を足すなど一意にする習慣もない環境。ついでにファイル間の依存関係もわからない
[^3]: 知っていて当然レベル？
[^4]: あるいは疑念を晴らすため無いと思いつつも毎回先に`if`の終わりを読みに行く。

[^5]: 処理DやEは`if(!$error)`でくくっていないかもという疑念で総チェック

[^6]: そも`break`が`goto`や例外の代替なのでは…となるとニワトリタマゴでしょうか