よくある「自分は初めて知った」系です。
do {
//
if ($error) {
break;
}
//
} while (0);
遷移
-
do while
を使わないコード群中にぽつりと現れる - 一瞬意味がわからない
-
break
出来ることに気づく - 感動する1
- 調べる
- C方面のテクニックらしい
- ちょっと賛否あり?
- クラス-メソッドでちゃんと作ってるといらないっぽい?
どんなときに使うか
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失敗はエラーではない)
実行される成功パターンとしては
- Aのみ成功 (B1,B3失敗)
- A,B1成功 (B2失敗)
- A,B1,B2成功 (C失敗)
- A,B3成功 (B1,C失敗)
- A,B1,B2,C成功
- 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
そもそも$error
がtrue
になったから後は全部処理しないとは言い切れない。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連続なぞ、まだましな方で、今までは関数内なら早期リターンで崩してきていたのですが、
グローバル領域に書かれていては迂闊にreturn
やexit
を使うわけにもいかず、手が出せませんでした。
そんなわけでreturn
を使わずともグローバル領域に書かれたネスト地獄を解消できる可能性を持つこのdo-while(false)
にいたく感動したのでした。