36
34

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

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

Posted at

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


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

遷移

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

どんなときに使うか

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

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

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

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

参考記事

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と見るといい感じに思えてしまいます。

コード

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

  • 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失敗)

がある。

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


$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
そもそも$errortrueになったから後は全部処理しないとは言い切れない。5
エラーチェックだけでこれだけあり、もちろん処理にはループやifでさらにややこしくなります。

do-while(false)


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のことを考えずによくなりました。
「エラー = 以後の処理スキップ」が明確になります。

例外処理


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まで関数の中に入れれば、この中で例外処理が一番いいと思うのですが…


人によってはこう書いてある

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


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

  1. 他のエラーフラグ系の多重ネストコードもこれに置き換えられる!

  2. 名前空間もなく、関数名にファイル名を足すなど一意にする習慣もない環境。ついでにファイル間の依存関係もわからない

  3. 知っていて当然レベル?

  4. あるいは疑念を晴らすため無いと思いつつも毎回先にifの終わりを読みに行く。

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

  6. そもbreakgotoや例外の代替なのでは…となるとニワトリタマゴでしょうか

36
34
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
36
34

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?