return付きのswitchステートメント—コードの正確さ


91

Cにほぼこの構造のコードがあるとします。

switch (something)
{
    case 0:
      return "blah";
      break;

    case 1:
    case 4:
      return "foo";
      break;

    case 2:
    case 3:
      return "bar";
      break;

    default:
      return "foobar";
      break;
}

明らかに、breaksはコードを正しく実行するために必要ではありませんが、私がそれらをそこに置かなければ、それは一種の悪い習慣のように見えます。

どう思いますか?削除しても大丈夫ですか?または、「正確さ」を高めるためにそれらを保持しますか?

回答:


137

breakステートメントを削除します。それらは必要ではなく、おそらく一部のコンパイラは「到達不能コード」警告を発行します。


2
continueorのような他の無条件制御ジャンプ文にも同じことが当てはまりますgoto-の代わりにそれらを使用するのは慣用的breakです。
ca

31

私はまったく別のタックを取ります。メソッド/関数の途中でRETURNを返さないでください。代わりに、戻り値をローカル変数に入れて、最後に送信します。

個人的には、以下の方が読みやすくなっています。

String result = "";

switch (something) {
case 0:
  result = "blah";
  break;
case 1:
  result = "foo";
  break;
}

return result;

24
「One Exit」の哲学は、Cで理にかなっています。Cでは、物事を正しくクリーンアップする必要があります。C ++では、例外によっていつでも関数から外れる可能性があることを考えると、1つの出口の哲学はC ++では実際には役に立ちません。
Billy ONeal

29
理論的にはこれは素晴らしいアイデアですが、読みやすさを妨げる追加の制御ブロックが必要になることがよくあります。
ミケロビ

8
私がこのように物事を行う主な理由は、長い関数ではコードをスキャンするときに出口ベクトルを見逃しがちだからです。ただし、出口が常に最後にある場合は、何が起こっているのかをすばやく把握する方が簡単です。
NotMe

7
さて、それと、コードの途中のreturnブロックは、GOTOの悪用を思い出させます。
NotMe

5
@Chris:より長い関数では、私は完全に同意します-しかし、それは通常より大きな問題を意味するので、それをリファクタリングします。多くの場合、それはスイッチで戻る簡単な関数であり、何が起こるかが非常に明確であるため、余分な変数の追跡に頭を使いすぎないでください。
Stephen

8

個人的には、リターンを取り除き、休憩を取ります。変数に値を割り当てるには、switchステートメントを使用します。次に、switchステートメントの後にその変数を返します。

これは議論の余地のあるポイントですが、優れたデザインとカプセル化は一方向と一方向の両方を意味するものだと常に感じていました。ロジックを保証する方がはるかに簡単であり、関数の循環的な複雑さに基づいてクリーンアップコードを誤って見落とすことがありません。

1つの例外:関数の最初でリソースが取得される前に不正なパラメーターが検出された場合、早期に戻ることは問題ありません。


この特定の場合は良いかもしれswitchませんが、一般的には必ずしも最善ではありません。さて、関数内のswitchステートメントが、特定の場合にのみ有効である別の500行のコードの前にあるとしましょうtrue。それを実行する必要のない場合のために、すべての余分なコードを実行する意味は何ですか。これらののreturn内部に配置する方が良いのではないですか?switchcase
Fr0zenFyr

6

改行を保持する-改行が既に配置されている場合は、後でコードを編集するときに、問題が発生する可能性が低くなります。

そうは言っても、多くの人(私を含む)は関数の途中から戻るのは悪い習慣だと考えています。理想的には、関数には1つの入口点と1つの出口点が必要です。


5

それらを削除します。caseステートメントから戻るのは慣用的であり、そうでなければ「到達不能なコード」ノイズです。


5

それらを削除します。私の本では、このようなデッドコードは、ダブルテイクを実行して「自分でその行を実行する方法を教えてください」と思わせるため、エラーと見なす必要があります。


4

私は通常それらなしでコードを書くでしょう。IMO、デッドコードは、だらしのないことや理解不足を示す傾向があります。

もちろん、次のようなことも検討します。

char const *rets[] = {"blah", "foo", "bar"};

return rets[something];

編集:編集された投稿でも、この一般的なアイデアはうまく機能します。

char const *rets[] = { "blah", "foo", "bar", "bar", "foo"};

if ((unsigned)something < 5)
    return rets[something]
return "foobar";

ある時点で、特に入力値がスパースである場合(たとえば、1、100、1000、10000)、代わりにスパース配列が必要になります。これはツリーまたはマップとして適切に実装できます(もちろん、この場合もスイッチは機能します)。


このソリューションがうまく機能しない理由を反映するように投稿を編集しました。
houbysoft 2010年

@あなたの編集:はい、しかしそれはより多くのメモリを必要とするでしょう、等々。
houbysoft 2010年

3
@houbysoft:追加のメモリが必要になると結論付ける前に注意深く見てください。典型的なコンパイラでは、 "foo"を2回(たとえば)使用すると、データの複製ではなく、単一のデータブロックへの2つのポインタが使用されます。短いコードも期待できます。多くの繰り返し値がない限り、多くの場合、全体的なメモリが節約されます。
Jerry Coffin

本当。値のリストが非常に長く、スイッチの方が優れているように見えるので、私はまだ私の解決策に固執します。
houbysoft 2010年

1
実際、このような問題に対するよりエレガントで効率的な答えは、スイッチの値が隣接し、データ型が同種である場合、配列を使用してスイッチを完全に回避することです。
ドウェインロビンソン

2

私はそれらを削除してdefault:ブランチを定義すると言います。


適切なデフォルトがない場合は、デフォルトを定義しないでください。
ビリーONeal

1つあり、私は1つを定義しました。質問には入れず、編集しました。
houbysoft 2010年

2

配列を持っている方がいいのではないでしょうか

arr[0] = "blah"
arr[1] = "foo"
arr[2] = "bar"

やるreturn arr[something];

それが一般的なプラクティスに関するものである場合は、breakステートメントをスイッチ内に保持する必要があります。return今後、ステートメントが不要になった場合は、次のステップに進む可能性が低くなりますcase


このソリューションがうまく機能しない理由を反映するように投稿を編集しました。
houbysoft 2010年

2

「正確さ」については、単一のエントリー、単一の出口ブロックが良い考えです。少なくとも、私がコンピュータサイエンスの学位を取得したときのことです。だから私はおそらく変数を宣言し、スイッチに割り当て、関数の最後に一度戻るでしょう


2

どう思いますか?削除しても大丈夫ですか?または、「正確さ」を高めるためにそれらを保持しますか?

削除しても問題ありません。使用return は、使用すべきでないシナリオ とまったく同じbreakです。


1

面白い。これらの回答のほとんどからのコンセンサスは、冗長なbreakステートメントが不要な混乱であるように思われます。一方、私breakはスイッチのステートメントをケースの「クローズ」として読みました。バグで落ちる可能性があるため、私で飛び出そcaseうとしないbreak傾向にあるブロック。

私はそれがのreturn代わりにがあるときのようではないことを知っていますが、それはbreak私の目がスイッチのケースブロックを「読み取る」方法であるため、個人的にはそれぞれをcaseとペアにすることを好みbreakます。しかし、多くのコンパイラーはbreakreturn余計なものに到達できないという不満を訴えています。どうやら、私は少数派のようです。

したがって、break次のa を削除しますreturn

注意:これはすべて、単一の入口/出口ルールに違反することが良い考えであるかどうかを無視しています。それに関しては、残念ながら状況次第で変わっていくという意見があります...


0

私はそれらを削除すると言います。コードが非常に読みにくいので、「安全のため」に改行を続ける必要がある場合は、コーディングスタイルを再検討する必要があります。

また、switchステートメントで改行と改行を混在させないで、それらのいずれかを使用することを常に好んでいます。


0

私は個人的にbreaks を失う傾向があります。おそらく、この習慣の1つの原因は、Windowsアプリのウィンドウプロシージャのプログラミングにあります。

LRESULT WindowProc (HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    switch (uMsg)
    {
        case WM_SIZE:
            return sizeHandler (...);
        case WM_DESTROY:
            return destroyHandler (...);
        ...
    }

    return DefWindowProc(hwnd, uMsg, wParam, lParam);
}

私は個人的に、このアプローチを、各ハンドラーによって設定された戻り変数を宣言し、最後にそれを返すよりも、はるかに単純で簡潔かつ柔軟であると感じています。このアプローチを考えると、breaksは冗長であるため、使用する必要があります。これらは有用な目的(構文上または視覚的にIMO)を提供せず、コードを膨張させるだけです。


0

* breakと思います *は目的のため。プログラミングの「イデオロギー」を存続させるためです。論理的な一貫性のないコードを「プログラム」するだけの場合は、おそらくあなたには読めるでしょうが、明日試してください。上司に説明してみてください。Windows 3030で実行してみてください。

ブレア、アイデアは非常に簡単です:


Switch ( Algorithm )
{

 case 1:
 {
   Call_911;
   Jump;
 }**break**;
 case 2:
 {
   Call Samantha_28;
   Forget;
 }**break**;
 case 3:
 {
   Call it_a_day;
 }**break**;

Return thinkAboutIt?1:return 0;

void Samantha_28(int oBed)
{
   LONG way_from_right;
   SHORT Forget_is_my_job;
   LONG JMP_is_for_assembly;
   LONG assembly_I_work_for_cops;

   BOOL allOfTheAbove;

   int Elligence_says_anyways_thinkAboutIt_**break**_if_we_code_like_this_we_d_be_monkeys;

}
// Sometimes Programming is supposed to convey the meaning and the essence of the task at hand. It is // there to serve a purpose and to keep it alive. While you are not looking, your program is doing  // its thing. Do you trust it?
// This is how you can...
// ----------
// **Break**; Please, take a **Break**;

/ *ちょっとした質問です。上記を読んでいる間にコーヒーはどれくらい食べましたか?IT がシステムを破壊する場合がある* /


0

ある時点でコードを終了します。これにより、コードが読みやすくなります。間にreturnステートメント(複数の出口)を追加すると、デバッグが困難になります。


0

「ルックアップ」タイプのコードがある場合は、switch-case句をメソッドにパッケージ化できます。

私は楽しみのために開発している「趣味」システムにこれらのいくつかを持っています:

private int basePerCapitaIncomeRaw(int tl) {
    switch (tl) {
        case 0:     return 7500;
        case 1:     return 7800;
        case 2:     return 8100;
        case 3:     return 8400;
        case 4:     return 9600;
        case 5:     return 13000;
        case 6:     return 19000;
        case 7:     return 25000;
        case 8:     return 31000;
        case 9:     return 43000;
        case 10:    return 67000;
        case 11:    return 97000;
        default:    return 130000;
    }
}

(そうです。それがGURPSスペースです...)

ほとんどの場合、メソッドで複数のリターンを回避する必要があることを他の人に同意します。これは、配列または他の何かとして実装する方が適切だったかもしれません。上記のように、switch-case-returnが入力と出力の間に1-1の相関関係があるルックアップテーブルとかなり簡単に一致することがわかりました(ロールプレイングゲームはそれらでいっぱいですが、それらは他にも存在すると確信しています) 「ビジネス」も):D

一方、case-clauseがより複雑な場合、またはswitchステートメントの後に何かが発生する場合は、returnを使用することはお勧めしませんが、スイッチに変数を設定し、breakで終了して、returnします。最後に変数の値。

(... 3番目に?ハンド...いつでもスイッチを独自のメソッドにリファクタリングできます...パフォーマンスに影響があるとは思えません。最近のコンパイラがそれをインライン化できるもの...)

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.