for文からのreturnとbreak


「for文の中からbreakで抜けるのは良いがreturnで抜けるのはよくない」みたいな中途半端な話を見かけたので、元記事2009年ですが、2013年にも言及されていたりして突っ込む価値があるのかな、と思って書きます。

とりあえず、規約で決めとこうぜ、という話で終わりなのですが、それじゃ元記事よりひどいので、なぜ中途半端なのか、なぜ使うのかを書きます。

えと、breakとか途中returnとかって大変悩ましいのですよ。関数の機能の外出しが自由にできなくなる。どういうことかというとこういうことだ。

int func(void)
{
    for(int i; i<5; ++i)
    {
        //色々
        //関連の深い処理
        //関連の超深い処理
        if(mazuikoto){break;}
        //関連の超深い処理
        //関連の深い処理
        //色々
    }
}

で、関連の深い処理がちょっと肥大化したとするじゃん?

int chokanren(void)
{
    //関連の超深い処理
    if(mazuikoto)
    {
        break; //<- あうち!
    }
    //関連の超深い処理
}

int kannrenn(void)
{
    //関連の深い処理
    chokanren();
    //関連の深い処理
}

int func(void)
{
    for(int i; i<5; ++i)
    {
        //色々
        kannrenn();
        //色々
    }
}

あうち。2階層とか機能を切り分けられたら、もう例外機構のお世話にならないと同じ処理ってできないよね? 返値でって言ったって、この様子を見ると既に使ってそうだよね。これがbreakとか、ループ中returnとかを禁じる本質的な意味だと思うんだよな。だから、returnが駄目でbreakならいいというのは謎だ(一応、breakならコンパイラエラーで気付くけど、returnだと下手すると伏在するバグになりかねない、という違いは認めよう)。もちろん、後述のように線形探索とか、returnならツリー探索とか、探索にはよく出てくると思うんだが、それはチーム開発でもメンバー個々にやらさない方が良いと思うな。

ヤバいフローなら、例外がサポートされている環境では関数を分ける前から例外を使うべきだし、正直、後々肥大化する類いのループなら、もとより終了判定が柔らかい類いのアルゴリズムなんだと思うから、forで回さずに、do{}while();とか、そういうもんに適したループなんじゃないかと思う。

例外機構は(C++みたいな実装レベルではともかく)とても綺麗な言語仕様で、内部の構造にかかわらず使えることになっとるので、そっちが本命。

だから、決して元記事みたいな「読みやすさ」とか「ブレークポイントの付けやすさ」とかそういう問題じゃない。広い意味での読みやすさではあるけど、そう呼ぶのは問題の本質じゃない。ソースコードの構造上のリスクを抱え込むということなんじゃよ。ふぉっふぉっふぉっ。

ただね、線形探索みたいに、単に何かを見つけたいだけなら、for if breakで問題は起きんだろう。もうそれはイディオムだよね。それを許容しないなら、融通の利かない子が配列全走査しまくることにならんかえ? チーム開発としてそれでいいんかえ? 一番の解決方法は、共用ライブラリにバグのない線形探索入れとけよ、って感じ。エラーでなくてbreak,return使いたいのなんて、どうせ何らかの探索だろうよ。(ifのスコープがでかすぎて先に抜けたい、というケースもありそうだが、それはそれで別の問題がある)

個人で書くときとか、理論を示すときとか、回し続けないツールとかの場合だったら、良いと思うよ。再帰で書いた方がスムーズな処理はあるし、その場合、途中returnがある場合は少なくなかろう。そして、コンパクトに書けていれば、デバッグだって困難にはならなかろう。

Q.不定個のデータを扱うので無限ループさせたいんだけど

真面目に書くんなら、例外機構があればそれも例外がほんとだよね(ループ脱出の発生は稀なはずだから)。さもなくば、結局do{}while(!endflag);とかじゃね?