web-dev-qa-db-ja.com

空きブロックを残さずにIf Elseコードのクリーンなリファクタリングを行う方法は?

if(condition1)
{
   Statement1A;
   Statement1B;
}
else if(condition2)
{
  Statement2;
}
else if(condition3)
{
  Statement3;
}
else 
{
   Statement1A;
   Statement1B;
}

   return;

ステートメントが重複しないように、そのコードをリファクタリングしたいと思います。 I 常に他の条件の前にcondition1をチェックする必要があります。 (つまり、条件の順序を変更することはできません)。また、他のすべての条件の後に&&!condition1を書きたくありません。

このように解決しました

if(condition1)
{
}
else if(condition2)
{
  Statement2;
  return;
}
else if(condition3)
{
  Statement3;
  return;
}

Statement1A;
Statement1B;
return;

でも、他の人にも(しばらくは自分でも)わかりやすい状態だとは思いません。

より良いアプローチとは何ですか?

13
M.C.
notCondition2And3 = !condition2 & !condition3; 
// in place of notCondition2And3 should be some meaningful name
// representing what it actually MEANS that neither condition2 nor condition3 were true

そしていま:

if (condition1 || notCondition2And3)
{
   Statement1A;
   Statement1B;
   return;
}
if (condition2)
{
   Statement2;
   return;
}
if (condition3)
{
   Statement3;
   return;
}

Kieveli's answer へのコメントで書いたように、メモリ管理の考慮事項がない場合(Cの場合など)、メソッド内の複数のreturnsには何の問題もありません。去る前にすべてのリソースを手動で解放する必要があるC++)。

または、まだ別のアプローチ。以下は、混乱しないように決定マトリックスです。

F F F - 1
---------
F F T - 3
---------    
F T F - 2
F T T - 2
---------    
T F F - 1
T F T - 1
T T F - 1
T T T - 1

TsおよびFsは、condition1condition2およびcondition3の値をそれぞれ表します。数字は結果を表します。

これにより、コードを次のように記述することも可能になります。

if (!condition1 && condition2) // outcome 2 only possible for FTF or FTT, condition3 irrelevant
{
   Statement2;
   return;
}
if (!condition1 && !condition2 && condition3)  // outcome 3 only when FFT
{
   Statement3;
   return;
}
// and for the remaining 5 combinations...
Statement1A;
Statement1B;

!condition1(両方に存在するifs)を抽出すると、次のようになります。

if (!condition1)
{
    if (condition2) // outcome 2 only possible for FTF or FTT, condition3 irrelevant
    {
       Statement2;
       return;
    }
    if (condition3)  // outcome 3 only when FFT
    {
       Statement3;
       return;
    }
}
// and for the remaining 5 combinations...
Statement1A;
Statement1B;

これはキエベリが示唆したとおりであり、3つの条件がすべて偽の場合は何も起こらないため、初期のreturnsに対する軽蔑だけが彼の実装にバグを引き起こしました。

または、それを元に戻すこともできます(これはおそらくすべての言語で機能するわけではありません。C#では複数の変数間の等価比較が可能であるため、C#で機能します)。これで実質的に最初の変数に戻ります。

// note that "condition1" on the right side of || is actually redundant and can be removed, 
// because if the expression on the right side of || gets evaluated at all,
// it means that condition1 must have been false anyway:

if (condition1 || (condition1 == condition2 == condition3 == false)) // takes care of all 4 x T** plus FFF (the one special case). 
{
    Statement1A;
    Statement1B;
    return;
}
// and now it's Nice and clean
if (condition2) 
{
    Statement2;
    return; // or "else" if you prefer
}
if (condition3)
{
    Statement3;
    return; // if necessary
}
32
Konrad Morawski

私はあなたの空のifとreturnステートメントのファンではありません。従うのは難しくなり、コードの本体内に複数のreturnステートメントを含めることは一般的に嫌われます。あなたの目標を考えると、私はこのようなことをします:

if ( ! condition1 )
{
   if ( condition2 )
      Statement2;
   else if ( condition3 )
      Statement3;
}
else
{
   Statement1A;
   Statement1B;
}

注:この解決策は間違っています! !condition1 &&!condition2 &&!condition3を検討してください。

10
Kieveli

ステートメントの繰り返しは、コードの一部をレプリケートする場合にのみ問題になります。この場合、「Statement1Aを実行してからStatement1Bを実行」します。

よりすっきりしたリファクタリングは、他のメソッドが2行しかない場合でも、それらのブロックをそれぞれ別のメソッドに移動することです。

void _statement1() {
  Statement1A;
  Statement1B;
}

このようなメソッドを使用すると、自分自身を繰り返す場所でそれを簡単に参照できます。

if(condition1) 
{
  _statement1()
} 
else if(condition2) 
{
  Statement2;
}
else if(condition3)
{
  Statement3;
}
else 
{
  _statement1()
}

もちろん、これは「自分自身を繰り返すのではなく、いつメソッドを使用すべきか?」というフォローアップの質問を引き起こします。 (私は「可能な限り」を優先しますが、言語によってはそのルールに著しく違反します。)

6
DougM

私はこのように好きです:

        if (!condition1)
        {
            if (condition2)
            {
                Statement2();
                return;
            }
            else if (condition3)
            {
                Statement3();
                return;
            }
        }

        Statement1A();
        Statement1B();
        return;

[〜#〜]編集[〜#〜]

Returnステートメントが1つとIFステートメントが2つしかないので、この方法がさらに好きです。

        if (!condition1 && condition2)
            Statement2();
        else if (!condition1 && condition3)
            Statement3();
        else
        {
            Statement1A();
            Statement1B();
        }
        return;
4
coutol

多分それは私だけかもしれませんが、if (!a && b)のような条件は比較的読みにくく、可能であればif (b && !a)を一般的に好みます(つまり、短絡評価に依存しない限り、 bは、aがfalseの場合にのみ評価されます)。

それに基づいて、私はおそらくそれを次のように書くでしょう:

if (condition2 && !condition1)
   statement2;
else if (condition3 && !condition1) 
   statement3;
else {
    statement1A;
    statement1B;
}

ただし、先ほど述べたように、評価されるstatementsのみを気にしても問題ありませんが、評価されるconditionsを気にしても機能しません。

2
Jerry Coffin

別の方法があります。元の制御フローを維持したいと思います。次に、2回行われた作業を関数に入れて、2つの異なる場所からその関数を呼び出します。

function X()
{  
  if(condition1)
  {
     HandleDefault();
     return;
  }

  if(condition2)
  {
     Statement2;
     return;
  }

  if(condition3)
  {
    Statement3;
    return;
  }

  HandleDefault();
  return;

}
/// 


function HandleDefault(   )
{
    Statement1A;
    Statement1B;
}

多くの場合、一緒に行くステートメントのブロックがある場合、関数に意味のある名前を付けることが可能であり、コードの可読性がさらに向上します。しかし、意味のある名前がない場合でも、(例で行ったように)わかりにくい名前でプライベート関数を作成できます。

これが条件付きの順序を保持するため、これが最良のソリューションであると私が思う理由は、Statement1AとStatement1Bのコピーを1つだけ保持することを実現し、ネスト構造や一時変数を導入することなく実現します。これにより、このコードを読むときに精神的に追跡する必要があるものの量が減ります。

現在、アーリーリターンが常に使用できるわけではない言語(特にC)がありますが、他の主流の言語では、私はあなたができると考えることができます。

2
jdv-Jan de Vaan

まだ言及されていない2つのアプローチは、ステートメントを式として書き直すことです(表現に適している場合)またはgで始まる小さなWordを使用することです。私はそれらのそれぞれを最良の代替案と考える場合があります。

式の場合、コードは次のようになります。

if (condition1 ||
     condition2 ? (action2(),1) :
     condition3 ? (action3(),1) :
     1)
  action1;

むしろ厄介で、一般的に私が使用するものではありませんが、条件2とアクション2の両方が、操作を試みて成功するかどうかを示すメソッドにカプセル化されている場合や、条件2とアクション3も同様に合理的かもしれません。その場合、次のようになります。

if (cantTryAnything || (
     tryFirstThing() != FIRST_TRY_SUCCESS &&
     trySecondThing() != SECOND_TRY_SUCCESS
)
{
  default actions here
}

何も試すことができなかった場合、または試みたすべてが失敗した場合は、デフォルトのアクションを実行します。

アクションを単にコピーするか、それらを別のメソッドに因数分解することは、上記のロジックスタイルまたはg___よりも一般的に好ましいことに注意してください。しかし、「ここでのデフォルトのアクション」のコードがローカル変数を操作する必要があり、その正確な動作が変わる可能性がある場合、コードを外部メソッドにカプセル化することが難しく、コードを複製すると、バージョンが重複して変更されるリスクが生じる可能性があります。完全に一致しなくなりました。さらに、特定の制御フローステートメントが有害であるという概念は、-most実世界の制御フローの問題が一般的な言語構成要素に適切にマップできること、およびwhen現実世界の問題は、使用する必要がある言語構成要素によく対応しています。アプリケーションが必要とする特定のプログラムフローが複数の条件の組み合わせで同じ動作を必要とする場合、問題を回避するよりも、そのようなすべての条件で共通のポイントに実行を明示的に強制する方が良いと私は思います。

0
supercat

次の2つのことが頭に浮かびます。

スイッチケース

常に適用できるわけではありませんが、コードを読みやすく、保守しやすくすることができます。基本的には列挙型のさまざまな条件をキャプチャし、コンテキストを分析して正しい列挙値を生成する関数を作成し、それを切り替えて適切なコードを実行します。

あなたは2つのことを達成します:

  • ロジックコードと実行を分離します。この分離により、各パーツのバグを修正しやすくなります。
  • 読みやすい制御フロー。スイッチの構造はかなり直感的で、特に列挙値に注意深く名前を付けた場合はそうです。

関数

関数内のコードの各ブロックをキャプチャし、関数の最初にいくつかのコードを挿入して、実行するかどうかを決定します。

ここでは、上記の分離は行われませんが、IF-ELSEコードフロー構造をより扱いやすいビットに分割します。さらに、バグが発生した場合、単一のコードブロックに分離されるため、修正が迅速になります。

ただし、最大の利点は、これらの関数のいずれかを変更しても、コードの他の部分には影響がないことです。

関数は、スイッチの場合よりもエッジを持っています。動的に追加できる場合があります。 1つの自然な拡張は、コードの最初の部分がコンテキストを分析してアクションをリストにキューイングし、次に2番目の部分がこれらのアクションを順次実行するコマンドパターンに似たものになります。

選択した戦略に関係なくKEEP IT SIMPLE一連のif-elseで十分な場合があり、そのままにしておく必要があります。経験則として、同じ問題のように見えるものを修正するために同じコードに戻った場合、またはまだ新しい条件を追加するために頭をかき回さなければならないことが多い場合は、おそらく、言及されているような代替策を探すときです上記。

0
Newtopian