voidメソッド内でreturnを使用するのは悪い習慣ですか?


92

次のコードを想像してみてください。

void DoThis()
{
    if (!isValid) return;

    DoThat();
}

void DoThat() {
    Console.WriteLine("DoThat()");
}

voidメソッド内でreturnを使用しても大丈夫ですか?パフォーマンスの低下はありますか?または、次のようなコードを作成することをお勧めします。

void DoThis()
{
    if (isValid)
    {
        DoThat();
    }
}
c#  return  void 

1
どうですか:void DoThis(){if(isValid)DoThat(); }
Dscoduc 2009

30
コードを想像しますか?どうして?すぐそこです!:-D
STW

これは良い質問です。私は常にreturnを使用するのが良い習慣だと思います。メソッドまたは関数を終了します。特に、複数のIQueryable <T>結果があり、それらすべてが相互に依存しているLINQデータマイニングメソッドでは。それらの1つに結果がない場合は、警告して終了します。
Cheung

回答:



33

(ネストされたコードではなく)ガードを使用するもう1つの大きな理由があります。別のプログラマーが関数にコードを追加した場合、それらはより安全な環境で動作します。

考えてみましょう:

void MyFunc(object obj)
{
    if (obj != null)
    {
        obj.DoSomething();
    }
}

対:

void MyFunc(object obj)
{
    if (obj == null)
        return;

    obj.DoSomething();
}

ここで、別のプログラマーが次の行を追加するとします。obj.DoSomethingElse();

void MyFunc(object obj)
{
    if (obj != null)
    {
        obj.DoSomething();
    }

    obj.DoSomethingElse();
}

void MyFunc(object obj)
{
    if (obj == null)
        return;

    obj.DoSomething();
    obj.DoSomethingElse();
}

明らかにこれは単純なケースですが、プログラマーは最初の(ネストされたコード)インスタンスのプログラムにクラッシュを追加しました。2番目の例(ガード付きの早期終了)では、ガードを通過すると、コードはnull参照の意図しない使用から安全になります。

確かに、優れたプログラマーはこのような間違いを犯しません(多くの場合)。しかし、予防は治療よりも優れています。この潜在的なエラーの原因を完全に排除する方法でコードを記述できます。ネスティングは複雑さを増すため、ベストプラクティスでは、ネスティングを減らすためにコードをリファクタリングすることをお勧めします。


はい。ただし、その一方で、ネストのいくつかのレイヤーとその条件により、コードはさらにバグが発生しやすくなり、ロジックの追跡がより困難になり、さらに重要なことに、デバッグがより困難になります。フラット関数はそれほど悪いものではありません、IMO。
スクリム2009

18
私は入れ子を減らすことに賛成だと主張しています!:-)
ジェイソンウィリアムズ

私はこれに同意する。また、リファクタリングの観点から、objが構造体になった場合、または保証できるものがnullにならない場合は、メソッドをリファクタリングする方が簡単で安全です。
Phil Cooper

18

悪い習慣??? ありえない。実際、検証が失敗した場合は、できるだけ早くメソッドから戻って検証を処理することをお勧めします。それ以外の場合は、ネストされたifとelseが大量に発生します。早期に終了すると、コードの可読性が向上します。

同様の質問に対する回答も確認してください。if-elseの代わりにreturn / continueステートメントを使用する必要がありますか?


8

それは悪い習慣ではありません(すでに述べたすべての理由で)。ただし、メソッドでのリターンが多いほど、より小さな論理メソッドに分割する必要があります。


8

最初の例は、ガードステートメントの使用です。ウィキペディアから:

コンピュータプログラミングでは、ガードはブール式であり、プログラムの実行が問題のブランチで続行される場合はtrueと評価される必要があります。

メソッドの先頭にたくさんのガードを配置することは、完全に理解できるプログラミング方法だと思います。基本的には、「これらのいずれかが当てはまる場合は、このメソッドを実行しないでください」と言っています。

したがって、一般的には次のようになります。

void DoThis()
{
  if (guard1) return;
  if (guard2) return;
  ...
  if (guardN) return;

  DoThat();
}

私はそれがはるかに読みやすいと思います:

void DoThis()
{
  if (guard1 && guard2 && guard3)
  {
    DoThat();
  }
}

3

パフォーマンスの低下はありませんが、2番目のコードは読みやすく、保守が容易です。


ラッセル私はあなたの意見に同意しませんが、あなたはそれに反対票を投じるべきではありませんでした。+1して均等にします。ところで、ブールテストと1行のリターンの後に空白行が続くことは、何が起こっているのかを明確に示していると思います。たとえば、ロドリゴの最初の例。
Paul Sasik 2009

私はこれに同意しません。ネストを増やしても、読みやすさは向上しません。最初のコードは、完全に理解できるパターンである「ガード」ステートメントを使用しています。
cdmckay 2009

私も同意しません。早期に関数から抜け出すガード句は、読者が実装を理解するのを助ける上で、今日では一般的に良いことと見なされています。
ピートホジソン

2

この場合、2番目の例はより良いコードですが、それはvoid関数から戻ることとは関係ありません。これは、2番目のコードがより直接的であるためです。しかし、void関数から戻ることはまったく問題ありません。


0

これは完全に問題なく、「パフォーマンスの低下」はありませんが、角かっこなしで「if」ステートメントを記述しないでください。

常に

if( foo ){
    return;
}

ずっと読みやすくなっています。また、コードの一部がステートメント内にないのに、そのステートメント内にあると誤って想定することはありません。


2
読み取り可能は主観的です。imho、不要なコードに追加すると読みにくくなります...(もっと読む必要がありますが、なぜそこにあるのか疑問に思い、何かを見逃していないことを確認するために時間を無駄にします)...しかしそれは私のです主観的な意見
Charles Bretana 2009

10
常に中括弧を含めるより良い理由は、読みやすさではなく、安全性です。中かっこがないと、後で誰かがifの一部として追加のステートメントを必要とするバグを修正するのは簡単です。十分な注意を払わず、中かっこも追加せずに追加してください。常に中括弧を含めることにより、このリスクが排除されます。
スコットドーマン

2
シルキー、あなたの前にEnterキーを押してください{。これにより、同じ列に{あなたが表示さ}れ、読みやすさが大幅に向上します(対応する開閉中括弧を見つけるのがはるかに簡単になります)。
イマジスト

1
@Imagist私はそれを個人的な好みに任せます。そしてそれは私が好む方法で行われました:)
正午シルク

1
すべての中括弧が同じレベルのインデント配置された中括弧と一致する場合、どのifステートメントが中括弧を必要とするかを視覚的に区別するのは簡単であり、したがって、ifステートメントで単一のステートメントを制御することは安全です。オープンブレースをラインに押し戻すと、if各マルチステートメントの垂直方向のスペースのラインが節約されますifが、それ以外の場合は不要なクローズブレースラインを使用する必要があります。
スーパーキャット2014

0

私はこれについてあなたのすべての若いホイッパースナッパーに反対するつもりです。

メソッドの途中でreturnを使用することは、無効であろうとなかろうと、非常に悪い習慣です。これは、40年近く前に、故Edsger W. Dijkstraによって、よく知られている「有害と見なされるGOTOステートメント」から始まった理由が非常に明確に示されているためです。 」、そしてダール、ダイクストラ、ホアによる「構造化プログラミング」を続けています。

基本的なルールは、すべての制御構造とすべてのモジュールに、1つの入口と1つの出口が必要であるということです。モジュールの途中で明示的に戻ると、そのルールが破られ、プログラムの状態について推論するのがはるかに難しくなります。その結果、プログラムが正しいかどうかを判断するのがはるかに難しくなります(これははるかに強力なプロパティです) 「それが機能しているように見えるかどうか」よりも)。

「有害と見なされるGOTOステートメント」と「構造化プログラミング」は、1970年代の「構造化プログラミング」革命を開始しました。これらの2つの部分が、if-then-else、while-do、およびその他の明示的な制御構造が今日ある理由であり、高水準言語のGOTOステートメントが絶滅危惧種リストに含まれている理由です。(私の個人的な意見では、彼らは絶滅種リストに載っている必要があります。)

メッセージフロー変調器は、最初の試行で受け入れテストに合格した最初の軍事ソフトウェアであり、逸脱、免除、または「ええ、しかし」の言い回しはなく、言語がない言語で書かれていたことは注目に値します。 GOTOステートメント。

Nicklaus Wirthが、Oberonプログラミング言語の最新バージョンであるOberon-07のRETURNステートメントのセマンティクスを変更し、型付きプロシージャ(つまり、関数)の宣言の最後の部分にしたことにも言及する価値があります。関数本体の実行可能ステートメント。変更の彼の解明は、彼が前のフォームが正確ので、それをやったと言ったWAS構造化プログラミングの一の出口原則の違反。


2
@ジョン:パスカルを乗り越えたちょうどその頃に、複数の返品に関するDykstraの差し止め命令を乗り越えました(とにかく、私たちのほとんど)。
ジョンサンダース

複数の返品が必要な場合は、メソッドがやりすぎを試みていることを示す兆候であることが多く、削減する必要があります。これについてはJohnまでは説明しません。パラメーター検証の一部としてのreturnステートメントは妥当な例外かもしれませんが、アイデアがどこから来ているのかはわかります。
kyoryu 2009

@nairdaen:その四半期の例外についてはまだ論争があります。私のガイドラインは次のとおりです。開発中のシステムが元の例外的な状態を引き起こした問題を修正する必要があり、そのコードを書かなければならない人を怒らせてもかまわない場合は、例外をスローします。それから私は会議で怒鳴られます。なぜなら、その男はわざわざ例外をキャッチせず、アプリはテスト中にクラッシュしました。なぜ彼が問題を修正しなければならないのかを説明し、事態は再び落ち着きます。
John R. Strohm

ガードステートメントとgotoには大きな違いがあります。gotosの悪は、どこにでもジャンプできることです。そのため、解き明かして覚えるのが非常に混乱する可能性があります。ガードステートメントは正反対です。メソッドへのゲートエントリを提供します。その後、「安全な」環境で作業していることがわかり、残りのコードを記述するときに考慮する必要のある事項の数が減ります(例: 「このポインターがnullになることはないので、コード全体でそのケースを処理する必要はありません」)。
ジェイソンウィリアムズ

@Jason:元の質問は、特にガードステートメントに関するものではなく、メソッドの途中でのランダムなreturnステートメントに関するものでした。与えられた例は警備員のようでした。重要な問題は、リターンサイトで、メソッドが何をしたか、何をしなかったかについて推論できるようにしたいということです。ランダムなGOTOが難しくするのとまったく同じ理由で、ランダムなリターンはそれを難し​​くします。参照:Dijkstra、「GOTOステートメントは有害と見なされます」。構文の面では、cdmckayは別の答えで、ガードに適した構文を示しました。どちらの形式がより読みやすいかという彼の意見には同意しません。
ジョンR.ストローム

0

ガードを使用するときは、読者を混乱させないように、特定のガイドラインに必ず従ってください。

  • 関数 は1つのことを行います
  • 警備員は最初のものとしてのみ導入されます、関数のロジックます
  • ネストされていない部分には、関数のコアインテントが含まれています

// guards point you to the core intent
void Remove(RayCastResult rayHit){

  if(rayHit== RayCastResult.Empty)
    return
    ;
  rayHit.Collider.Parent.Remove();
}

// no guards needed: function split into multiple cases
int WonOrLostMoney(int flaw)=>
  flaw==0 ? 100 :
  flaw<10 ? 30 :
  flaw<20 ? 0 :
  -20
;

-3

オブジェクトがnullの場合など、何も返さない代わりに例外をスローします。

メソッドはオブジェクトがnullでないことを想定しており、そうではないため、例外をスローして呼び出し元に処理させる必要があります。

しかし、それ以外の場合、早期復帰は悪い習慣ではありません。


1
答えは質問に答えません。質問はvoidメソッドであるため、何も返されません。また、メソッドにはパラメーターがありません。戻り値の型がオブジェクトの場合、nullを返さないという点がありますが、それはこの質問には当てはまりません。
ルークハンマー
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.