読者です 読者をやめる 読者になる 読者になる

gotoがいやならメソッドを使えばいいじゃない

同僚の人はフラグを使ったやりかた
〜プログラムは略〜
や、例外を使ったやりかた
〜プログラムは略〜
で、いままでやってきたらしい。
なんで?って聞いたら、goto絶対ダメ派なんだそうな。
ラベル付きbreakはgotoじゃないと思うけど、なかなか納得してくれんかったなあ。

2008-12-27 - プログラマとSEのあいだ


ラベル付きbreakは許せなくて、例外は許せるってのはちょっとよくわからん考え方だな。
そんなにラベルつきのbreakがいやなら、メソッド使ってラベルなしのbreakを使うというのはどうだろう。

public class DoubleLoop {
    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            if (!innerLoop(i + 1))
                break;  // 追記:この例ではreturn;の方がいい。コメント参照。
        }
    }
    private static boolean innerLoop(int outer) {
        for (int i = 0; i < 10; i++) {
            int inner = i + 1;
            System.out.printf("i = %d, j = %d%n", outer, inner);
            if (outer % 3 == 0 && inner % 3 == 0)
                return false;
        }
        return true;
    }
}

mainメソッド内のif文の中が気持ち悪いなら、ちょっと説明的にしてみるとか。

public class DoubleLoop {
    private static enum InnerLoopResult {
        BREAK(true), CONTINUE(false);
        final boolean isBreak;
        private InnerLoopResult(boolean isBreak) { this.isBreak = isBreak; }
    }
    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            if (innerLoop(i + 1).isBreak)
                break;  // 追記:この例ではreturn;の方がいい。コメント参照。
        }
    }
    private static InnerLoopResult innerLoop(int outer) {
        for (int i = 0; i < 10; i++) {
            int inner = i + 1;
            System.out.printf("i = %d, j = %d%n", outer, inner);
            if (outer % 3 == 0 && inner % 3 == 0)
                return InnerLoopResult.BREAK;
        }
        return InnerLoopResult.CONTINUE;
    }
}

個人的には最初の例で十分だけどね。


それぞれの利点・欠点はこんな感じ?

  利点 欠点
ラベルつきbreak 読みやすい
3重ループ以上にネストしても可読性が落ちない
処理が複雑になると可読性が落ちる
フラグ 細かい制御ができる ループがネストすると可読性は皆無
2重ループでさえやりたいことを素直に記述できない
例外 読みやすい
3重ループ以上にネストしても可読性が落ちない
インデントが深くなる
パフォーマンスが悪い
catch (Exception e)と書いてしまうと、本当の例外を握りつぶしてしまう
メソッドに分ける
(booleanで判定)
ループ内の処理が複雑になっても可読性が落ちない 3重ループ以上にネストすると、他の方法と併用するか、さらに複雑な記述が必要
breakの判定が少し分かりにくい
メソッドに分ける
(enumで判定)
ループ内の処理が複雑になっても可読性が落ちない
外側のループに相当する部分の可読性が高い
3重ループ以上にネストすると、他の方法と併用するか、さらに複雑な記述が必要
やってることに対してコードの量が多い


例外を使った場合のパフォーマンス低下だけど、2重ループの外側に大きなループがなければ、十分無視できるかもしれない。
かといって、例外を通常のフローの制御に使うのは反対だけどね。
もしやるとしたら、最大限例外じゃないことを明示するとか?

public class DoubleLoop {
    // privateなクラスにして、さらにExceptionというサフィックスを付けない
    private static class LoopBreakSignal extends Exception {
       ...
    }
    public static void main(String[] args) {
        try {
            ...
        } catch (LoopBreakSignal s) {   // eじゃなくてs
            // フロー制御に例外を使用しています。
            // LoopBreakSignalをExceptionやThrowableに変更しないでください
        }
    }
}

うーん、意味的にはThrowableから派生させたほうがいいのかな。