制御フローによって冗長になる状況では、「else」を使用する必要がありますか?


18

次の例のようなコードに出くわすことがあります(この関数の機能はこの質問の範囲外です)。

function doSomething(value) {
  if (check1(value)) {
    return -1;
  }
  else if (check2(value)) {
    return value;
  }
  else {
    return false;
  }
}

あなたが見ることができるように、ifelse ifおよびelseステートメントは、と組み合わせて使用されているreturn声明。これはカジュアルなオブザーバーにはかなり直感的に思えますが、else-s を削除し、次のようにコードを簡略化する方が(ソフトウェア開発者の観点から)よりエレガントになると思います。

function doSomething(value) {
  if (check1(value)) {
    return -1;
  }
  if (check2(value)) {
    return value;
  }
  return false;
}

これは理にかなっています。return(同じスコープ内の)ステートメントに続くすべてが実行されることはないため、上記のコードは意味的に最初の例と等しくなります。

上記のうち、優れたコーディングプラクティスに適しているものはどれですか?コードの可読性に関して、どちらの方法にも欠点はありますか?

編集:参照として提供されたこの質問で、重複した提案が行われました。私の質問は別のトピックに関係していると思います。他の質問で提示されたような重複したステートメントを避けることを求めていないからです。どちらの質問も、わずかに異なる方法ではありますが、繰り返しを減らすことを目指しています。


14
これelseはささいな問題ではありませんが、大きな問題は、明らかに単一の関数から2つのデータ型を返すため、関数のAPIが予測不能になることです。
アンディ

3
E_CLEARLY_NOTADUPE
小次郎

3
@DavidPacker:少なくとも2つ。3つかもしれません。-1は数値でfalseあり、ブール値であり、valueここでは指定されていないため、どのタイプのオブジェクトでもかまいません。
Yay295 16

1
@ Yay295私valueは実際に整数であることを望んでいました。それが何かである場合、それはさらに悪いことです。
アンディ

@DavidPackerこれは、特に優れた実践に関する質問に関して提起された場合に、非常に重要なポイントです。私はあなたとYay295に同意しますが、コードサンプルは、コーディングの無関係なテクニックを示す例として役立ちます。それにもかかわらず、これは重要な問題であり、実際のコードで見落としてはならないことに留意してください。
サイ

回答:


23

私はなしのものが好きで、elseここに理由があります:

function doSomething(value) {
  //if (check1(value)) {
  //  return -1;
  //}
  if (check2(value)) {
    return value;
  }
  return false;
}

それをすることは何も壊さなかったので、それは想定されていなかった。

すべての形式での相互依存を憎む(関数の命名を含むcheck2())。分離できるものをすべて分離します。必要な場合もありますがelse、ここには表示されません。


ここで述べた理由から、私もこれを好む。ただし、通常、各ifブロックの最後にコメントを付けて} //else、コードを読み取るときに、ifコードブロックの後の実行がifステートメントの条件がfalse である場合のみであることを明確にします。このようなものがなければ、特にifブロック内のコードがより複雑になったif場合、if条件が真であるときにブロックを超えて実行することを意図していないことをコードを維持している人にはわかりません。
Makyen

@Makyen良い点を挙げます。無視しようとしていたもの。またif、このセクションのコードが完了し、構造が完成したことを明確にするために、後に何かを行う必要があると思います。これを行うには、ここでまだ行っていないことを行います(他の変更を加えることでOPの主要な点から注意をそらしたくないためです)。気を散らすことなく、それをすべて達成できるように、私はできる限り簡単なことをします。空白行を追加します。
candied_orange 16

27

コードのセマンティクスに依存すると思います。3つのケースが互いに依存している場合は、明示的に述べてください。これにより、コードが読みやすくなり、他の人が理解しやすくなります。例:

if (x < 0) {
    return false;
} else if (x > 0) {
    return true;
} else {
    throw new IllegalArgumentException();
}

ここでxは、3つのケースすべての値に明らかに依存しています。lastを省略した場合else、これはそれほど明確ではありません。

ケースが互いに直接依存していない場合は、省略します。

if (x < 0) {
    return false;
}
if (y < 0) {
    return true;
}
return false;

コンテキストのない単純な例でこれを示すのは難しいですが、私はあなたが私のポイントを得ることを願っています。


6

私は2番目のオプション(分離しないことを好むifs何をelse if、早期returnしかしそれである限り、コードブロックが短いように

コードブロックが長い場合はelse if、を使用することをお勧めします。それ以外の場合は、コードが持ついくつかの出口点を明確に理解できないためです。

例えば:

function doSomething(value) {
  if (check1(value)) {
    // ...
    // imagine 200 lines of code
    // ...
    return -1;
  }
  if (check2(value)) {
    // ...
    // imagine 150 lines of code
    // ...
    return value;
  }
  return false;
}

その場合は、を使用する方が適切else ifです。変数に戻りコードを格納し、最後に1つの戻り文のみを含めます。

function doSomething(value) {
  retVal=0;
  if (check1(value)) {
    // ...
    // imagine 200 lines of code
    // ...
    retVal=-1;
  } else if (check2(value)) {
    // ...
    // imagne 150 lines of code
    retVal=value;
  }
  return retVal;
}

ただし、関数が短くなるように努力する必要があるため、最初のコードスニペットのように、短くして終了することをお勧めします。


1
私はあなたの前提に同意しますが、コードがどのようにあるべきかを議論している限り、コードブロックを短くすることに同意するだけではないでしょうか?私が選択する必要がある場合、関数に分割されないコードの長いブロックを使用するよりも、他のコードを使用するよりも悪いと思います。
小次郎

1
奇妙な議論。returnは3行以内であるifためelse if、200行のコードの後でもそれほど違いはありません。単一のリターンスタイルあなたはここに紹介するには、例外を除いて、エラーを報告しませんCと他の言語の伝統です。ポイントは、リソースをクリーンアップする単一の場所を作成することでした。例外言語はそのためにfinallyブロックを使用します。デバッガーが中括弧を閉じる関数にブレークポイントを配置できない場合、これによりブレークポイントを配置する場所も作成されると思います。
candied_orange 16

4
割り当てがまったく好きではありませんretVal。関数を安全に終了できる場合は、関数からの単一出口の変数に返される安全な値を割り当てずに、すぐに終了してください。本当に長い関数で単一の戻り点を使用するとき、私は一般に他のプログラマーを信頼せず、戻り値の他の変更についても関数の残りを調べます。早く失敗し、早く戻ることは、とりわけ、私が生きている2つのルールです。
アンディ

2
私の意見では、長いブロックではさらに悪い。外部コンテキストが何であれ、できるだけ早く関数を終了しようとします。
アンディ

2
私はここでDavidPackerと一緒です。シングルリターンスタイルでは、すべての割り当てポイントと割り当てポイントの後のコードを調査する必要があります。アーリーリターンスタイルの出口点を研究することは、ロングまたはショートのどちらにとっても望ましいことです。言語が最終ブロックを許可している限り。
candied_orange 16

4

私は異なる状況で両方を使用します。検証チェックでは、elseを省略します。制御フローでは、elseを使用します。

bool doWork(string name) {
  if(name.empty()) {
    return false;
  }

  //...do work
  return true;
}

bool doWork(int value) {
  if(value % 2 == 0) {
    doWorkWithEvenNumber(value);
  }
  else {
    doWorkWithOddNumber(value);
  }
}

最初のケースは、最初にすべての前提条件をチェックし、それらを邪魔にならないようにし、実行したい実際のコードに進んでいるようなものです。そのため、実際に実行したいジューシーなコードは、関数のスコープに直接属します。それが関数の本当の目的です。
2番目のケースは、両方のパスが実行される有効なコードであり、条件に基づいて他のパスと同じように機能に関連しているように見えます。そのため、これらは互いに同様のスコープレベルに属します。


0

私がこれまで本当に重要だと思った唯一の状況は、次のようなコードです。

List<?> toList() {
    if (left != null && right != null) {
        Arrays.asList(merge(left, right));
    }
    if (left != null) {
        return Arrays.asList(left);
    }
    if (right != null) {
        return Arrays.asList(right);
    }
    return Arrays.asList();
}

ここには明らかに何か問題があります。問題は、return追加する必要があるのか​​、それとも明確ではないArrays.asList削除。これを修正するには、関連するメソッドをさらに詳しく調べる必要があります。常に完全なif-elseブロックを使用することにより、このあいまいさを回避できます。この:

List<?> toList() {
    if (left != null && right != null) {
        Arrays.asList(merge(left, right));
    } else if (left != null) {
        return Arrays.asList(left);
    } else if (right != null) {
        return Arrays.asList(right);
    } else {
        return Arrays.asList();
    }
}

最初に明示的な戻り値を追加しない限り、(静的にチェックされた言語で)コンパイルしません if

一部の言語では、最初のスタイルさえ許可されていません。私は厳密に命令的なコンテキストまたは長いメソッドの前提条件でのみそれを使用しようとします:

List<?> toList() {
    if (left == null || right == null) {
        throw new IllegalStateException()
    }
    // ...
    // long method
    // ...
}

-2

そのような関数から3つの出口があることは、コードをたどって悪いプラクティスを実行しようとしている場合、本当に混乱します。

関数の単一の出口点がある場合、elseが必要です。

var result;
if(check1(value)
{
    result = -1;
}
else if(check2(value))
{
    result = value;
}
else 
{
    result = 0;
}
return result;

実際、さらに先へ進みましょう。

var result = 0;
var c1 = check1(value);
var c2 = check2(value);

if(c1)
{
    result = -1;
}
else if(c2)
{
    result = value;
}

return result;

入力検証の単一の早期リターンについて議論することができます。ifでロジックを作成する場合は、バルク全体をラップしないでください。


3
このスタイルは、明示的なリソース管理を備えた言語に由来します。割り当てられたリソースを解放するには単一ポイントが必要でした。自動リソース管理を備えた言語では、単一のリターンポイントはそれほど重要ではありません。むしろ、変数の数と範囲を減らすことに集中する必要があります。
バンサー16

a:質問に言語が指定されていません。b:これは間違っていると思います。シングルリターンの理由はたくさんあります。複雑さを軽減し、読みやすさを向上させる
Ewan

1
複雑さを軽減することもあれば、増加させることもあります。wiki.c2.com/?ArrowAntiPatternを
ロバートジョンソン

いいえ、それは常に循環的複雑さを軽減すると思います。2番目の例を参照してください。CHECK2は常に動作します
ユアン・

早期復帰を有する条件にいくつかのコードを動かすことによって、コードを複雑に最適化である
ユアン

-3

冗長なelseステートメントを省略することを好みます。(コードのレビューやリファクタリングで)それを見るたびに、著者が制御フローを理解しているかどうか、そしてコードにバグがあるかもしれないと思います。

著者がelseステートメントが必要であり、したがってreturnステートメントの制御フローの意味を理解していなかったと確信した場合、このような理解の欠如は、この機能および一般的なバグの主な原因です。


3
私はたまたまあなたに同意しますが、これは世論調査ではありません。あなたの主張をバックアップしてください。そうしないと、有権者はあなたを親切に扱いません。
candied_orange
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.