31

私が時々疑問に思ったことの1つは、以下に示す2つのスタイル(ある場合)のうち、どちらが優れているかということです。ガード条件が満たされていない場合はすぐに戻るのが良いですか、それともガード条件満たされた場合にのみ他のことを行うべきですか?

議論のために、ガード条件は、例外をスローすることによって制御フローに影響を与える可能性のあるものではなく、要素がコレクションにあるかどうかを確認するなど、ブール値を返す単純なテストであると想定してください。また、メソッド/関数がエディターのスクロールを必要としないほど短いと仮定します。

// Style 1
public SomeType aMethod() {
  SomeType result = null;

  if (!guardCondition()) {
    return result;
  }

  doStuffToResult(result);
  doMoreStuffToResult(result);

  return result;
}

// Style 2
public SomeType aMethod() {
  SomeType result = null;

  if (guardCondition()) {
    doStuffToResult(result);
    doMoreStuffToResult(result);
  }

  return result;
}
4

12 に答える 12

39

私は最初のスタイルを好みますが、必要がないときに変数を作成しない点が異なります。私はこれをします:

// Style 3
public SomeType aMethod() {

  if (!guardCondition()) {
    return null;
  }

  SomeType result = new SomeType();
  doStuffToResult(result);
  doMoreStuffToResult(result);

  return result;
}
于 2010-05-28T11:34:28.267 に答える
29

80 年代後半に Jackson 構造化プログラミングのトレーニングを受けた私の根深い哲学は、常に「関数には単一のエントリ ポイントと単一の出口ポイントが必要である」というものでした。これは、スタイル 2 に従ってコードを記述したことを意味します。

ここ数年で、このスタイルで書かれたコードはしばしば複雑すぎて読みにくく、維持しにくいことに気づき、スタイル 1 に切り替えました。

年老いた犬は新しいトリックを学べないと誰が言いますか? ;)

于 2010-05-28T12:55:21.747 に答える
16

スタイル1は、Linuxカーネルが間接的に推奨するものです。

https://www.kernel.org/doc/Documentation/process/coding-style.rstの第1章から

さて、8文字のインデントがあると、コードが右に移動しすぎて、80文字の端末画面で読みにくくなると主張する人もいます。その答えは、3レベルを超えるインデントが必要な場合は、とにかく失敗しているので、プログラムを修正する必要があるということです。

スタイル2はインデントのレベルを追加します、エルゴ、それはお勧めできません。

個人的にはスタイル1も好きです。スタイル2では、いくつかのガードテストがある関数で中括弧を合わせるのが難しくなります。

于 2010-05-28T11:44:41.967 に答える
6

Martin Fowler は、このリファクタリングを次のように 呼んでいます。

If/else ステートメントも循環的複雑性をもたらします。したがって、ケースのテストが難しくなります。すべての if/else ブロックをテストするには、多くのオプションを入力する必要がある場合があります。

ガード句があるかのように、最初にそれらをテストし、if/else 句内の実際のロジックをより明確な方法で処理できます。

于 2014-11-18T14:27:39.513 に答える
6

ここでガードという言葉が正しいかどうかはわかりません。通常、ガードが満たされない場合、例外またはアサーションが発生します。
しかし、これに加えて、私の意見ではコードをきれいに保つため、スタイル 1を使用します。条件が 1 つだけの簡単な例があります。しかし、多くの条件とスタイル 2 の場合はどうなるでしょうか。if多くのネストされたs または巨大な if 条件 (と||, )につながり&&ます。できるとわかったらすぐにメソッドから戻る方がよいと思います。
しかし、これは確かに非常に主観的です^^

于 2010-05-28T11:37:37.650 に答える
5

.net-Reflector を使用して .net-Framework を掘り下げると、.net プログラマーがスタイル 1 (または unbeli によって既に言及されているスタイル 3) を使用していることがわかります。理由は上記の回答ですでに述べられています。おそらくもう 1 つの理由は、コードをより読みやすく、簡潔で明確にすることです。このスタイルが最もよく使用されるのは、入力パラメーターをチェックするときです。ある種の frawework/library/dll をプログラムする場合は、常にこれを行う必要があります。最初にすべての入力パラメーターを確認してから、それらを操作してください。

于 2010-05-28T12:03:07.000 に答える
4

言語と、使用している「リソース」の種類 (開いているファイル ハンドルなど) に依存する場合があります。

C では、関数は実行中に取得したすべてのリソースを閉じたり解放したりする必要があるため、スタイル 2 の方が確実に安全で便利です。これには、割り当てられたメモリ ブロック、ファイル ハンドル、オペレーティング システム リソース (スレッドや描画コンテキストなど) へのハンドル、ミューテックスのロック、およびその他のさまざまなものが含まれます。関数を最後まで遅らせるreturnか、関数からの終了回数を制限することで、プログラマは適切にクリーンアップすることをより簡単に確認できるようになり、メモリ リーク、ハンドル リーク、デッドロック、およびその他の問題の防止に役立ちます。

RAIIスタイルのプログラミングを使用する C++ では、どちらのスタイルも同じように安全であるため、より便利な方を選択できます。個人的には、RAII スタイルの C++ でスタイル 1 を使用しています。RAII を使用しない C++ は C に似ているため、その場合もおそらくスタイル 2 の方が適しています。

ガベージ コレクションを使用する Java のような言語では、ランタイムは 2 つのスタイルの違いをスムーズにするのに役立ちます。ただし、一部のタイプのオブジェクトを明示的に「閉じ」ないと、これらの言語にも微妙な問題が発生する可能性があります。たとえば、 new を構築し、返す前にそれを閉じjava.io.FileOutputStreamない場合、関連付けられたオペレーティング システム ハンドルは、ランタイム ガベージがスコープ外のインスタンスを収集するまで開いたままになります。これは、書き込みのためにファイルを開く必要がある別のプロセスまたはスレッドが、インスタンスが収集されるまで開くことができない可能性があることを意味する可能性があります。FileOutputStreamFileOutputStream

于 2010-05-28T11:55:44.050 に答える
3

私が教えられたベスト プラクティスには反しますが、このような状態の場合は、if ステートメントのネストを減らす方がはるかに優れていることがわかりました。はるかに読みやすく、複数の場所で終了しますが、デバッグは非常に簡単だと思います。

于 2010-05-28T11:36:20.710 に答える
1

Style1 は、小さなメソッドと組み合わせた場合のベストプラクティスであるため、より使用されるようになったと思います。

大きなメソッドがある場合は、Style2 の方が優れたソリューションに見えます。それらがあれば...終了方法に関係なく実行したい共通コードがいくつかあります。しかし、適切な解決策は、単一の出口点を強制するのではなく、メソッドを小さくすることです。

たとえば、大きなメソッドから一連のコードを抽出したい場合、このメソッドに 2 つの終了ポイントがある場合、問題が発生し始めます。これを自動的に行うのは困難です。style1 で記述された大きなメソッドがある場合、通常はそれを style2 に変換してからメソッドを抽出し、それぞれに Style1 コードを含める必要があります。

したがって、Style1 が最適ですが、小さなメソッドと互換性があります。Style2 はあまり良くありませんが、必要のない大きなメソッドがあり、分割する時間があればお勧めします。

于 2011-10-19T18:00:26.880 に答える
0

私は「それは...に依存します」と言うでしょう

関数/メソッドを終了する前に2行または3行を超えるクリーンアップシーケンスを実行する必要がある状況では、クリーンアップシーケンスを1回だけ記述および変更する必要があるため、スタイル2を使用します。つまり、保守性が容易になります。

他のすべての場合、私はスタイル1を好みます。

于 2010-05-28T13:06:28.620 に答える
0

私自身は方法#1を使用することを好みます。論理的に読みやすく、論理的にも私たちがやろうとしていることにより似ています。(何か問題が発生した場合は、今すぐ機能を終了し、go を渡さないでください。$200 を収集しないでください)

さらに、ほとんどの場合、関数が正しく実行されなかったことを関数を呼び出したユーザーに示し、適切なアクションを実行するために、論理的に可能な結果ではない値 (つまり -1) を返す必要があります。これは、方法 1 にも適しています。

于 2010-05-28T12:53:06.607 に答える
-3

番号 1 は、通常、簡単で怠惰でずさんな方法です。番号 2 は、ロジックをきれいに表現します。他の人が指摘しているのは、はい、面倒になる可能性があるということです。ただし、この傾向には重要な利点があります。スタイル #1 は、関数がおそらくやりすぎていることを隠すことができます。何が起こっているかの複雑さを視覚的にうまく示していません。つまり、コードが「これはこの 1 つの関数に対して少し複雑になりすぎている」と言うのを防ぎます。また、あなたのコードを知らない他の開発者が、あち​​こちにちりばめられたリターンを見逃すのを少し簡単にします。

それでは、コードに話させてください。長い条件が表示されたり、ネストされた if ステートメントが表示されたりする場合は、これを複数の関数に分割した方がよいか、よりエレガントに書き直す必要があることを示しています。

于 2010-05-28T13:35:38.390 に答える